Jak wdrożyć Enums w Ruby?

323

Jaki jest najlepszy sposób na implementację idiomu enum w Ruby? Szukam czegoś, czego mogę (prawie) użyć, na przykład wyliczeń Java / C #.

Auramo
źródło
7
@auramo, dobre pytanie i świetny wybór dla najlepszej odpowiedzi. Uwielbiam to lub nienawidzę, nie dostajesz bezpieczeństwa typu i (przynajmniej w Ruby) bezpieczeństwa literówek. Byłem podekscytowany, kiedy odkryłem wyliczenia w C #, a później w Javie (wybierz wartość, ale z nich!), Ruby w żadnym wypadku nie zapewnia prawdziwego sposobu na zrobienie tego.
Dan Rosenstark,
2
Problem z tym pytaniem polega na tym, że wyliczenia Java i C # są diametralnie różne. Członek enum Java jest instancją obiektu i singletonem. Wyliczenie Java może mieć konstruktor. Natomiast wartości wyliczone w języku C # oparte są na wartościach pierwotnych. Jakiego zachowania szuka pytający? Chociaż jest prawdopodobne, że pożądany jest przypadek C #, Java jest wyraźnie wymieniona, a nie C lub C ++, więc są pewne wątpliwości. Co do sugerowania, że ​​nie ma sposobu, aby być „bezpiecznym” w Rubim, jest to oczywiście fałszywe, ale musisz wdrożyć coś bardziej wyrafinowanego.
user1164178

Odpowiedzi:

317

Dwie drogi. Symbole ( :foonotacja) lub stałe ( FOOnotacja).

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
mlibby
źródło
2
Co jeśli te wyliczenia też są przechowywane w bazie danych? Czy notacja symboli działa? Wątpię ...
Phương Nguyễn
Użyłbym podejścia do stałych, gdybym zapisywał dane w bazie danych. Oczywiście wtedy trzeba wykonać jakiś przegląd podczas wyciągania danych z bazy danych. Możesz również użyć czegoś takiego jak :minnesota.to_spodczas 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ć.
mlibby
7
Czy moduł nie byłby lepszy do grupowania stałych - skoro nie będziesz go tworzył?
thomthom
3
Tylko komentarz. Ruby ma trochę kłopotów z konwencjami nazewnictwa, ale nie jest do końca oczywista, dopóki się nad nimi nie potkniesz. Nazwy wyliczeń muszą zawierać wszystkie wielkie litery, a pierwsza litera nazwy modułu musi być pisana wielkimi literami, aby ruby ​​wiedział, że moduł jest modułem stałych.
Rokujolady
3
Nie do końca prawda. Pierwsza litera stałej musi być wielka, ale nie wszystkie litery muszą być. Jest to kwestia preferencji konwencji. Na przykład wszystkie nazwy modułów i nazwy klas są w rzeczywistości również stałymi.
Michael Brown
59

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.

Charles
źródło
1
To świetny sposób na rozwiązanie tego konkretnego problemu, ale powód, dla którego nikt nie sugerował, prawdopodobnie ma związek z faktem, że nie przypomina on wyliczeń C # / Java.
mlibby
1
Jest to nieco niekompletne, ale stanowi dobrą wskazówkę, jak można wdrożyć rozwiązania z dynamicznym podejściem. Jest podobny do wyliczenia C # z zestawem FlagsAttribute, ale podobnie jak powyższe rozwiązania oparte na symbolach / stałych, jest to jedna z wielu odpowiedzi. Problemem jest oryginalne pytanie, które jest niejasne (C # i Java nie są wymienne). Istnieje wiele sposobów wyszczególnienia obiektów w Ruby; wybór właściwego zależy od rozwiązanego problemu. Niewłaściwe kopiowanie niewolniczych funkcji jest mylące. Prawidłowa odpowiedź musi zależeć od kontekstu.
user1164178
52

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.

emk
źródło
107
Tak więc duch Ruby brzmi: „Literówki się skompilują”
mxcl
82
Popularne frameworki Ruby w dużej mierze polegają na metaprogramowaniu środowiska wykonawczego, a wykonanie zbyt dużej kontroli czasu ładowania zabrałoby większość ekspresyjnej mocy Ruby. Aby uniknąć problemów, większość programistów Rubiego ćwiczy projektowanie oparte na testach, które znajdzie nie tylko literówki, ale także błędy logiczne.
emk
10
@yar: Cóż, projektowanie języków to szereg kompromisów, a funkcje językowe współdziałają. Jeśli chcesz dobrego, wysoce dynamicznego języka, idź z Ruby, najpierw napisz testy jednostkowe i idź z duchem języka. :-) Jeśli nie tego szukasz, istnieje wiele innych doskonałych języków, z których każdy ma inne kompromisy.
emk
10
@emk, zgadzam się, ale moim osobistym problemem jest to, że czuję się dobrze w Ruby, ale nie czuję się komfortowo refaktoryzując się w Ruby. A teraz, kiedy zacząłem pisać testy jednostkowe (wreszcie), zdaję sobie sprawę, że to nie jest panaceum: zgaduję, że 1) że kod Ruby nie jest masowo refaktoryzowany tak często, w praktyce i 2) Ruby to nie koniec -of-the-line pod względem dynamicznych języków, właśnie dlatego, że trudno jest automatycznie refaktoryzować. Zobacz moje pytanie 2317579, które dziwnie przejęli ludzie z Smalltalk.
Dan Rosenstark,
4
Tak, ale używanie tych ciągów nie byłoby zgodne z duchem języka C #, jest to po prostu zła praktyka.
Ed S.
38

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:

  1. Grupuje wartości wizualnie jako jedną całość
  2. Sprawdza czas kompilacji (w przeciwieństwie do zwykłych symboli)
  3. Mogę łatwo uzyskać dostęp do listy wszystkich możliwych wartości: po prostu MY_ENUM
  4. Mogę łatwo uzyskać dostęp do różnych wartości: MY_VALUE_1
  5. Może mieć wartości dowolnego typu, nie tylko symbol

Symbole mogą być lepsze, ponieważ nie musisz wpisywać nazwy klasy zewnętrznej, jeśli używasz jej w innej klasie ( MyClass::MY_VALUE_1)

Alexey
źródło
4
Myślę, że to najlepsza odpowiedź. Funkcjonalność, składnia i minimalny narzut kodu są najbliższe Java / C #. Możesz także zagnieździć definicje nawet głębiej niż jeden poziom i nadal odzyskiwać wszystkie wartości za pomocą MyClass :: MY_ENUM.flatten. Na marginesie użyłbym tutaj wielkich liter, tak jak jest to standard dla stałych w Rubim. MyClass :: MyEnum może być mylone z odniesieniem do podklasy.
Janosch
@Janosch, zaktualizowałem nazwiska. dzięki za sugestię
Alexey
Nadal jestem trochę zdezorientowany, a link 410'd (nie, nie 404). Czy możesz podać przykłady wykorzystania tego wyliczenia?
Shelvacu
17

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
wedant
źródło
7
Jak powiedziałeś - nieprzydatne, jeśli OP nie używa Railsów (a ściślej obiekt nie jest typu ActiveRecord). Samo wyjaśnienie mojej opinii jest wszystkim.
Ger
2
To nie są wyliczenia w Rubim, to interfejs ActiveRecord do wyliczeń w bazie danych. Nie jest to uogólnione rozwiązanie, które można zastosować w każdym innym przypadku użycia.
Adam Lassek
Aleady wspomniałem o tym w mojej odpowiedzi.
wedant
To jest najlepsza odpowiedź IFF przy użyciu Railsów.
theUtherSide
Nie podoba mi się to, ponieważ musi być przechowywane w bazie danych Railsów (do pracy) i ponieważ pozwala na tworzenie wielu instancji Conversationklasy - uważam, że musi ona dopuszczać tylko 1 instancję.
prograils
8

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
Johnnypez
źródło
8

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
Daniel Lubarov
źródło
1
IMHO bardzo dokładnie odwzorowuje użycie i cel (bezpieczeństwo typu) z Javy, a także, w ramach preferencji, stałe można zdefiniować w następujący sposób:class ABC; end
wik
8

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
Oded Niv
źródło
5

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 fetchzamiast []:

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.

Andrew Grimm
źródło
Wygląda na to, że popierasz emulację wyliczeń za pomocą skrótów , nie mówiąc wprost. Dobrym pomysłem może być edytowanie odpowiedzi. (I również mają obecnie zapotrzebowanie na coś takiego jak teksty stałe w Ruby, a moje pierwsze podejście do rozwiązywania go jest za pomocą skrótów: FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}Definiuje główne symbole. missing, somethingItp, a także sprawia, że są porównywalne poprzez odpowiednie wartości.)
Teemu Leisti,
Nie mówię tego na samym początku odpowiedzi.
Teemu Leisti,
4

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.

dlamblin
źródło
Nigdy nie potrzebowałem wyliczenia w Ruby. Symbole i stałe są idiomatyczne i rozwiązują te same problemy, prawda?
Chuck
Prawdopodobnie Chuck; ale szukanie wyliczenia w rubinie nie zaprowadzi cię tak daleko. To pokaże ci wyniki najlepszej ludzkiej próby bezpośredniego odpowiednika. Zastanawiam się, może jest coś fajnego w tym, że ta koncepcja jest razem.
dlamblin
@Chuck Symbole i stałe nie wymuszają, np. Że wartość musi być jednym z małego zestawu wartości.
David Moles,
3

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 Settyp 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"}>
mislav
źródło
9
Dlaczego nie użyć symboli Set[:a, :b, :c]?
Dan Rosenstark,
2
Znacznie lepsza praktyka używania symboli tutaj, IMO.
Collin Graves,
3

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).

ka8725
źródło
Pozwala na samodzielne zwiększanie wartości bez wyraźnego ich podawania. +1
dimid
3

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'
zrozumiałem
źródło
2

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

Jonke
źródło
Czy server_Symbz 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_casei symbole :lower_case.
Andrew Grimm,
1
@Andrzej; ten przykład został zaczerpnięty ze świata rzeczywistego, a dokumentacja protokołu sieciowego używała xxx_Rrr, więc kod w kilku językach wykorzystywał tę samą koncepcję, aby można było śledzić zmiany specyfikacji.
Jonke,
1
Kod golfa: server_Symb.each_with_index { |e,i| server_Enum[e] = i}. Nie ma potrzeby i = 0.
Andrew Grimm,
2

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
Masuschi
źródło
2

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.

jjk
źródło
Nie radziłbym tego podejścia, ponieważ polega ono na ręcznym ustawianiu wartości i zapewnianiu prawidłowego zamówienia :VAL. Lepiej byłoby zacząć od tablicy i zbudować skrót za pomocą.map.with_index
DaveMongoose,
1
Dokładnym celem jest przywiązanie się do wartości podyktowanej przez osoby trzecie. Nie chodzi o samo rozszerzanie, ale o radzenie sobie z zewnętrznymi ograniczeniami, które wpływają na obliczalność w granicach procesu.
jjk
Uczciwy punkt! W takim przypadku zdecydowanie warto podać wartości, ale byłbym skłonny do wyszukiwania wstecznego z kluczem .keylub .invertzamiast niego :VAL( stackoverflow.com/a/10989394/2208016 )
DaveMongoose 23.0919
Tak, to (z powrotem na ciebie) słuszna kwestia. Mój rubin był nieelegancki i nieporęczny. Zdecydowanie skorzystałby keylubinvert
jjk
1

Większość ludzi używa symboli (taka jest :foo_barskł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.

Jan Krüger
źródło
1
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

Anu
źródło
to_enumdaje enumera Tora , natomiast enumw C # / Java jest sens enumera cja
DaveMongoose
1
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)

Wynik:

GOOD
Hossein
źródło
1

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.

dark_src
źródło
0

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).

Philippe Monnet
źródło
0

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

goreorto
źródło
0

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
Daniel Doubleday
źródło