Enum en Ruby


¿Cuál es la mejor manera de implementar el modismo enum en Ruby? Estoy buscando algo que pueda usar (casi) como los enums Java/C#.

 278
Author: Adam Bellaire, 2008-09-16

25 answers

De dos maneras. Símbolos (notación:foo) o constantes (notaciónFOO).

Los símbolos son apropiados cuando se desea mejorar la legibilidad sin ensuciar el código con cadenas literales.

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

Las constantes son apropiadas cuando se tiene un valor subyacente que es importante. Simplemente declare un módulo para contener sus constantes y luego declare las constantes dentro de eso.

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

flags = Foo::BAR | Foo::BAZ # flags = 3
 275
Author: mlibby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-20 15:44:12

La forma 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 enums, 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.

 52
Author: emk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-16 19:06:04

Me sorprende que nadie haya ofrecido algo como lo siguiente (cosechado de la gema 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

Que se puede usar así:

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

Ejemplo:

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

Esto funciona bien en escenarios de bases de datos, o cuando se trata de constantes/enums de estilo C (como es el caso cuando se usa FFI, que RAPI hace un uso extensivo).

Además, no tiene que preocuparse por errores tipográficos que causen fallas silenciosas, como lo haría con el uso de una solución de tipo hash.

 51
Author: Charles,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-29 21:19:58

Utilizo el siguiente enfoque:

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

Me gusta por las siguientes ventajas:

  1. Agrupa valores visualmente como un todo
  2. Hace alguna comprobación del 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 distintos valores: MY_VALUE_1
  5. Puede tener valores de cualquier tipo, no solo Símbolo

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

 28
Author: Alexey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-14 09:45:06

Si está utilizando Rails 4.2 o superior, puede utilizar enumeraciones de Rails.

Rails ahora tiene enumeraciones por defecto sin necesidad de incluir gemas.

Esto es muy similar (y más con características) a Java, C++ enums.

Citado 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
 16
Author: vedant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-24 11:23:26

Este es mi enfoque de enums en Ruby. Iba por corto y dulce, no necesariamente el más C-like. ¿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
 7
Author: johnnypez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-12 15:48:35

Echa un vistazo a la gema 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
 7
Author: dB.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-28 02:03:20

Sé que ha pasado mucho tiempo desde que el tipo publicó esta pregunta, pero yo tenía la misma pregunta y este post 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, el soporte de ActiveRecord para la búsqueda utilizando la columna que representa la enumeración.

No encontré nada, así que hice una implementación impresionante 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
 7
Author: Oded Niv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-03 07:35:34

Alguien siguió adelante y escribió una gema de rubí llamada Renum. Afirma obtener el comportamiento similar a Java/ C # más cercano. Personalmente todavía estoy aprendiendo Ruby, y me sorprendió un poco cuando quise hacer que una clase específica contuviera una enumeración estática, posiblemente un hash, que no se encontraba exactamente fácilmente a través de Google.

 4
Author: dlamblin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-04 20:47:49

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

my_value = my_hash.fetch(:key)

O haciendo que el hash raise sea 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 el comportamiento de elevación 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 escribes mal una constante nombre, por lo general va a plantear una excepción.

 4
Author: Andrew Grimm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-16 23:14:48

Tal vez el mejor enfoque ligero sería

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

De esta manera los valores tienen nombres asociados, como en Java/C#:

MyConstants::ABC
=> MyConstants::ABC

Para obtener todos los valores, puede hacer

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

Si desea el valor ordinal de una enumeración, puede hacer

MyConstants.constants.index :GHI
=> 2
 4
Author: Daniel Lubarov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-19 21:50:52

Recientemente lanzamos una gema que implementa Enumeraciones en Ruby. En mi post encontrarás las respuestas a tus preguntas. También describí por qué nuestra implementación es mejor que las existentes (en realidad hay muchas implementaciones de esta característica en Ruby aún como gemas).

 3
Author: ka8725,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-28 17:53:47

Todo depende de cómo utilice Java o C# enums. Cómo lo uses dictará la solución que elegirás en Ruby.

Pruebe el tipo nativo Set, 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"}>
 2
Author: mislav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-16 20:35:24

Símbolos es la forma de ruby. Sin embargo, a veces uno necesita hablar con algún código C o algo o Java que exponga alguna enumeración para varias cosas.


#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

Esto se puede usar así


require 'server_roles'

sSymb, sEnum =EnumLike.server_role()

foreignvec[sEnum[:SERVER_WORKSTATION]]=8

Esto es, por supuesto, puede hacerse abstracto y puede rodar nuestra propia clase de enumeración

 2
Author: Jonke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-02 20:47:41

He implementado enum como ese

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 hacer operaciones

EnumType.ENUM_1.label

...

enum = EnumType.find_by_id 1

...

valueArray = EnumType.values
 2
Author: Masuschi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-28 13:38:05

Esto parece un poco superfluo, pero esta es una metodología que he utilizado un par de 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 una enumeración de c# y está ligado al modelo.

 2
Author: jjk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-05 17:51:27

Otra solución está usando OpenStruct. Es bastante sencillo y limpio.

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

Ejemplo:

# 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'
 2
Author: Roger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-14 09:17:55

La mayoría de la gente usa símbolos (esa es la sintaxis :foo_bar). Son valores opacos únicos. Los símbolos no pertenecen a ningún tipo de enumeración, por lo que no son realmente una representación fiel del tipo de enumeración de C, pero esto es casi tan bueno como se pone.

 1
Author: Jan Krüger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-16 19:04:45
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

 1
Author: Anu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-06 16:09:05
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)

Salida:

GOOD
 1
Author: Hossein,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-11 12:29:01

A veces todo lo que necesito es ser capaz de obtener el valor de enum e identificar su nombre similar a 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'

Esto para mí sirve al propósito de enum y lo mantiene muy extensible también. Puede agregar más métodos a la clase Enum y viola los obtiene gratis en todos los enums definidos. por ejemplo. get_all_names y cosas por el estilo.

 1
Author: dark_src,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-21 21:54:19

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

 0
Author: Philippe Monnet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-29 18:07:32

Creo que la mejor manera de implementar la enumeración como tipos es con símbolos ya que prácticamente se comportan como enteros (cuando se trata de performace, object_id se usa para hacer comparaciones ); no necesita preocuparse por la indexación y se ven realmente bien en su código XD

 0
Author: goreorto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-04-03 23:57:49

Otra forma de imitar una enumeración con un manejo de igualdad consistente (descaradamente adoptado de Dave Thomas). Permite enum abiertos (como símbolos) y cerrados (predefinidos).

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
 0
Author: Daniel Doubleday,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-30 01:44:43

Prueba el 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

Ver más https://github.com/alfa-jpn/inum#usage

 0
Author: horun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-11 04:47:02