Enums in Ruby

Quelle est la meilleure façon d’implémenter l’énum idiom dans Ruby? Je cherche quelque chose que je peux utiliser (presque) comme les énumérations Java / C #.

Deux façons. Symboles (notation :foo ) ou constantes (notation FOO ).

Les symboles sont appropriés lorsque vous souhaitez améliorer la lisibilité sans jeter du code avec des chaînes littérales.

 postal_code[:minnesota] = "MN" postal_code[:new_york] = "NY" 

Les constantes sont appropriées lorsque vous avez une valeur sous-jacente importante. Déclarez simplement un module pour contenir vos constantes, puis déclarez les constantes dans celui-ci.

 module Foo BAR = 1 BAZ = 2 BIZ = 4 end flags = Foo::BAR | Foo::BAZ # flags = 3 

La méthode la plus idiomatique consiste à utiliser des symboles. Par exemple, au lieu de:

 enum { FOO, BAR, BAZ } myFunc(FOO); 

… vous pouvez simplement utiliser des symboles:

 # 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) 

C’est un peu plus ouvert que les énumérations, mais il correspond bien à l’esprit Ruby.

Les symboles fonctionnent également très bien. La comparaison de deux symboles pour l’égalité, par exemple, est beaucoup plus rapide que la comparaison de deux chaînes.

Je suis surpris que personne n’ait offert quelque chose comme ce qui suit (récolté à partir du joyau RAPI ):

 class Enum private def self.enum_attr(name, num) name = name.to_s define_method(name + '?') do @attrs & num != 0 end define_method(name + '=') do |set| if set @attrs |= num else @attrs &= ~num end end end public def initialize(attrs = 0) @attrs = attrs end def to_i @attrs end end 

Qui peut être utilisé comme ça:

 class FileAtsortingbutes < Enum enum_attr :readonly, 0x0001 enum_attr :hidden, 0x0002 enum_attr :system, 0x0004 enum_attr :directory, 0x0010 enum_attr :archive, 0x0020 enum_attr :in_rom, 0x0040 enum_attr :normal, 0x0080 enum_attr :temporary, 0x0100 enum_attr :sparse, 0x0200 enum_attr :reparse_point, 0x0400 enum_attr :compressed, 0x0800 enum_attr :rom_module, 0x2000 end 

Exemple:

 >> example = FileAtsortingbutes.new(3) => # >> example.readonly? => true >> example.hidden? => true >> example.system? => false >> example.system = true => true >> example.system? => true >> example.to_i => 7 

Cela joue bien dans les scénarios de firebase database, ou dans les constantes / énumérations de style C (comme dans le cas de l'utilisation de FFI , que RAPI utilise souvent).

En outre, vous n'avez pas à vous soucier des fautes de frappe provoquant des échecs silencieux, comme vous le feriez avec une solution de type hachage.

J’utilise l’approche suivante:

 class MyClass MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2'] end 

Je l’aime pour les avantages suivants:

  1. Il regroupe les valeurs visuellement comme un tout
  2. Il vérifie le temps de compilation (contrairement à l’utilisation de symboles)
  3. Je peux facilement accéder à la liste de toutes les valeurs possibles: juste MY_ENUM
  4. Je peux facilement accéder à des valeurs distinctes: MY_VALUE_1
  5. Il peut avoir des valeurs de tout type, pas seulement des symboles

Les symboles peuvent être meilleurs car vous n’avez pas à écrire le nom de la classe externe, si vous l’utilisez dans une autre classe ( MyClass::MY_VALUE_1 )

Si vous utilisez Rails 4.2 ou plus, vous pouvez utiliser les énumérations Rails.

Rails a maintenant des énumérations par défaut sans avoir besoin d’inclure des gemmes.

Ceci est très similaire (et plus aux fonctionnalités) à Java, aux énumérations C ++.

Cité de http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html :

 class Conversation < ActiveRecord::Base enum status: [ :active, :archived ] end # conversation.update! status: 0 conversation.active! conversation.active? # => true conversation.status # => "active" # conversation.update! status: 1 conversation.archived! conversation.archived? # => true conversation.status # => "archived" # conversation.update! status: 1 conversation.status = "archived" # conversation.update! status: nil conversation.status = nil conversation.status.nil? # => true conversation.status # => nil 

C’est mon approche des énumérations dans Ruby. J’allais pour le court et le doux, pas nécessairement le plus C-like. Des pensées?

 module Kernel def enum(values) Module.new do |mod| values.each_with_index{ |v,i| mod.const_set(v.to_s.capitalize, 2**i) } def mod.inspect "#{self.name} {#{self.constants.join(', ')}}" end end end end States = enum %w(Draft Published Trashed) => States {Draft, Published, Trashed} States::Draft => 1 States::Published => 2 States::Trashed => 4 States::Draft | States::Trashed => 3 

Découvrez le bijou ruby-enum, https://github.com/dblock/ruby-enum .

 class Gender include Enum Gender.define :MALE, "male" Gender.define :FEMALE, "female" end Gender.all Gender::MALE 

Je sais que ça fait longtemps que le gars n’a pas posté cette question, mais j’ai eu la même question et ce post ne m’a pas donné la réponse. Je voulais un moyen facile de voir ce que représente le nombre, une comparaison facile et surtout la prise en charge de la recherche ActiveRecord à l’aide de la colonne représentant l’énumération.

Je n’ai rien trouvé, j’ai donc réalisé une implémentation géniale appelée yinum qui autorisait tout ce que je cherchais. Des tonnes de spécifications, alors je suis sûr que c’est sûr.

Quelques exemples de fonctionnalités:

 COLORS = Enum.new(:COLORS, :red => 1, :green => 2, :blue => 3) => COLORS(:red => 1, :green => 2, :blue => 3) COLORS.red == 1 && COLORS.red == :red => true class Car < ActiveRecord::Base attr_enum :color, :COLORS, :red => 1, :black => 2 end car = Car.new car.color = :red / "red" / 1 / "1" car.color => Car::COLORS.red car.color.black? => false Car.red.to_sql => "SELECT `cars`.* FROM `cars` WHERE `cars`.`color` = 1" Car.last.red? => true 

Quelqu’un est allé de l’avant et a écrit une gemme de rbuy appelée Renum . Il prétend avoir le comportement similaire à Java / C #. Personnellement, j’apprends toujours Ruby, et j’ai été un peu choqué quand j’ai voulu faire en sorte qu’une classe spécifique contienne un enum statique, peut-être un hachage, qui n’était pas facilement accessible via google.

Si vous vous inquiétez des fautes de frappe avec des symboles, assurez-vous que votre code déclenche une exception lorsque vous accédez à une valeur avec une clé inexistante. Vous pouvez le faire en utilisant fetch plutôt que [] :

 my_value = my_hash.fetch(:key) 

ou en faisant en sorte que le hachage génère une exception par défaut si vous fournissez une clé inexistante:

 my_hash = Hash.new do |hash, key| raise "You sortinged to access using #{key.inspect} when the only keys we have are #{hash.keys.inspect}" end 

Si le hachage existe déjà, vous pouvez append le comportement de levée d’exception:

 my_hash = Hash[[[1,2]]] my_hash.default_proc = proc do |hash, key| raise "You sortinged to access using #{key.inspect} when the only keys we have are #{hash.keys.inspect}" end 

Normalement, vous n’avez pas à vous soucier de la sécurité typographique avec des constantes. Si vous orthographiez mal un nom de constante, il déclenchera généralement une exception.

La meilleure approche légère serait peut-être

 module MyConstants ABC = Class.new DEF = Class.new GHI = Class.new end 

De cette façon, les valeurs ont des noms associés, comme en Java / C #:

 MyConstants::ABC => MyConstants::ABC 

Pour obtenir toutes les valeurs, vous pouvez faire

 MyConstants.constants => [:ABC, :DEF, :GHI] 

Si vous voulez la valeur ordinale d’une enum, vous pouvez le faire

 MyConstants.constants.index :GHI => 2 

Récemment, nous avons publié un joyau qui implémente Enums dans Ruby . Dans mon post, vous trouverez les réponses à vos questions. J’ai également décrit la raison pour laquelle notre implémentation est meilleure que celles existantes (en réalité, il existe de nombreuses implémentations de cette fonctionnalité dans Ruby en tant que gemmes).

Tout dépend comment vous utilisez Java ou les énumérations C #. Comment vous l’utilisez déterminera la solution que vous choisirez dans Ruby.

Essayez le type de Set natif, par exemple:

 >> enum = Set['a', 'b', 'c'] => # >> enum.member? "b" => true >> enum.member? "d" => false >> enum.add? "b" => nil >> enum.add? "d" => # 

Les symboles sont la manière ruby. Cependant, il est parfois nécessaire de parler à un code C ou à quelque chose ou à Java pour exposer certains points.


 #server_roles.rb module EnumLike def EnumLike.server_role server_Symb=[ :SERVER_CLOUD, :SERVER_DESKTOP, :SERVER_WORKSTATION] server_Enum=Hash.new i=0 server_Symb.each{ |e| server_Enum[e]=i; i +=1} return server_Symb,server_Enum end end 

Cela peut alors être utilisé comme ça


 require 'server_roles' sSymb, sEnum =EnumLike.server_role() foreignvec[sEnum[:SERVER_WORKSTATION]]=8 

Cela peut bien sûr être rendu abstrait et vous pouvez lancer notre propre classe Enum

J’ai mis en enums comme ça

 module EnumType def self.find_by_id id if id.instance_of? Ssortingng id = id.to_i end values.each do |type| if id == type.id return type end end nil end def self.values [@ENUM_1, @ENUM_2] end class Enum attr_reader :id, :label def initialize id, label @id = id @label = label end end @ENUM_1 = Enum.new(1, "first") @ENUM_2 = Enum.new(2, "second") end 

alors c’est facile de faire des opérations

 EnumType.ENUM_1.label 

 enum = EnumType.find_by_id 1 

 valueArray = EnumType.values 

Cela semble un peu superflu, mais il s’agit d’une méthodologie que j’ai utilisée à quelques resockets, en particulier là où j’intègre xml ou quelque chose du genre.

 #model class Profession def self.pro_enum {:BAKER => 0, :MANAGER => 1, :FIREMAN => 2, :DEV => 3, :VAL => ["BAKER", "MANAGER", "FIREMAN", "DEV"] } end end Profession.pro_enum[:DEV] #=>3 Profession.pro_enum[:VAL][1] #=>MANAGER 

Cela me donne la rigueur du rendement et il est lié au modèle.

Une autre solution consiste à utiliser OpenStruct. C’est assez simple et net.

https://ruby-doc.org/stdlib-2.3.1/libdoc/ostruct/rdoc/OpenStruct.html

Exemple:

 # bar.rb require 'ostruct' # not needed when using Rails # by patching Array you have a simple way of creating a ENUM-style class Array def to_enum(base=0) OpenStruct.new(map.with_index(base).to_h) end end class Bar 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" end end end # 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' 

La plupart des gens utilisent des symboles (c’est la syntaxe :foo_bar ). Ils sont en quelque sorte des valeurs opaques uniques. Les symboles n’appartiennent à aucun type de style enum, ils ne sont donc pas vraiment une représentation fidèle du type enum de C, mais c’est à peu près aussi bien que ça.

 irb(main):016:0> num=[1,2,3,4] irb(main):017:0> alph=['a','b','c','d'] irb(main):018:0> l_enum=alph.to_enum irb(main):019:0> s_enum=num.to_enum irb(main):020:0> loop do irb(main):021:1* puts "#{s_enum.next} - #{l_enum.next}" irb(main):022:1> end 

Sortie:

1 – un
2 – b
3 – c
4 – d

 module Status BAD = 13 GOOD = 24 def self.to_str(status) for sym in self.constants if self.const_get(sym) == status return sym.to_s end end end end mystatus = Status::GOOD puts Status::to_str(mystatus) 

Sortie:

 GOOD 

Parfois, tout ce dont j’ai besoin est de pouvoir extraire la valeur d’énum et d’identifier son nom similaire à celui de java world.

 module Enum def get_value(str) const_get(str) end def get_name(sym) sym.to_s.upcase end end class Fruits include Enum APPLE = "Delicious" MANGO = "Sweet" end Fruits.get_value('APPLE') #'Delicious' Fruits.get_value('MANGO') # 'Sweet' Fruits.get_name(:apple) # 'APPLE' Fruits.get_name(:mango) # 'MANGO' 

Cela me sert le but de enum et le garde très extensible aussi. Vous pouvez append plus de méthodes à la classe Enum et les obtenir gratuitement dans toutes les énumérations définies. par exemple. get_all_names et des trucs comme ça.

Une autre approche consiste à utiliser une classe Ruby avec un hachage contenant les noms et les valeurs, comme décrit dans l’article suivant du blog RubyFleebie . Cela vous permet de convertir facilement entre les valeurs et les constantes (surtout si vous ajoutez une méthode de classe pour rechercher le nom d’une valeur donnée).

Je pense que le meilleur moyen d’implémenter l’énumération comme les types est d’utiliser des symboles, car ils se comportent plutôt comme des entiers (quand il s’agit de performace, object_id est utilisé pour faire des comparaisons); vous n’avez pas besoin de vous soucier de l’indexation et ils ont l’air vraiment bien dans votre code xD

Une autre manière d’imiter un enum avec une gestion d’égalité cohérente (adoptée sans vergogne de Dave Thomas). Permet des énumérations ouvertes (un peu comme des symboles) et des énumérations fermées (prédéfinies).

 class Enum def self.new(values = nil) enum = Class.new do unless values def self.const_missing(name) const_set(name, new(name)) end end def initialize(name) @enum_name = name end def to_s "#{self.class}::#@enum_name" end end if values enum.instance_eval do values.each { |e| const_set(e, enum.new(e)) } end end enum end end Genre = Enum.new %w(Gothic Metal) # creates closed enum Architecture = Enum.new # creates open enum Genre::Gothic == Genre::Gothic # => true Genre::Gothic != Architecture::Gothic # => true 

Essayez l’inum. https://github.com/alfa-jpn/inum

 class Color < Inum::Base define :RED define :GREEN define :BLUE end 
 Color::RED Color.parse('blue') # => Color::BLUE Color.parse(2) # => Color::GREEN 

voir plus https://github.com/alfa-jpn/inum#usage