Question

Quel est le meilleur moyen d'implémenter l'idiome enum dans Ruby? Je recherche quelque chose que je peux utiliser (ou presque) comme les énumérations Java / C #.

Était-ce utile?

La solution

Deux manières. Symboles (notation : toto ) ou constantes (notation FOO ).

Les symboles sont appropriés lorsque vous souhaitez améliorer la lisibilité sans jongler entre du code et 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 contenant vos constantes, puis déclarez les constantes qu'il contient.

module Foo
  BAR = 1
  BAZ = 2
  BIZ = 4
end

flags = Foo::BAR | Foo::BAZ # flags = 3

Autres conseils

Je suis surpris que personne n'ait proposé quelque chose comme ce qui suit (récolté à partir du RAPI bijou):

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

Ce qui peut être utilisé comme suit:

class FileAttributes < 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 = FileAttributes.new(3)
=> #<FileAttributes:0x629d90 @attrs=3>
>> example.readonly?
=> true
>> example.hidden?
=> true
>> example.system?
=> false
>> example.system = true
=> true
>> example.system?
=> true
>> example.to_i
=> 7

Cela fonctionne bien dans les scénarios de base de données ou dans les constantes / énumérations de style C (comme c'est le cas lorsque vous utilisez FFI , que RAPI utilise fréquemment).

De plus, vous n’avez pas à vous soucier des fautes de frappe causant des échecs silencieux, comme vous le feriez avec une solution de type hachage.

La façon la plus idiomatique de le faire est d’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 des enums, mais cela 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.

J'utilise l'approche suivante:

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

J'aime les avantages suivants:

  1. Il regroupe visuellement les valeurs dans un ensemble
  2. Il effectue quelques vérifications au moment de la compilation (contrairement à l'utilisation de symboles)
  3. Je peux facilement accéder à la liste de toutes les valeurs possibles: il suffit de MY_ENUM
  4. Je peux facilement accéder à des valeurs distinctes: MY_VALUE_1
  5. Il peut avoir des valeurs de n'importe quel type, pas seulement Symbol

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 version 4.2 ou supérieure, vous pouvez utiliser les énumérations Rails.

Rails a maintenant une énumération par défaut sans qu'il soit nécessaire d'inclure des gemmes.

C’est très similaire (avec d’autres fonctionnalités) à Java, enums C ++.

Cité tiré 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 enums en Ruby. J'allais faire court et 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 joyau de 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 cela fait longtemps que le gars n'a pas posté cette question, mais j'avais la même question et ce message 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 par ActiveRecord de la recherche à l'aide de la colonne représentant l'énum.

Je n'ai rien trouvé. J'ai donc implémenté une superbe implémentation appelée yinum qui autorisait tout ce que je cherchais. Une tonne de spécifications, alors je suis sûr que c'est sans danger.

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

Si vous vous inquiétez des fautes de frappe avec des symboles, assurez-vous que votre code lève 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 que le hachage lève une exception par défaut si vous fournissez une clé inexistante:

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

Si le hachage existe déjà, vous pouvez ajouter un comportement générateur d'exception:

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

Normalement, vous n'avez pas à vous soucier de la sécurité des fautes de frappe avec des constantes. Si vous orthographiez mal un nom de constante, cela déclenche généralement une exception.

Peut-être que la meilleure approche légère serait

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 énumération, vous pouvez faire

MyConstants.constants.index :GHI
=> 2

Quelqu'un est allé de l'avant et a écrit une gemme ruby ??appelée Renum . Il prétend obtenir le comportement Java / C # le plus proche. Personnellement, j'apprends encore Ruby et j'ai été un peu choqué de vouloir faire en sorte qu'une classe spécifique contienne une énumération statique, peut-être un hash, que ce n'était pas très facile à trouver via Google.

Tout dépend de la façon dont vous utilisez les énumérations Java ou C #. Son utilisation dictera la solution que vous choisirez en Ruby.

Essayez le type natif Set , par exemple:

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

Nous avons récemment publié un bijou qui implémente Enums in Ruby . Dans mon message , vous trouverez les réponses à vos questions. J'ai également expliqué pourquoi notre implémentation est meilleure que celles existantes (en fait, Ruby dispose de nombreuses implémentations en tant que gemmes).

Les symboles sont à la manière du rubis. Cependant, il est parfois nécessaire de parler à du code C, à quelque chose ou à Java qui expose une enum pour différentes choses.

#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

Ceci peut alors être utilisé comme ceci

require 'server_roles'

sSymb, sEnum =EnumLike.server_role()

foreignvec[sEnum[:SERVER_WORKSTATION]]=8

Ceci peut bien entendu être résumé et vous pouvez lancer notre propre classe Enum

J'ai implémenté des enums comme ça

module EnumType

  def self.find_by_id id
    if id.instance_of? String
      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 c’est une méthodologie que j’ai utilisée à quelques reprises, en particulier lorsque 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 d’un c # enum et cela est lié au modèle.

Une autre solution utilise OpenStruct. C'est assez simple et propre.

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 ). Ce sont en quelque sorte des valeurs opaques uniques. Les symboles n'appartenant à aucun type de style enum, ils ne représentent donc pas vraiment le type enum de C, mais ils sont à la hauteur de la réalité.

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, c'est de pouvoir récupérer la valeur d'énum et d'identifier son nom de manière similaire à 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'

Pour moi, cela sert le but d’enum et le maintient très extensible aussi. Vous pouvez ajouter plus de méthodes à la classe Enum et alto les obtenir gratuitement dans tous 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 décrits dans ce qui suit Article de blog RubyFleebie . Cela vous permet de convertir facilement des valeurs et des constantes (surtout si vous ajoutez une méthode de classe pour rechercher le nom d'une valeur donnée).

Je pense que la meilleure façon d'implémenter une énumération comme les types est d'utiliser des symboles, car ils se comportent plutôt comme un entier (en ce qui concerne 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 chouette dans votre code xD

Une autre façon d'imiter une énumération avec une gestion d'égalité cohérente (adoptée sans vergogne de Dave Thomas). Autorise les énumérations ouvertes (un peu comme les symboles) et les é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

Rapide et sale, on se croirait en C #:

class FeelsLikeAnEnum
  def self.Option_1() :option_1 end
  def self.Option_2() :option_2 end
  def self.Option_3() :option_3 end
end

Utilisez-le comme vous utiliseriez un Enum:

method_that_needs_options(FeelsLikeAnEnum.Option_1)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top