¿Cuál es la mejor manera de implementar el lenguaje de enumeración en Ruby? Estoy buscando algo que pueda usar (casi) como las enumeraciones Java / C #.
¿Cuál es la mejor manera de implementar el lenguaje de enumeración en Ruby? Estoy buscando algo que pueda usar (casi) como las enumeraciones Java / C #.
Respuestas:
Dos caminos. Símbolos ( :foo
notación) o constantes ( FOO
notación).
Los símbolos son apropiados cuando 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 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
:minnesota.to_s
guardar en una base de datos para guardar la versión de cadena del símbolo. Rails, creo, tiene algunos métodos auxiliares para lidiar con algo de esto.
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 / enumeraciones de estilo C (como es el caso cuando se usa FFI , que RAPI hace un uso extensivo).
Además, no tiene que preocuparse por los errores tipográficos que causan fallas silenciosas, como lo haría con el uso de una solución de tipo hash.
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 las enumeraciones, pero encaja bien con el espíritu de 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.
Yo uso el siguiente enfoque:
class MyClass
MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2']
end
Me gusta por las siguientes ventajas:
MY_ENUM
MY_VALUE_1
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
)
Si está utilizando Rails 4.2 o superior, puede usar las enumeraciones de Rails.
Rails ahora tiene enumeraciones por defecto sin la necesidad de incluir gemas.
Esto es muy similar (y más con características) a Java, enumeraciones de C ++.
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
Conversation
clase, creo que debe permitir solo 1 instancia.
Este es mi enfoque para las enumeraciones en Ruby. Iba por algo corto y dulce, no necesariamente el más parecido a 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
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
Quizás 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, puedes hacer
MyConstants.constants
=> [:ABC, :DEF, :GHI]
Si desea el valor ordinal de una enumeración, puede hacer
MyConstants.constants.index :GHI
=> 2
class ABC; end
Sé que ha pasado mucho tiempo desde que el chico publicó esta pregunta, pero tenía la misma pregunta y esta publicación no me dio la respuesta. Quería una manera fácil de ver qué representa el número, una comparación fácil y, sobre todo, el soporte de ActiveRecord para la búsqueda usando 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. Hice 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
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. Puede hacerlo utilizando en fetch
lugar de []
:
my_value = my_hash.fetch(:key)
o haciendo que el hash genere 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, generalmente generará una excepción.
FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}
esto define los símbolos de las teclas. missing
, something
, Etc., y también los hace comparables a través de los valores asociados.)
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 pudo encontrar fácilmente a través de Google.
Todo depende de cómo use las enumeraciones Java o C #. La forma en que lo use determinará la solución que elegirá en Ruby.
Pruebe el Set
tipo 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"}>
Set[:a, :b, :c]
?
Recientemente lanzamos una gema que implementa Enums en Ruby . En mi publicación encontrarás las respuestas a tus preguntas. También describí allí 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).
Otra solución es usar 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'
Símbolos es el camino de rubí. 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, por supuesto, puede hacerse abstracto y puede rodar nuestra propia clase Enum
server_Symb
) por una razón particular? A menos que haya una razón particular, es idiomático que las variables sean snake_case_with_all_lower_case
y que los símbolos sean :lower_case
.
server_Symb.each_with_index { |e,i| server_Enum[e] = i}
. No hay necesidad de i = 0
.
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
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á vinculado al modelo.
:VAL
. Sería mejor comenzar con una matriz y construir el hash usando.map.with_index
.key
o en .invert
lugar de una :VAL
clave ( stackoverflow.com/a/10989394/2208016 )
key
invert
La mayoría de las personas usan símbolos (esa es la :foo_bar
sintaxis). Son una especie de valores opacos únicos. Los símbolos no pertenecen a ningún tipo de estilo de enumeración, por lo que no son realmente una representación fiel del tipo de enumeración de C, pero esto es bastante bueno.
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
to_enum
le da una enumera tor , mientras que enum
en el C # / Java es un sentido enumera ción
A veces, todo lo que necesito es poder obtener el valor de enum e identificar su nombre de manera 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í tiene el propósito de enum y también lo mantiene muy extensible. Puede agregar más métodos a la clase Enum y viola obtenerlos de forma gratuita en todas las enumeraciones definidas. por ejemplo. get_all_names y cosas así.
Otro enfoque es usar una clase 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 de un valor dado).
Otra forma de imitar una enumeración con un manejo de igualdad consistente (adoptado descaradamente de Dave Thomas). Permite enumeraciones abiertas (muy parecidas a 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
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