Quelle est la meilleure façon d'implémenter l'idiome enum dans Ruby? Je cherche quelque chose que je peux utiliser (presque) comme les énumérations Java / C #.
Quelle est la meilleure façon d'implémenter l'idiome enum dans Ruby? Je cherche quelque chose que je peux utiliser (presque) comme les énumérations Java / C #.
Réponses:
Deux manières. Symboles ( :foo
notation) ou constantes ( FOO
notation).
Les symboles sont appropriés lorsque vous souhaitez améliorer la lisibilité sans encombrer le 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 qui est importante. Déclarez simplement un module pour contenir vos constantes, puis déclarez les constantes à l'intérieur.
module Foo
BAR = 1
BAZ = 2
BIZ = 4
end
flags = Foo::BAR | Foo::BAZ # flags = 3
:minnesota.to_s
lors de l'enregistrement dans une base de données pour enregistrer la version chaîne du symbole. Rails, je crois, a des méthodes d'aide pour gérer certains de ces problèmes.
Je suis surpris que personne n'ait offert quelque chose comme ce qui suit (récolté à partir de la gemme 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
Qui peut être utilisé comme ceci:
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 lors de l'utilisation de FFI , que RAPI utilise largement).
De plus, vous n'avez pas à vous soucier des fautes de frappe provoquant 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 les énumérations, 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
Je l'aime pour les avantages suivants:
MY_ENUM
MY_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 les énumérations Rails.
Rails a maintenant des énumérations par défaut sans avoir besoin d'inclure de gemmes.
Ceci est très similaire (et plus avec des fonctionnalités) aux énumérations Java, C ++.
Cité sur 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
classe - je crois qu'il ne doit autoriser qu'une seule instance.
C'est mon approche des énumérations en Ruby. J'allais pour 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 la gemme rubis-enum, https://github.com/dblock/ruby-enum .
class Gender
include Enum
Gender.define :MALE, "male"
Gender.define :FEMALE, "female"
end
Gender.all
Gender::MALE
La meilleure approche légère serait peut-être
module MyConstants
ABC = Class.new
DEF = Class.new
GHI = Class.new
end
De cette façon, les valeurs ont des noms associés, comme dans Java / C #:
MyConstants::ABC
=> MyConstants::ABC
Pour obtenir toutes les valeurs, vous pouvez faire
MyConstants.constants
=> [:ABC, :DEF, :GHI]
Si vous voulez une valeur ordinale d'une énumération, vous pouvez le faire
MyConstants.constants.index :GHI
=> 2
class ABC; end
Je sais que cela fait longtemps que le gars n'a pas posté cette question, mais j'avais la même question et ce post 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 d'ActiveRecord pour la recherche en utilisant la colonne représentant l'énumération.
Je n'ai rien trouvé, j'ai donc fait une implémentation géniale appelée yinum qui a permis tout ce que je cherchais. Fait des tonnes de spécifications, donc je suis sûr que c'est sûr.
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 le hachage lever 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 de levée 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 les constantes. Si vous mal orthographiez un nom constant, cela déclenchera généralement une exception.
FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}
Ceci définit les symboles clés. missing
, something
Etc., et rend également comparables par les valeurs associées.)
Quelqu'un est allé de l'avant et a écrit un joyau de rubis appelé Renum . Il prétend obtenir le comportement similaire à Java / C # le plus proche. Personnellement, j'apprends encore Ruby, et j'ai été un peu choqué quand j'ai voulu faire qu'une classe spécifique contienne une énumération statique, peut-être un hachage, qu'elle ne soit pas exactement facilement trouvée via google.
Tout dépend de la façon dont vous utilisez les énumérations Java ou C #. La façon dont vous l'utiliserez dictera la solution que vous choisirez dans Ruby.
Essayez le Set
type 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"}>
Set[:a, :b, :c]
?
Récemment, nous avons publié une gemme qui implémente Enums in Ruby . Dans mon article, vous trouverez les réponses à vos questions. J'y ai également expliqué pourquoi notre implémentation est meilleure que celles existantes (en fait, il existe de nombreuses implémentations de cette fonctionnalité dans Ruby encore comme des gemmes).
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'
Les symboles sont la voie rubis. Cependant, il faut parfois parler à du code C ou à quelque chose ou à Java qui exposent une énumération pour diverses 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 ensuite être utilisé comme ceci
require 'server_roles'
sSymb, sEnum =EnumLike.server_role()
foreignvec[sEnum[:SERVER_WORKSTATION]]=8
Ceci peut bien sûr être rendu abstrait et vous pouvez lancer notre propre classe Enum
server_Symb
) pour une raison particulière? À moins qu'il n'y ait une raison particulière, il est idiomatique que les variables soient snake_case_with_all_lower_case
et que les symboles soient :lower_case
.
server_Symb.each_with_index { |e,i| server_Enum[e] = i}
. Pas besoin de i = 0
.
J'ai implémenté des énumérations 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
puis ses opérations faciles à faire
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 avec 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 ac # enum et c'est lié au modèle.
:VAL
. Il serait préférable de commencer avec un tableau et de construire le hachage en utilisant.map.with_index
.key
ou .invert
plutôt qu'avec une :VAL
clé ( stackoverflow.com/a/10989394/2208016 )
key
ouinvert
La plupart des gens utilisent des symboles (c'est la :foo_bar
syntaxe). Ce sont en quelque sorte des valeurs opaques uniques. Les symboles n'appartiennent à aucun type de style enum, ils ne sont donc pas vraiment une représentation fidèle du type enum de C, mais c'est à peu près aussi bon que possible.
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
Production:
1 - a
2 - b
3 - c
4 - d
to_enum
vous donne un enumera teur , alors que enum
dans le C # / Java sens est une enumera tion
Parfois, tout ce dont j'ai besoin est de pouvoir récupérer la valeur d'énumération et 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 de l'énumération et le maintient très extensible aussi. Vous pouvez ajouter plus de méthodes à la classe Enum et l'alto les obtenir gratuitement dans toutes 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 des noms et des valeurs comme décrit dans le billet de blog RubyFleebie suivant . Cela vous permet de convertir facilement entre 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 l'énumération comme les types est avec des symboles car ils se comportent à peu près comme des entiers (quand il s'agit de performace, object_id est utilisé pour faire des comparaisons); vous n'avez pas à vous soucier de l'indexation et ils ont l'air vraiment bien dans votre code xD
Une autre façon d'imiter une énumération avec une gestion cohérente de l'égalité (adoptée sans vergogne par Dave Thomas). Permet des énumérations ouvertes (un peu comme les symboles) et des é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