Jaki jest najlepszy sposób na implementację idiomu enum w Ruby? Szukam czegoś, czego mogę (prawie) użyć, na przykład wyliczeń Java / C #.
Jaki jest najlepszy sposób na implementację idiomu enum w Ruby? Szukam czegoś, czego mogę (prawie) użyć, na przykład wyliczeń Java / C #.
Odpowiedzi:
Dwie drogi. Symbole ( :foo
notacja) lub stałe ( FOO
notacja).
Symbole są odpowiednie, gdy chcesz zwiększyć czytelność bez zaśmiecania kodu literałami.
postal_code[:minnesota] = "MN"
postal_code[:new_york] = "NY"
Stałe są odpowiednie, gdy masz podstawową wartość, która jest ważna. Po prostu zadeklaruj moduł do przechowywania stałych, a następnie zadeklaruj stałe w tym zakresie.
module Foo
BAR = 1
BAZ = 2
BIZ = 4
end
flags = Foo::BAR | Foo::BAZ # flags = 3
:minnesota.to_s
podczas zapisywania w bazie danych, aby zapisać ciąg znaków w wersji symbolu. Wierzę, że Rails ma pewne metody pomocnicze, aby sobie z tym poradzić.
Dziwi mnie, że nikt nie zaoferował czegoś takiego (zebranego z klejnotu 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
Które można wykorzystać tak:
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
Przykład:
>> 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
Działa to dobrze w scenariuszach baz danych lub w przypadku stałych / wyliczeń w stylu C (jak w przypadku korzystania z FFI , z którego RAPI korzysta w szerokim zakresie).
Ponadto nie musisz się martwić, że literówki powodują cichą awarię, tak jak w przypadku rozwiązania typu skrótu.
Najbardziej idiomatycznym sposobem na to jest użycie symboli. Na przykład zamiast:
enum {
FOO,
BAR,
BAZ
}
myFunc(FOO);
... możesz po prostu użyć symboli:
# 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)
Jest to nieco bardziej otwarte niż wyliczenia, ale dobrze pasuje do ducha Ruby.
Symbole również działają bardzo dobrze. Na przykład porównywanie dwóch symboli dla równości jest znacznie szybsze niż porównywanie dwóch ciągów.
Stosuję następujące podejście:
class MyClass
MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2']
end
Podoba mi się z następujących zalet:
MY_ENUM
MY_VALUE_1
Symbole mogą być lepsze, ponieważ nie musisz wpisywać nazwy klasy zewnętrznej, jeśli używasz jej w innej klasie ( MyClass::MY_VALUE_1
)
Jeśli używasz Railsów 4.2 lub nowszych, możesz użyć wyliczeń Railsów.
Railsy mają teraz domyślnie wyliczenia bez potrzeby dołączania jakichkolwiek klejnotów.
Jest to bardzo podobne (i więcej z funkcjami) do wyliczeń Java, C ++.
Cytat z 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
klasy - uważam, że musi ona dopuszczać tylko 1 instancję.
To jest moje podejście do wyliczeń w Ruby. Chciałem być krótki i słodki, niekoniecznie najbardziej podobny do C. jakieś pomysły?
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
Sprawdź klejnot rubinowy: https://github.com/dblock/ruby-enum .
class Gender
include Enum
Gender.define :MALE, "male"
Gender.define :FEMALE, "female"
end
Gender.all
Gender::MALE
Być może najlepsze byłoby podejście lekkie
module MyConstants
ABC = Class.new
DEF = Class.new
GHI = Class.new
end
W ten sposób wartości mają powiązane nazwy, jak w Javie / C #:
MyConstants::ABC
=> MyConstants::ABC
Aby uzyskać wszystkie wartości, możesz to zrobić
MyConstants.constants
=> [:ABC, :DEF, :GHI]
Jeśli chcesz wartości porządkowej wyliczenia, możesz to zrobić
MyConstants.constants.index :GHI
=> 2
class ABC; end
Wiem, że minęło dużo czasu, odkąd facet opublikował to pytanie, ale miałem to samo pytanie i ten post nie dał mi odpowiedzi. Chciałem w łatwy sposób zobaczyć, co reprezentuje liczba, łatwe porównanie, a przede wszystkim obsługę ActiveRecord dla wyszukiwania za pomocą kolumny reprezentującej wyliczenie.
Nic nie znalazłem, więc zrobiłem niesamowitą implementację o nazwie yinum która pozwoliła na wszystko, czego szukałem. Stworzyłem mnóstwo specyfikacji, więc jestem pewien, że to bezpieczne.
Niektóre przykładowe funkcje:
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
Jeśli martwisz się literówkami z symbolami, upewnij się, że Twój kod zgłasza wyjątek podczas uzyskiwania dostępu do wartości za pomocą nieistniejącego klucza. Możesz to zrobić, używając fetch
zamiast []
:
my_value = my_hash.fetch(:key)
lub wprowadzając skrót domyślnie wyjątek, jeśli podasz nieistniejący klucz:
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
Jeśli skrót już istnieje, możesz dodać zachowanie powodujące wyjątki:
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
Zwykle nie musisz się martwić o bezpieczeństwo literówek ze stałymi. Jeśli źle przeliterujesz stałą nazwę, zwykle spowoduje to wyjątek.
FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}
Definiuje główne symbole. missing
, something
Itp, a także sprawia, że są porównywalne poprzez odpowiednie wartości.)
Ktoś poszedł naprzód i napisał rubinowy klejnot o nazwie Renum . Twierdzi, że ma najbliższe zachowanie podobne do Java / C #. Osobiście wciąż uczę się Ruby i byłem trochę zszokowany, gdy chciałem, aby konkretna klasa zawierała statyczny wyliczenie, być może skrót, że nie było łatwo znaleźć google.
Wszystko zależy od tego, w jaki sposób używasz wyliczeń Java lub C #. Sposób korzystania z niego podyktuje rozwiązanie, które wybierzesz w Ruby.
Wypróbuj Set
typ macierzysty , na przykład:
>> 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]
?
Niedawno wydaliśmy klejnot, który implementuje Enums w Ruby . W moim poście znajdziesz odpowiedzi na swoje pytania. Opisałem również, dlaczego nasza implementacja jest lepsza niż istniejące (w rzeczywistości istnieje wiele implementacji tej funkcji w Ruby jako klejnoty).
Innym rozwiązaniem jest użycie OpenStruct. Jest całkiem prosty i czysty.
https://ruby-doc.org/stdlib-2.3.1/libdoc/ostruct/rdoc/OpenStruct.html
Przykład:
# 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'
Symbole to rubinowa droga. Czasami jednak trzeba porozmawiać z jakimś kodem C lub czymś lub Javą, która ujawnia pewną enum dla różnych rzeczy.
#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
Można to następnie wykorzystać w ten sposób
require 'server_roles'
sSymb, sEnum =EnumLike.server_role()
foreignvec[sEnum[:SERVER_WORKSTATION]]=8
Można to oczywiście uczynić abstrakcyjnym i możesz rzucić naszą własną klasą Enum
server_Symb
z jakiegoś powodu używasz drugiego słowa w zmiennych (np. )? O ile nie ma konkretnego powodu, idiomatyczne jest, aby zmienne były snake_case_with_all_lower_case
i symbole :lower_case
.
server_Symb.each_with_index { |e,i| server_Enum[e] = i}
. Nie ma potrzeby i = 0
.
Zaimplementowałem takie wyliczenia
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
to jest łatwe do zrobienia
EnumType.ENUM_1.label
...
enum = EnumType.find_by_id 1
...
valueArray = EnumType.values
To wydaje się trochę zbyteczne, ale jest to metodologia, której użyłem kilka razy, szczególnie tam, gdzie integruję się z XML-em lub podobnymi.
#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
Daje mi to rygor ac acumum i jest on związany z modelem.
:VAL
. Lepiej byłoby zacząć od tablicy i zbudować skrót za pomocą.map.with_index
.key
lub .invert
zamiast niego :VAL
( stackoverflow.com/a/10989394/2208016 )
key
lubinvert
Większość ludzi używa symboli (taka jest :foo_bar
składnia). To rodzaj unikalnych nieprzejrzystych wartości. Symbole nie należą do żadnego typu typu wyliczeniowego, więc nie są tak naprawdę wierną reprezentacją typu wyliczeniowego C, ale jest to tak dobre, jak to możliwe.
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
Wynik:
1 - a
2 - b
3 - c
4 - d
to_enum
daje enumera Tora , natomiast enum
w C # / Java jest sens enumera cja
Czasami wszystko, czego potrzebuję, to móc pobrać wartość wyliczenia i zidentyfikować jego nazwę podobną do świata 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'
To dla mnie służy celowi wyliczania i sprawia, że jest on również bardzo rozszerzalny. Możesz dodać więcej metod do klasy Enum i viola uzyskać je za darmo we wszystkich zdefiniowanych wyliczeniach. na przykład. get_all_names i takie tam.
Innym podejściem jest użycie klasy Ruby z hashem zawierającym nazwy i wartości, jak opisano w poniższym poście na blogu RubyFleebie . Pozwala to na łatwą konwersję między wartościami a stałymi (szczególnie jeśli dodasz metodę klasy, aby wyszukać nazwę dla danej wartości).
Myślę, że najlepszym sposobem na zaimplementowanie wyliczenia podobnego do typów jest użycie symboli, ponieważ właściwie zachowują się jak liczby całkowite (jeśli chodzi o performace, object_id służy do dokonywania porównań); nie musisz się martwić indeksowaniem, a one wyglądają naprawdę porządnie w kodzie xD
Kolejny sposób naśladowania wyliczenia z konsekwentnym traktowaniem równości (bezwstydnie przyjęty przez Dave'a Thomasa). Pozwala na otwarte wyliczenia (podobnie jak symbole) i zamknięte (predefiniowane) wyliczenia.
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
Wypróbuj 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
zobacz więcej https://github.com/alfa-jpn/inum#usage