Enums en Ruby


Answers

La manera más idiomática de hacer esto es usar símbolos. Por ejemplo, en lugar de:

enum {
  FOO,
  BAR,
  BAZ
}

myFunc(FOO);

... solo puedes 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)

Esto es un poco más abierto que las enumeraciones, pero encaja bien con el espíritu Ruby.

Los símbolos también funcionan muy bien. Comparar dos símbolos para la igualdad, por ejemplo, es mucho más rápido que comparar dos cadenas.

Question

¿Cuál es la mejor manera de implementar la expresión enum en Ruby? Estoy buscando algo que pueda usar (casi) como las enumeraciones Java / C #.




Si le preocupan los errores tipográficos con símbolos, asegúrese de que su código genere una excepción cuando acceda a un valor con una clave inexistente. Puedes hacer esto usando fetch lugar de [] :

my_value = my_hash.fetch(:key)

o haciendo que el hash levante una excepción por defecto si proporciona una clave 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

Si el hash ya existe, puede agregar un comportamiento de aumento de excepciones:

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, no tiene que preocuparse por la seguridad de los errores tipográficos con constantes. Si escribe mal un nombre constante, por lo general se generará una excepción.




A veces, todo lo que necesito es poder obtener el valor de enum e identificar su nombre similar al mundo de Java.

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'

Esto para mí cumple el propósito de enum y lo mantiene muy extensible también. Puede agregar más métodos a la clase Enum y viola obtenerlos gratis en todas las enumeraciones definidas. por ejemplo. get_all_names y cosas por el estilo




He implementado enumeraciones como esa

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

entonces es fácil de hacer operaciones

EnumType.ENUM_1.label

...

enum = EnumType.find_by_id 1

...

valueArray = EnumType.values



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

Salida:

1 - a
2 - b
3 - c
4 - d




Este es mi enfoque para enumeraciones en Ruby. Me iba para abreviar y dulce, no necesariamente el más parecido a un C. ¿Alguna idea?

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



Otra forma de imitar una enumeración con un manejo consistente de la igualdad (descaradamente adoptado de Dave Thomas). Permite enumeraciones abiertas (al igual que los símbolos) y enumeraciones cerradas (predefinidas).

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



Otro enfoque es usar una clase de Ruby con un hash que contenga nombres y valores como se describe en la siguiente publicación de blog de RubyFleebie . Esto le permite convertir fácilmente entre valores y constantes (especialmente si agrega un método de clase para buscar el nombre para un valor dado).




Sé que ha pasado mucho tiempo desde que el chico publicó esta pregunta, pero tuve la misma pregunta y esta publicación no me dio la respuesta. Quería una manera fácil de ver lo que representa el número, una comparación fácil y, sobre todo, la compatibilidad con ActiveRecord para buscar usando la columna que representa la enumeración.

No encontré nada, así que hice una implementación increíble llamada yinum que permitió todo lo que estaba buscando. Hecho toneladas de especificaciones, así que estoy bastante seguro de que es seguro.

Algunas características de ejemplo:

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



Yo uso el siguiente enfoque:

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

Me gusta por las siguientes ventajas:

  1. Agrupa los valores visualmente como un todo
  2. Hace alguna comprobación de tiempo de compilación (en contraste con solo usar símbolos)
  3. Puedo acceder fácilmente a la lista de todos los valores posibles: solo MY_ENUM
  4. Puedo acceder fácilmente a valores distintos: MY_VALUE_1
  5. Puede tener valores de cualquier tipo, no solo Symbol

Los símbolos pueden ser mejores porque no tiene que escribir el nombre de la clase externa, si lo está usando en otra clase ( MyClass::MY_VALUE_1 )




Esto parece un poco superfluo, pero esta es una metodología que he usado varias veces, especialmente cuando me estoy integrando con xml o algo así.

#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

Esto me da el rigor de ac # enum y está ligado al modelo.




Todo depende de cómo uses las enumeraciones Java o C #. Cómo lo use dictará la solución que elija en Ruby.

Pruebe el tipo de Set nativo, por ejemplo:

>> 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"}>