Qual è il modo migliore per implementare l'enum idiom in Ruby? Sto cercando qualcosa che possa usare (quasi) come le enumerazioni Java/C #.
Due strade. Simboli (notazione :foo
) o costanti (notazione FOO
).
I simboli sono appropriati quando si desidera migliorare la leggibilità senza litterare il codice con stringhe letterali.
postal_code[:minnesota] = "MN"
postal_code[:new_york] = "NY"
Le costanti sono appropriate quando hai un valore sottostante che è importante. Basta dichiarare un modulo per contenere le costanti e quindi dichiarare le costanti al suo interno.
module Foo
BAR = 1
BAZ = 2
BIZ = 4
end
flags = Foo::BAR | Foo::BAZ # flags = 3
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 dell'enum, ma si adatta bene allo spirito Ruby.
Anche i simboli si comportano molto bene. Ad esempio, il confronto di due simboli per l'uguaglianza è molto più rapido rispetto al confronto di due stringhe.
Sono sorpreso che nessuno abbia offerto qualcosa del genere (raccolto dalla 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
Quale può essere usato così:
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 in scenari di database, o quando si tratta di costanti/enumerazioni in stile C (come nel caso in cui si usi FFI , che RAPI fa largo uso di).
Inoltre, non devi preoccuparti di errori di battitura che causano errori silenziosi, come faresti con l'uso di una soluzione di tipo hash.
Io uso il seguente approccio:
class MyClass
MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2']
end
Mi piace per i seguenti vantaggi:
MY_ENUM
MY_VALUE_1
I simboli possono essere migliori perché non devi scrivere il nome della classe esterna, se la stai usando in un'altra classe (MyClass::MY_VALUE_1
)
Se utilizzi Rails 4.2 o versioni successive, puoi utilizzare le enumerazioni di Rails.
Rails ora ha enumerazioni di default senza la necessità di includere gemme.
Questo è molto simile (e più con le caratteristiche) a enigmi Java, 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. Stavo andando per corto e dolce, non necessariamente il più C-like. 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
So che è passato molto tempo da quando il ragazzo ha postato questa domanda, ma ho avuto la stessa domanda e questo post non mi ha dato la risposta. Volevo un modo semplice per vedere cosa rappresenta il numero, facile confronto e soprattutto supporto ActiveRecord per la ricerca usando la colonna che rappresenta l'enum.
Non ho trovato nulla, quindi ho realizzato un'impressionante implementazione chiamata yinum che permetteva tutto ciò che stavo cercando. Fatto tonnellate 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
Controlla la gemma di 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
Se sei preoccupato per 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 generi un'eccezione per impostazione predefinita se si fornisce 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 eccezione:
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 un nome errato, di solito genera un'eccezione.
Qualcuno è andato avanti e ha scritto una gemma Ruby chiamata Renum . Sostiene di ottenere il comportamento più simile a Java/C #. Personalmente sto ancora imparando Ruby, e sono rimasto un po 'scioccato quando volevo fare in modo che una classe specifica contenesse un enum statico, forse un hash, che non fosse facilmente reperibile tramite google.
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 enum, puoi farlo
MyConstants.constants.index :GHI
=> 2
Questo sembra un po 'superfluo, ma questa è una metodologia che ho usato un paio di volte, specialmente dove 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 c # enum ed è legato al modello.
I simboli sono il modo rubino. Tuttavia, a volte è necessario parlare con un codice C o qualcosa o Java che espone qualche enum 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 usato in questo modo
require 'server_roles'
sSymb, sEnum =EnumLike.server_role()
foreignvec[sEnum[:SERVER_WORKSTATION]]=8
Ovviamente questo 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
quindi è facile da fare operazioni
EnumType.ENUM_1.label
...
enum = EnumType.find_by_id 1
...
valueArray = EnumType.values
Dipende tutto da come usi le enumerazioni Java o C #. Il modo in cui lo utilizzerai determinerà la soluzione che sceglierai in Ruby.
Prova il tipo Set
nativo, 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"}>
Un'altra soluzione sta usando 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 (questa è la sintassi :foo_bar
). Sono una specie di valori opachi unici. I simboli non appartengono a nessun tipo di enum in modo che non siano realmente una rappresentazione fedele del tipo enum di C, ma questo è praticamente il meglio che ottiene.
A volte tutto ciò di cui ho bisogno è di essere in grado di recuperare il valore di enum e identificare il suo nome simile al mondo di 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 viola riceverli gratuitamente in tutte le enumerazioni definite. per esempio. get_all_names e cose del genere.
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 - 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)
Produzione:
GOOD
Veloce e sporco, si sente come 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 se usassi un Enum:
method_that_needs_options(FeelsLikeAnEnum.Option_1)
Penso che il modo migliore per implementare i tipi di enumerazione sia con i simboli dato che praticamente si comportano come numeri interi (quando si tratta di performace, object_id è usato per fare confronti); non è necessario preoccuparsi dell'indicizzazione e appaiono davvero accurati nel codice xD
Un altro approccio consiste nell'usare una classe Ruby con un hash contenente nomi e valori come descritto nel seguente RubyFleebie post del blog . Questo ti permette di convertire facilmente tra valori e costanti (specialmente se aggiungi un metodo di classe per cercare il nome di un dato valore).
Un altro modo di imitare un enum con una gestione coerente delle uguaglianze (adottato spudoratamente da Dave Thomas). Consente l'enumerazione aperta (molto simile ai simboli) e le 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
vedere di più https://github.com/alfa-jpn/inum#usage