Come implementare le enumerazioni in Ruby?
Domanda
Qual è il modo migliore per implementare l'idioma enum in Ruby?Sto cercando qualcosa che posso usare (quasi) come le enumerazioni Java/C#.
Soluzione
Due strade.Simboli (:foo
notazione) o costanti (FOO
notazione).
I simboli sono appropriati quando si desidera migliorare la leggibilità senza riempire il codice con stringhe letterali.
postal_code[:minnesota] = "MN"
postal_code[:new_york] = "NY"
Le costanti sono appropriate quando si ha un valore sottostante importante.Dichiara semplicemente un modulo per contenere le tue costanti e poi dichiara le costanti al suo interno.
module Foo
BAR = 1
BAZ = 2
BIZ = 4
end
flags = Foo::BAR | Foo::BAZ # flags = 3
Altri suggerimenti
Sono sorpreso che nessuno abbia offerto qualcosa di simile al seguente (raccolto dal file RAPI gemma):
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
Che può essere usato in questo modo:
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
Esempio:
>> 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
Questo funziona bene negli scenari di database o quando si ha a che fare con costanti/enumerazioni in stile C (come nel caso quando si utilizza FFI, di cui la RAPI fa ampio uso).
Inoltre, non devi preoccuparti che errori di battitura causino errori silenziosi, come faresti con l'utilizzo di una soluzione di tipo hash.
Il modo più idiomatico per farlo è usare i simboli.Ad esempio, invece di:
enum {
FOO,
BAR,
BAZ
}
myFunc(FOO);
...puoi semplicemente usare i simboli:
# 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)
Questo è un po' più aperto delle enumerazioni, ma si adatta bene allo spirito di Ruby.
Anche i simboli funzionano molto bene.Confrontare due simboli per l'uguaglianza, ad esempio, è molto più veloce che confrontare due stringhe.
Utilizzo il seguente approccio:
class MyClass
MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2']
end
Mi piace per i seguenti vantaggi:
- Raggruppa visivamente i valori come un tutto unico
- Esegue alcuni controlli in fase di compilazione (in contrasto con l'utilizzo dei soli simboli)
- Posso accedere facilmente all'elenco di tutti i valori possibili:Appena
MY_ENUM
- Posso facilmente accedere a valori distinti:
MY_VALUE_1
- Può avere valori di qualsiasi tipo, non solo Symbol
I simboli potrebbero essere migliori perché non è necessario scrivere il nome della classe esterna, se la si utilizza in un'altra classe (MyClass::MY_VALUE_1
)
Se stai utilizzando Rails 4.2 o versione successiva puoi utilizzare le enumerazioni Rails.
Rails ora ha enumerazioni per impostazione predefinita senza la necessità di includere gemme.
Questo è molto simile (e più con funzionalità) alle enumerazioni Java e C++.
Citato da 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
Questo è il mio approccio alle enumerazioni in Ruby.Volevo essere breve e dolce, non necessariamente il più in stile C.qualche 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
Dai un'occhiata alla gemma rubino-enum, https://github.com/dblock/ruby-enum.
class Gender
include Enum
Gender.define :MALE, "male"
Gender.define :FEMALE, "female"
end
Gender.all
Gender::MALE
So che è passato molto tempo dall'ultima volta che il ragazzo ha postato questa domanda, ma avevo la stessa domanda e questo post non mi ha dato la risposta.Volevo un modo semplice per vedere cosa rappresenta il numero, un confronto facile e soprattutto il supporto ActiveRecord per la ricerca utilizzando la colonna che rappresenta l'enum.
Non ho trovato nulla, quindi ho creato un'implementazione fantastica chiamata yinum che ha permesso tutto quello che cercavo.Ha fatto un sacco di specifiche, quindi sono abbastanza sicuro che sia sicuro.
Alcune caratteristiche di esempio:
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
Se sei preoccupato per gli errori di battitura con i simboli, assicurati che il tuo codice sollevi un'eccezione quando accedi a un valore con una chiave inesistente.Puoi farlo usando fetch
piuttosto che []
:
my_value = my_hash.fetch(:key)
o facendo in modo che l'hash sollevi un'eccezione per impostazione predefinita se fornisci una chiave inesistente:
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
Se l'hash esiste già, puoi aggiungere un comportamento di raccolta delle eccezioni:
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 non devi preoccuparti della sicurezza degli errori di battitura con le costanti.Se scrivi in modo errato un nome costante, di solito verrà sollevata un'eccezione.
Forse il miglior approccio leggero sarebbe
module MyConstants
ABC = Class.new
DEF = Class.new
GHI = Class.new
end
In questo modo i valori hanno nomi associati, come in Java/C#:
MyConstants::ABC
=> MyConstants::ABC
Per ottenere tutti i valori, puoi farlo
MyConstants.constants
=> [:ABC, :DEF, :GHI]
Se vuoi il valore ordinale di un'enumerazione, puoi farlo
MyConstants.constants.index :GHI
=> 2
Qualcuno è andato avanti e ha scritto una gemma rubino chiamata Renum.Afferma di ottenere il comportamento più simile a Java/C#.Personalmente sto ancora imparando Ruby, e sono rimasto un po' scioccato quando ho voluto fare in modo che una classe specifica contenesse un enum statico, possibilmente un hash, che non era facilmente reperibile tramite Google.
Tutto dipende da come usi le enumerazioni Java o C#.Il modo in cui lo utilizzerai determinerà la soluzione che sceglierai in Ruby.
Prova il nativo Set
digitare, ad esempio:
>> 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"}>
Recentemente abbiamo rilasciato a gemma che implementa Enumerazioni in Ruby.Nel mio inviare troverai le risposte alle tue domande.Inoltre ho descritto perché la nostra implementazione è migliore di quelle esistenti (in realtà ci sono molte implementazioni di questa funzionalità in Ruby ancora come gemme).
I simboli sono la via del rubino.Tuttavia, a volte è necessario parlare con del codice C o qualcosa del genere o Java che esponga alcune enumerazioni per varie cose.
#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
Questo può quindi essere utilizzato in questo modo
require 'server_roles'
sSymb, sEnum =EnumLike.server_role()
foreignvec[sEnum[:SERVER_WORKSTATION]]=8
Questo ovviamente può essere reso astratto e puoi lanciare la nostra classe Enum
Ho implementato enumerazioni del genere
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
allora è facile eseguire le operazioni
EnumType.ENUM_1.label
...
enum = EnumType.find_by_id 1
...
valueArray = EnumType.values
Sembra un po' superfluo, ma questa è una metodologia che ho usato alcune volte, soprattutto dove mi sto integrando con xml o qualcosa del genere.
#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
Questo mi dà il rigore di un enum c# ed è legato al modello.
Un'altra soluzione sta utilizzando OpenStruct.È piuttosto semplice e pulito.
https://ruby-doc.org/stdlib-2.3.1/libdoc/ostruct/rdoc/OpenStruct.html
Esempio:
# 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 maggior parte delle persone usa i simboli (questo è il :foo_bar
sintassi).Sono una sorta di valori opachi unici.I simboli non appartengono a nessun tipo di stile enum, quindi non sono una rappresentazione fedele del tipo enum di C, ma questo è più o meno il massimo.
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
Produzione:
1 - a
2 - b
3 - ca
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)
Produzione:
GOOD
A volte tutto ciò di cui ho bisogno è poter recuperare il valore di enum e identificare il suo nome in modo simile al mondo 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'
Questo per me serve allo scopo di enum e lo mantiene anche molto estensibile.Puoi aggiungere più metodi alla classe Enum e ottenerli gratuitamente in tutte le enumerazioni definite.Per esempio.get_all_names e cose del genere.
Un altro approccio consiste nell'utilizzare una classe Ruby con un hash contenente nomi e valori come descritto di seguito Post del blog RubyFleebie.Ciò ti consente di convertire facilmente tra valori e costanti (specialmente se aggiungi un metodo di classe per cercare il nome di un determinato valore).
Penso che il modo migliore per implementare tipi simili a enumerazioni sia con i simboli poiché praticamente si comportano come numeri interi (quando si tratta di prestazioni, object_id viene utilizzato per effettuare confronti);non devi preoccuparti dell'indicizzazione e sembrano davvero ordinati nel tuo codice xD
Un altro modo per imitare un'enumerazione con una gestione coerente dell'uguaglianza (adottato spudoratamente da Dave Thomas).Consente enumerazioni aperte (molto simili ai simboli) ed enumerazioni chiuse (predefinite).
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
Prova 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
Vedi altro https://github.com/alfa-jpn/inum#usage
Veloce e sporco, sembra 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
Usalo come useresti un Enum:
method_that_needs_options(FeelsLikeAnEnum.Option_1)