Qual è il modo migliore per attuare il linguaggio enumatico in Ruby? Sto cercando qualcosa che posso usare (quasi) come gli enumerati Java / C #.
Qual è il modo migliore per attuare il linguaggio enumatico in Ruby? Sto cercando qualcosa che posso usare (quasi) come gli enumerati Java / C #.
Risposte:
Due strade. Simboli ( :foo
notazione) o costanti ( FOO
notazione).
I simboli sono appropriati quando si desidera migliorare la leggibilità senza sporcare 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. Dichiarare semplicemente 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
:minnesota.to_s
durante il salvataggio in un database per salvare la versione stringa del simbolo. Rails, credo, ha alcuni metodi di supporto per affrontare alcuni di questi.
Sono sorpreso che nessuno abbia offerto qualcosa di simile al seguente (raccolto dalla gemma 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
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 ha a che fare con costanti / enumerazioni in stile C (come nel caso dell'utilizzo di FFI , di cui RAPI fa ampio uso).
Inoltre, non devi preoccuparti di errori di battitura che causano errori silenziosi, come faresti con l'uso di una soluzione di tipo hash.
Il modo più idiomatico per farlo è usare i simboli. Ad esempio, anziché:
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 rispetto agli enum, ma si adatta bene allo spirito di Ruby.
Anche i simboli funzionano molto bene. Il confronto tra due simboli per l'uguaglianza, ad esempio, è molto più veloce del confronto tra due stringhe.
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 potrebbero essere migliori perché non devi scrivere il nome della classe esterna, se lo stai usando in un'altra classe ( MyClass::MY_VALUE_1
)
Se si utilizza Rails 4.2 o versione successiva, è possibile utilizzare gli enum Rails.
Rails ora ha enumerazioni di default senza la necessità di includere gemme.
Questo è molto simile (e più con funzionalità) a enumerazioni 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
Conversation
classe - credo che debba consentire solo 1 istanza.
Questo è il mio approccio alle enumerazioni in Ruby. Stavo andando in breve 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
Dai un'occhiata alla gemma di 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
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
class ABC; end
So che è passato molto tempo da quando il ragazzo ha pubblicato 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, un facile confronto e soprattutto il 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 ha permesso tutto ciò che stavo cercando. Ho 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
Se sei preoccupato per errori di battitura con simboli, assicurati che il tuo codice generi un'eccezione quando accedi a un valore con una chiave inesistente. Puoi farlo usando fetch
invece di []
:
my_value = my_hash.fetch(:key)
o facendo in modo che l'hash sollevi 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 il comportamento di aumento 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 dell'errore di battitura con le costanti. Se si scrive erroneamente un nome costante, si solleverà un'eccezione.
FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}
Questo definisce i simboli chiave. missing
, something
Ecc, e li rende anche confrontabili con i valori associati.)
Qualcuno è andato avanti e ha scritto una gemma rubino chiamata Renum . Afferma di ottenere il comportamento simile a Java / C # più vicino. Personalmente sto ancora imparando Ruby, ed ero un po 'scioccato quando volevo che una classe specifica contenesse un enum statico, possibilmente un hash, che non era facilmente reperibile tramite google.
Dipende tutto da come usi enumerazioni Java o C #. Il modo in cui lo utilizzerai determinerà la soluzione che sceglierai in Ruby.
Prova il Set
tipo 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"}>
Set[:a, :b, :c]
?
Recentemente abbiamo rilasciato una gemma che implementa Enums in Ruby . Nel mio post troverai le risposte alle tue domande. Ho anche descritto lì perché la nostra implementazione è migliore di quelle esistenti (in realtà ci sono molte implementazioni di questa funzionalità in Ruby ma come gemme).
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'
Simboli è 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
Questo può ovviamente essere reso astratto e puoi tirare la nostra classe Enum
server_Symb
) Per un motivo particolare? A meno che non ci sia una ragione particolare, è idiomatico che le variabili siano snake_case_with_all_lower_case
e che i simboli siano :lower_case
.
server_Symb.each_with_index { |e,i| server_Enum[e] = i}
. Non c'è bisogno di i = 0
.
Ho implementato enum così
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 fare operazioni
EnumType.ENUM_1.label
...
enum = EnumType.find_by_id 1
...
valueArray = EnumType.values
Questo sembra un po 'superfluo, ma questa è una metodologia che ho usato alcune volte, specialmente 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 ac # enum ed è legato al modello.
:VAL
. Sarebbe meglio iniziare con un array e costruire l'hash usando.map.with_index
.key
o .invert
piuttosto che una :VAL
chiave ( stackoverflow.com/a/10989394/2208016 )
key
oinvert
Molte persone usano simboli (questa è la :foo_bar
sintassi). Sono una sorta di valori opachi unici. I simboli non appartengono a nessun tipo di stile enumico, quindi non sono in realtà una rappresentazione fedele del tipo enumatico di C, ma questo è praticamente buono come sembra.
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
to_enum
ti dà un enumera tor , mentre enum
in senso C # / Java è un enumera zione
A volte tutto ciò di cui ho bisogno è essere in grado di recuperare il valore di enum e identificare il suo nome 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 ha lo scopo di enum e lo rende anche molto estensibile. È possibile aggiungere più metodi alla classe Enum e Viola li ottiene gratuitamente in tutti gli enum definiti. 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 nel seguente post sul blog di RubyFleebie . Ciò consente di convertire facilmente tra valori e costanti (soprattutto se si aggiunge un metodo di classe per cercare il nome per un determinato valore).
Penso che il modo migliore per implementare l'enumerazione come i tipi sia con i simboli poiché praticamente si comportano come numeri interi (quando si tratta di performace, object_id viene usato per fare confronti); non devi preoccuparti dell'indicizzazione e sembrano davvero ordinati nel tuo codice xD
Un altro modo per imitare un enum con una gestione coerente dell'uguaglianza (adottato spudoratamente da Dave Thomas). Consente enumerazioni aperte (molto simili ai simboli) e 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 di più https://github.com/alfa-jpn/inum#usage