Quel est le meilleur moyen d'implémenter l'idiome enum dans Ruby? Je cherche quelque chose que je peux utiliser (presque) comme les énumérations Java/C #.
Deux manières. Symboles (notation :foo
) ou constantes (notation FOO
).
Les symboles sont appropriés lorsque vous souhaitez améliorer la lisibilité sans joncher 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 contenant vos constantes, puis déclarez les constantes qui s'y trouvent.
module Foo
BAR = 1
BAZ = 2
BIZ = 4
end
flags = Foo::BAR | Foo::BAZ # flags = 3
La manière 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 cadre bien avec l'esprit Ruby.
Les symboles fonctionnent également très bien. Par exemple, comparer deux symboles en termes d'égalité est beaucoup plus rapide que comparer deux chaînes.
Je suis surpris que personne n'ait offert quelque chose comme ce qui suit (récolté du RAPI gem):
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 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 lorsqu'il s'agit de constantes/énumérations de style C (comme c'est le cas avec FFI , que RAPI utilise fréquemment).
De plus, vous n'avez pas à vous soucier des fautes de frappe qui entraînent 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:
MY_ENUM
seulementMY_VALUE_1
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 supérieur, vous pouvez utiliser des énumérations Rails.
Rails a maintenant des énumérations par défaut sans qu'il soit nécessaire d'inclure des gemmes.
Ceci est très similaire (et plus avec des fonctionnalités) à Java, enums 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
Ceci 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
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 le nombre représente, 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é, alors j'ai réalisé une implémentation géniale appelée yinum qui permettait tout ce que je cherchais. Fait des tonnes de spécifications, donc 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
Découvrez la gemme 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
Si vous vous inquiétez des fautes de frappe avec des symboles, assurez-vous que votre code génère 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.
Quelqu'un est allé de l'avant et a écrit un bijou Ruby appelé 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.
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
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 autre.
#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 il est lié au modèle.
Les symboles sont à la manière de Ruby. 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
Cela peut alors être utilisé comme ça
require 'server_roles'
sSymb, sEnum =EnumLike.server_role()
foreignvec[sEnum[:SERVER_WORKSTATION]]=8
Ceci peut évidemment ê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
Tout dépend de la manière dont vous utilisez les énumérations Java ou C #. Son utilisation dictera la solution que vous choisirez dans Ruby.
Essayez le type Set
natif, 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"}>
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 à peu près aussi bons qu'ils le peuvent.
Parfois, tout ce dont j'ai besoin est de pouvoir récupérer la valeur d’énum et d’identifier son nom de manière similaire au monde 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'
Pour moi, cela sert l'objectif 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.
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 - a
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
Rapide et sale, se sent comme 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)
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 (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 chouette dans votre code xD
Une autre approche consiste à utiliser une classe Ruby avec un hachage contenant des noms et des valeurs, comme décrit dans l'article suivant sur le 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).
Une autre façon d'imiter une énumération avec un traitement cohérent de l'égalité (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