@uramo, boa pergunta e ótima opção para a melhor resposta. Ame ou odeie, você não tem segurança de tipo e (pelo menos em Ruby) não tem erro de digitação. Fiquei emocionado quando descobri enums em C # e mais tarde em Java (escolha um valor, mas dentre esses!), Ruby não fornece uma maneira real de fazer isso, em nenhum caso.
Dan Rosenstark 11/03/10
2
O problema com esta pergunta é que Java e C # enums são coisas dramaticamente diferentes. Um membro enum Java é uma instância de objeto e um singleton. Um enum Java pode ter um construtor. Por outro lado, enums de C # são baseadas em valores primitivos. Qual comportamento o pesquisador está procurando? Embora seja provável que o caso C # seja desejado, o Java é mencionado explicitamente, em vez de C ou C ++, então há alguma dúvida. Quanto a sugerir que não há como ser 'seguro' em Ruby, isso é claramente falso, mas você precisa implementar algo mais sofisticado.
user1164178
Respostas:
319
Dois caminhos. Símbolos ( :foonotação) ou constantes ( FOOnotação).
Os símbolos são apropriados quando você deseja aprimorar a legibilidade sem desarrumar o código com cadeias literais.
As constantes são apropriadas quando você tem um valor subjacente importante. Apenas declare um módulo para manter suas constantes e, em seguida, declare as constantes dentro dele.
moduleFoo
BAR =1
BAZ =2
BIZ =4end
flags =Foo::BAR |Foo::BAZ # flags = 3
E se esses enum também forem armazenados no banco de dados? A notação de símbolo funciona? Duvido ...
Phương Nguyễn
Eu usaria a abordagem de constantes se estivesse salvando em um banco de dados. Obviamente, você precisa fazer algum tipo de pesquisa ao extrair os dados do banco de dados. Você também pode usar algo como :minnesota.to_sao salvar em um banco de dados para salvar a versão em cadeia do símbolo. O Rails, acredito, tem alguns métodos auxiliares para lidar com isso.
mlibby
7
Um módulo não seria melhor para agrupar constantes - como você não fará nenhuma instância dele?
21812 thomthom
3
Apenas um comentário. Ruby está um pouco preocupada com as convenções de nomenclatura, mas não é realmente óbvia sobre elas até você tropeçar nelas. Os nomes das enumerações devem estar em maiúsculas e a primeira letra do nome do módulo deve ser maiúscula para que o ruby saiba que o módulo é um módulo de constantes.
Rokujolady # 8/13
3
Não é inteiramente verdade. A primeira letra da constante deve ser maiúscula, mas nem todas as letras precisam ser. Essa é uma questão de preferência da convenção. Por exemplo, todos os nomes de módulos e nomes de classes também são constantes.
Michael Brown
59
Estou surpreso que ninguém tenha oferecido algo como o seguinte (extraído da gema RAPI ):
classEnum
private
defself.enum_attr(name, num)
name = name.to_s
define_method(name +'?')do@attrs& num !=0end
define_method(name +'=')do|set|if set
@attrs|= num
else@attrs&=~num
endendend
public
def initialize(attrs =0)@attrs= attrs
enddef to_i
@attrsendend
Isso funciona bem em cenários de banco de dados ou ao lidar com constantes / enumerações de estilo C (como é o caso do FFI , do qual o RAPI faz uso extensivo).
Além disso, você não precisa se preocupar com erros de digitação que causam falhas silenciosas, como faria com o uso de uma solução do tipo hash.
Essa é uma ótima maneira de resolver esse problema específico, mas a razão pela qual ninguém sugeriu que provavelmente tenha a ver com o fato de não ser muito parecido com enumerações C # / Java.
mlibby
1
Isso é um pouco incompleto, mas serve como uma boa dica de como você pode implementar soluções com uma abordagem dinâmica. Ele tem alguma semelhança com uma enumeração C # com o conjunto FlagsAttribute, mas, como as soluções baseadas em símbolos / constantes acima, é uma resposta de muitas. O problema é a pergunta original, que é confusa em sua intenção (C # e Java não são intercambiáveis). Existem várias maneiras de especificar objetos no Ruby; selecionar o caminho certo depende do problema que está sendo resolvido. Replicar servilmente os recursos dos quais você não precisa é equivocado. A resposta correta precisa depender do contexto.
user1164178
52
A maneira mais idiomática de fazer isso é usar símbolos. Por exemplo, em vez de:
enum {
FOO,
BAR,
BAZ
}
myFunc(FOO);
... você pode simplesmente usar símbolos:
# You don't actually need to declare these, of course--this is# just to show you what symbols look like.:foo
:bar
:baz
my_func(:foo)
Isso é um pouco mais aberto do que enums, mas se encaixa bem com o espírito Ruby.
Os símbolos também funcionam muito bem. Comparar dois símbolos para igualdade, por exemplo, é muito mais rápido do que comparar duas cadeias.
As estruturas populares do Ruby dependem muito da metaprogramação em tempo de execução, e executar muita verificação no tempo de carregamento tiraria a maior parte do poder expressivo do Ruby. Para evitar problemas, a maioria dos programadores Ruby pratica o design orientado a testes, que encontrará não apenas erros de digitação, mas também erros de lógica.
emk
10
@ yar: Bem, o design de linguagem é uma série de vantagens e recursos de linguagem interagem. Se você deseja uma linguagem boa e altamente dinâmica, vá com Ruby, escreva seus testes de unidade primeiro e siga o espírito da linguagem. :-) Se não é isso que você procura, existem dezenas de outros idiomas excelentes por aí, cada um dos quais faz trocas diferentes.
emk
10
@emk, eu concordo, mas meu problema pessoal é que me sinto bastante à vontade em Ruby, mas não me refiro à refatoração em Ruby. E agora que comecei a escrever testes de unidade (finalmente), percebo que eles não são uma panacéia: meu palpite é 1) que o código Ruby não é massivamente refatorado com frequência, na prática, e 2) Ruby não é o fim em termos de linguagens dinâmicas, precisamente porque é difícil refatorar automaticamente. Veja minha pergunta 2317579, que foi assumida, estranhamente, pelo pessoal da Smalltalk.
Dan Rosenstark 10/03/10
4
Sim, mas o uso dessas strings não estaria no espírito da linguagem C #, é simplesmente uma prática ruim.
Eu acho que essa é a melhor resposta. A funcionalidade, sintaxe e sobrecarga mínima de código se aproximam do Java / C #. Além disso, você pode aninhar as definições ainda mais profundamente que um nível e ainda recuperar todos os valores com MyClass :: MY_ENUM.flatten. Como observação, eu usaria nomes em maiúsculas aqui, como é o padrão para constantes no Ruby. MyClass :: MyEnum pode ser confundido com uma referência a uma subclasse.
Janosch
@ Janosch, eu atualizei os nomes. obrigado pela sugestão
Alexey
Eu ainda estou um pouco confuso, e o link 410'd (não, não 404). Você poderia dar exemplos de como esse enum seria usado?
21415 Shelvacu
17
Se você estiver usando o Rails 4.2 ou superior, poderá usar as enumerações do Rails.
O Rails agora tem enums por padrão, sem a necessidade de incluir gemas.
Isso é muito semelhante (e mais com os recursos) ao Java, enums C ++.
Como você disse - não é útil se o OP não estiver usando o Rails (ou mais precisamente, o objeto não é do tipo ActiveRecord). Apenas explicar o meu voto negativo é tudo.
Ger
2
Essas não são enums no Ruby, é uma interface do ActiveRecord para as enums no seu banco de dados. Não é uma solução generalizável que pode ser aplicada em qualquer outro caso de uso.
Adam Lassek
Eu já mencionei isso na minha resposta.
vedant 13/05/16
Esta é a melhor resposta do IFF usando o Rails.
theUtherSide
Não gosto porque deve ser armazenado em um banco de dados Rails (para funcionar) e porque permite criar muitas instâncias da Conversationclasse - acredito que deve permitir apenas 1 instância.
prograils
8
Esta é a minha abordagem para enums em Ruby. Eu estava indo para curto e doce, não necessariamente o mais parecido com C. Alguma ideia?
IMHO isso replica muito de perto o uso e a finalidade (segurança de tipo) do Java, também, por uma questão de preferência, as constantes podem ser definidas assim:class ABC; end
wik
8
Eu sei que já faz muito tempo desde que o cara postou esta pergunta, mas eu tinha a mesma pergunta e essa postagem não me deu a resposta. Eu queria uma maneira fácil de ver o que o número representa, a comparação fácil e, acima de tudo, o suporte ao ActiveRecord para pesquisa usando a coluna que representa a enumeração.
Não encontrei nada, então fiz uma implementação incrível chamada yinum que permitiu tudo o que estava procurando. Feito toneladas de especificações, então eu tenho certeza que é seguro.
Se você estiver preocupado com erros de digitação com símbolos, verifique se o seu código gera uma exceção ao acessar um valor com uma chave inexistente. Você pode fazer isso usando, em fetchvez de []:
my_value = my_hash.fetch(:key)
ou fazendo o hash gerar uma exceção por padrão, se você fornecer uma chave inexistente:
my_hash =Hash.new do|hash, key|
raise "You tried to access using #{key.inspect} when the only keys we have are #{hash.keys.inspect}"end
Se o hash já existir, você poderá adicionar um comportamento de criação de exceção:
my_hash =Hash[[[1,2]]]
my_hash.default_proc = proc do|hash, key|
raise "You tried to access using #{key.inspect} when the only keys we have are #{hash.keys.inspect}"end
Normalmente, você não precisa se preocupar com a segurança de erros de digitação com constantes. Se você escreve incorretamente um nome constante, isso geralmente gera uma exceção.
Parece que você está defendendo emular enums com hashes , sem dizer isso explicitamente. Pode ser uma boa ideia editar sua resposta para dizer isso. (Eu também tenho atualmente uma necessidade de algo como enums em Ruby, e minha primeira abordagem para resolvê-lo é usando hashes: FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}Isto define os símbolos-chave. missing, somethingEtc., e também os torna comparáveis via os valores associados.)
Teemu Leisti
Quero dizer, sem dizer isso no início da resposta.
Teemu Leisti
4
Alguém foi em frente e escreveu uma gema de rubi chamada Renum . Alega obter o comportamento mais próximo de Java / C #. Pessoalmente, ainda estou aprendendo Ruby, e fiquei um pouco chocado quando quis fazer com que uma classe específica contivesse um enum estático, possivelmente um hash, que não foi encontrado com facilidade pelo google.
Eu nunca precisei de um enum em Ruby. Símbolos e constantes são idiomáticos e resolvem os mesmos problemas, não são?
Chuck #
Provavelmente Chuck; mas pesquisar por um enum em rubi não o levará tão longe. Ele mostrará os resultados da melhor tentativa das pessoas em um equivalente direto. O que me faz pensar, talvez haja algo de bom em envolver o conceito.
Dlamblin 5/03/09
@Chuck Símbolos e constantes não impõem, por exemplo, que um valor deva ser um de um pequeno conjunto de valores.
David Moles
3
Tudo depende de como você usa Java ou C # enums. A forma como você o usa determinará a solução que você escolherá no Ruby.
Prática muito melhor para usar símbolos aqui, IMO.
Collin Graves
3
Recentemente, lançamos uma gema que implementa Enums em Ruby . No meu post, você encontrará as respostas para suas perguntas. Também descrevi lá por que nossa implementação é melhor do que a existente (na verdade, existem muitas implementações desse recurso no Ruby ainda como gemas).
# bar.rb
require 'ostruct'# not needed when using Rails# by patching Array you have a simple way of creating a ENUM-styleclassArraydef to_enum(base=0)OpenStruct.new(map.with_index(base).to_h)endendclassBar
MY_ENUM =OpenStruct.new(ONE:1, TWO:2, THREE:3)
MY_ENUM2 =%w[ONE TWO THREE].to_enum
def use_enum (value)case value
when MY_ENUM.ONE
puts "Hello, this is ENUM 1"when MY_ENUM.TWO
puts "Hello, this is ENUM 2"when MY_ENUM.THREE
puts "Hello, this is ENUM 3"else
puts "#{value} not found in ENUM"endendend# usage
foo =Bar.new
foo.use_enum 1
foo.use_enum 2
foo.use_enum 9# put this code in a file 'bar.rb', start IRB and type: load 'bar.rb'
Você está capitalizando a segunda palavra em variáveis (por exemplo server_Symb) por um motivo específico? A menos que haja uma razão específica, é idiomático que as variáveis sejam snake_case_with_all_lower_casee que os símbolos existam :lower_case.
Andrew Grimm
1
@Andrew; esse exemplo foi extraído de uma coisa do mundo real e a documentação do protocolo de rede usou xxx_Yyy; portanto, o código em vários idiomas usou o mesmo conceito para que se pudesse acompanhar as alterações de especificação.
21711 Jonke
1
Golfe código: server_Symb.each_with_index { |e,i| server_Enum[e] = i}. Não há necessidade i = 0.
Andrew Grimm
2
Eu implementei enums como esse
moduleEnumTypedefself.find_by_id id
if id.instance_of?String
id = id.to_i
end
values.each do|type|if id == type.id
return type
endendnilenddefself.values
[@ENUM_1,@ENUM_2]endclassEnum
attr_reader :id,:label
def initialize id, label
@id= id
@label= label
endend@ENUM_1=Enum.new(1,"first")@ENUM_2=Enum.new(2,"second")end
Eu não recomendaria essa abordagem porque ela depende de você definir manualmente os valores e garantir que você receba o pedido corretamente :VAL. Seria melhor começar com uma matriz e construir o hash utilizando.map.with_index
DaveMongoose
1
O ponto exato é amarrar-se a um valor que é ditado por terceiros. Não se trata de extensibilidade propriamente dita, mas de ter que lidar com restrições externas que afetam a computabilidade dentro dos limites do processo.
JJK
Ponto justo! Nesse caso, ele definitivamente faz sentido para especificar os valores, mas eu estaria inclinado a fazer o lookup reverso com .keyou .invertem vez de uma :VALchave ( stackoverflow.com/a/10989394/2208016 )
DaveMongoose
Sim, isso é (de volta para você) um ponto justo. Meu rubi era deselegante e pesado. Def seria usar keyouinvert
jjk 14/11/19
1
A maioria das pessoas usa símbolos (essa é a :foo_barsintaxe). Eles são uma espécie de valores opacos únicos. Os símbolos não pertencem a nenhum tipo de enumeração, por isso não são uma representação fiel do tipo de enumeração de C, mas isso é praticamente o melhor possível.
Às vezes, tudo o que preciso é ser capaz de buscar o valor de enum e identificar seu nome semelhante ao mundo java.
moduleEnumdef get_value(str)
const_get(str)enddef get_name(sym)
sym.to_s.upcase
endendclassFruits
include Enum
APPLE ="Delicious"
MANGO ="Sweet"endFruits.get_value('APPLE')#'Delicious'Fruits.get_value('MANGO')# 'Sweet'Fruits.get_name(:apple)# 'APPLE'Fruits.get_name(:mango)# 'MANGO'
Isso para mim serve ao propósito de enum e o mantém extensível também. Você pode adicionar mais métodos à classe Enum e o viola os obtém gratuitamente em todas as enumerações definidas. por exemplo. get_all_names e coisas assim.
Outra abordagem é usar uma classe Ruby com um hash contendo nomes e valores, conforme descrito na seguinte postagem no blog do RubyFleebie . Isso permite converter facilmente entre valores e constantes (especialmente se você adicionar um método de classe para pesquisar o nome de um determinado valor).
Eu acho que a melhor maneira de implementar enumeração como tipos é com símbolos, já que eles se comportam como números inteiros (quando se trata de desempenho, object_id é usado para fazer comparações); você não precisa se preocupar com a indexação e eles parecem muito legais no seu código xD
Outra maneira de imitar uma enumeração com o tratamento consistente da igualdade (adotado descaradamente por Dave Thomas). Permite enumerações abertas (como símbolos) e enumerações fechadas (predefinidas).
Respostas:
Dois caminhos. Símbolos (
:foo
notação) ou constantes (FOO
notação).Os símbolos são apropriados quando você deseja aprimorar a legibilidade sem desarrumar o código com cadeias literais.
As constantes são apropriadas quando você tem um valor subjacente importante. Apenas declare um módulo para manter suas constantes e, em seguida, declare as constantes dentro dele.
fonte
:minnesota.to_s
ao salvar em um banco de dados para salvar a versão em cadeia do símbolo. O Rails, acredito, tem alguns métodos auxiliares para lidar com isso.Estou surpreso que ninguém tenha oferecido algo como o seguinte (extraído da gema RAPI ):
Que pode ser usado assim:
Exemplo:
Isso funciona bem em cenários de banco de dados ou ao lidar com constantes / enumerações de estilo C (como é o caso do FFI , do qual o RAPI faz uso extensivo).
Além disso, você não precisa se preocupar com erros de digitação que causam falhas silenciosas, como faria com o uso de uma solução do tipo hash.
fonte
A maneira mais idiomática de fazer isso é usar símbolos. Por exemplo, em vez de:
... você pode simplesmente usar símbolos:
Isso é um pouco mais aberto do que enums, mas se encaixa bem com o espírito Ruby.
Os símbolos também funcionam muito bem. Comparar dois símbolos para igualdade, por exemplo, é muito mais rápido do que comparar duas cadeias.
fonte
Eu uso a seguinte abordagem:
Gosto pelas seguintes vantagens:
MY_ENUM
MY_VALUE_1
Os símbolos podem ser melhores porque você não precisa escrever o nome da classe externa, se estiver usando-o em outra classe (
MyClass::MY_VALUE_1
)fonte
Se você estiver usando o Rails 4.2 ou superior, poderá usar as enumerações do Rails.
O Rails agora tem enums por padrão, sem a necessidade de incluir gemas.
Isso é muito semelhante (e mais com os recursos) ao Java, enums C ++.
Citado em http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html :
fonte
Conversation
classe - acredito que deve permitir apenas 1 instância.Esta é a minha abordagem para enums em Ruby. Eu estava indo para curto e doce, não necessariamente o mais parecido com C. Alguma ideia?
fonte
Confira a gema ruby-enum, https://github.com/dblock/ruby-enum .
fonte
Talvez a melhor abordagem leve seja
Dessa maneira, os valores têm nomes associados, como em Java / C #:
Para obter todos os valores, você pode fazer
Se você deseja o valor ordinal de uma enumeração, pode fazer
fonte
class ABC; end
Eu sei que já faz muito tempo desde que o cara postou esta pergunta, mas eu tinha a mesma pergunta e essa postagem não me deu a resposta. Eu queria uma maneira fácil de ver o que o número representa, a comparação fácil e, acima de tudo, o suporte ao ActiveRecord para pesquisa usando a coluna que representa a enumeração.
Não encontrei nada, então fiz uma implementação incrível chamada yinum que permitiu tudo o que estava procurando. Feito toneladas de especificações, então eu tenho certeza que é seguro.
Alguns exemplos de recursos:
fonte
Se você estiver preocupado com erros de digitação com símbolos, verifique se o seu código gera uma exceção ao acessar um valor com uma chave inexistente. Você pode fazer isso usando, em
fetch
vez de[]
:ou fazendo o hash gerar uma exceção por padrão, se você fornecer uma chave inexistente:
Se o hash já existir, você poderá adicionar um comportamento de criação de exceção:
Normalmente, você não precisa se preocupar com a segurança de erros de digitação com constantes. Se você escreve incorretamente um nome constante, isso geralmente gera uma exceção.
fonte
FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}
Isto define os símbolos-chave.missing
,something
Etc., e também os torna comparáveis via os valores associados.)Alguém foi em frente e escreveu uma gema de rubi chamada Renum . Alega obter o comportamento mais próximo de Java / C #. Pessoalmente, ainda estou aprendendo Ruby, e fiquei um pouco chocado quando quis fazer com que uma classe específica contivesse um enum estático, possivelmente um hash, que não foi encontrado com facilidade pelo google.
fonte
Tudo depende de como você usa Java ou C # enums. A forma como você o usa determinará a solução que você escolherá no Ruby.
Experimente o
Set
tipo nativo , por exemplo:fonte
Set[:a, :b, :c]
?Recentemente, lançamos uma gema que implementa Enums em Ruby . No meu post, você encontrará as respostas para suas perguntas. Também descrevi lá por que nossa implementação é melhor do que a existente (na verdade, existem muitas implementações desse recurso no Ruby ainda como gemas).
fonte
Outra solução está usando o OpenStruct. É bem simples e limpo.
https://ruby-doc.org/stdlib-2.3.1/libdoc/ostruct/rdoc/OpenStruct.html
Exemplo:
fonte
Símbolos é o caminho do rubi. No entanto, às vezes é preciso conversar com algum código C ou algo ou Java que exponha algum enum para várias coisas.
Isso pode ser usado assim
É claro que isso pode ser feito abstrato e você pode criar nossa própria classe Enum
fonte
server_Symb
) por um motivo específico? A menos que haja uma razão específica, é idiomático que as variáveis sejamsnake_case_with_all_lower_case
e que os símbolos existam:lower_case
.server_Symb.each_with_index { |e,i| server_Enum[e] = i}
. Não há necessidadei = 0
.Eu implementei enums como esse
então é fácil fazer operações
...
...
fonte
Isso parece um pouco supérfluo, mas é uma metodologia que já usei algumas vezes, especialmente onde estou me integrando ao xml ou algo assim.
Isso me dá o rigor do ac # enum e está vinculado ao modelo.
fonte
:VAL
. Seria melhor começar com uma matriz e construir o hash utilizando.map.with_index
.key
ou.invert
em vez de uma:VAL
chave ( stackoverflow.com/a/10989394/2208016 )key
ouinvert
A maioria das pessoas usa símbolos (essa é a
:foo_bar
sintaxe). Eles são uma espécie de valores opacos únicos. Os símbolos não pertencem a nenhum tipo de enumeração, por isso não são uma representação fiel do tipo de enumeração de C, mas isso é praticamente o melhor possível.fonte
Resultado:
1 - a
2 - b
3 - c
4 - d
fonte
to_enum
dá-lhe uma enumera tor , enquanto queenum
no C # / sentido Java é uma enumera çãoResultado:
fonte
Às vezes, tudo o que preciso é ser capaz de buscar o valor de enum e identificar seu nome semelhante ao mundo java.
Isso para mim serve ao propósito de enum e o mantém extensível também. Você pode adicionar mais métodos à classe Enum e o viola os obtém gratuitamente em todas as enumerações definidas. por exemplo. get_all_names e coisas assim.
fonte
Outra abordagem é usar uma classe Ruby com um hash contendo nomes e valores, conforme descrito na seguinte postagem no blog do RubyFleebie . Isso permite converter facilmente entre valores e constantes (especialmente se você adicionar um método de classe para pesquisar o nome de um determinado valor).
fonte
Eu acho que a melhor maneira de implementar enumeração como tipos é com símbolos, já que eles se comportam como números inteiros (quando se trata de desempenho, object_id é usado para fazer comparações); você não precisa se preocupar com a indexação e eles parecem muito legais no seu código xD
fonte
Outra maneira de imitar uma enumeração com o tratamento consistente da igualdade (adotado descaradamente por Dave Thomas). Permite enumerações abertas (como símbolos) e enumerações fechadas (predefinidas).
fonte
Experimente o inum. https://github.com/alfa-jpn/inum
veja mais https://github.com/alfa-jpn/inum#usage
fonte