Jak zaimplementować Enums w Ruby?

Jaki jest najlepszy sposób implementacji idiomu enum w Rubim? Szukam czegoś, czego mógłbym użyć (prawie) jak Java / C # enums.

 332
Author: Trilarion, 2008-09-16

25 answers

Na dwa sposoby. Symbole (:foo notacja) lub stałe (FOO notacja).

Symbole są odpowiednie, gdy chcesz poprawić czytelność bez zaśmiecania kodu literalnymi ciągami znaków.

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ł, który przechowuje Twoje stałe, a następnie zadeklaruj stałe w nim.

module Foo
  BAR = 1
  BAZ = 2
  BIZ = 4
end
 
flags = Foo::BAR | Foo::BAZ # flags = 3

Dodano 2021-01-17

Jeśli przekazujesz wartość enum (na przykład przechowujesz ją w bazie danych) i musisz być w stanie przetłumaczyć wartość z powrotem na symbol, istnieje mashup obu podejść

COMMODITY_TYPE = {
  currency: 1,
  investment: 2,
}

def commodity_type_string(value)
  COMMODITY_TYPE.key(value)
end

COMMODITY_TYPE[:currency]

To podejście zainspirowane odpowiedzią andrew-Grimma https://stackoverflow.com/a/5332950/13468

Polecam również przeczytać resztę odpowiedzi tutaj, ponieważ istnieje wiele sposobów, aby rozwiązać ten problem i to naprawdę sprowadza się do tego, co jest o enum innego języka, na którym ci zależy

 331
Author: mlibby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-17 15:16:00

Dziwię się, że nikt nie zaoferował czegoś takiego (pobranego z 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

Które mogą być używane 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

To dobrze sprawdza się w scenariuszach bazodanowych, lub gdy radzimy sobie ze stałymi/enumami w stylu C (jak to ma miejsce w przypadku używania FFI , z których RAPI szeroko korzysta).

Ponadto, nie musisz się martwić o literówki powodujące ciche błędy, tak jak przy użyciu rozwiązania typu hash.

 60
Author: Charles,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-29 21:19:58

Najbardziej idiomatycznym sposobem na to jest użycie symboli. Na przykład zamiast:

enum {
  FOO,
  BAR,
  BAZ
}

myFunc(FOO);

...można po prostu używać 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ż enum, ale dobrze pasuje do Ducha rubinowego.

Symbole również działają bardzo dobrze. Porównywanie dwóch symboli dla równości, na przykład, jest znacznie szybsze niż porównywanie dwóch ciągów.

 51
Author: emk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-16 19:06:04

Stosuję następujące podejście:

class MyClass
  MY_ENUM = [MY_VALUE_1 = 'value1', MY_VALUE_2 = 'value2']
end

Podoba mi się za następujące zalety:

  1. grupuje wartości wizualnie jako jedną całość
  2. W przeciwieństwie do symboli, program sprawdza czas kompilacji.]}
  3. mogę łatwo uzyskać dostęp do listy wszystkich możliwych wartości: just MY_ENUM
  4. mogę łatwo uzyskać dostęp do różnych wartości: MY_VALUE_1
  5. może mieć wartości dowolnego typu, a nie tylko Symbol

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

 44
Author: Alexey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-14 09:45:06

Jeśli używasz Rails 4.2 lub nowszego, możesz użyć rails enums.

Rails ma teraz domyślnie enums bez potrzeby dołączania żadnych klejnotów.

Jest to bardzo podobne (i Więcej z funkcjami) do JAVA, C++ enums.

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
 18
Author: vedant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-24 11:23:26

Wiem, że minęło sporo czasu odkąd facet napisał to pytanie, ale ja 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 wsparcie ActiveRecord dla lookup przy użyciu kolumny reprezentującej enum.

Nic nie znalazłem, więc zrobiłem niesamowitą implementację o nazwie yinum , która pozwalała na wszystko, czego szukałem. Zrobiłem mnóstwo specyfikacji, więc jestem prawie pewien, że to bezpiecznie.

Niektóre funkcje przykładowe:

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
 9
Author: Oded Niv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-03 07:35:34

To jest moje podejście do enum w Ruby. Szedłem na krótko i słodko, niekoniecznie na najbardziej "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
 8
Author: johnnypez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-12 15:48:35

Zobacz 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
 8
Author: dB.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-28 02:03:20

Być może najlepszym lekkim podejściem byłoby

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 wykonać

MyConstants.constants
=> [:ABC, :DEF, :GHI] 

Jeśli chcesz mieć wartość porządkową enum, możesz zrobić

MyConstants.constants.index :GHI
=> 2
 8
Author: Daniel Lubarov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-19 21:50:52

Jeśli obawiasz się literówek z symbolami, upewnij się, że kod wyświetla wyjątek, gdy uzyskujesz dostęp do wartości za pomocą nieistniejącego klucza. Możesz to zrobić używając fetch zamiast []:

my_value = my_hash.fetch(:key)

Lub poprzez wywołanie skrótu domyślnie wywołuje 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 hash już istnieje, możesz dodać zachowanie podnoszą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

Normalnie nie musisz się martwić o bezpieczeństwo literówek ze stałymi. Jeśli źle przeliczysz stałą nazwa, Zwykle wywoła wyjątek.

 6
Author: Andrew Grimm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-16 23:14:48

Wszystko zależy jak używasz Javy lub C# enums. Sposób użycia będzie dyktował rozwiązanie, które wybierzesz w Rubim.

Spróbuj natywnego typu Set, 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"}>
 4
Author: mislav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-16 20:35:24

Ktoś poszedł naprzód i napisał rubinowy klejnot o nazwie Renum . Twierdzi, że zachowanie podobne do Javy/C# jest najbliższe. Osobiście wciąż uczę się Rubiego i byłem trochę zszokowany, gdy chciałem, aby konkretna Klasa zawierała statyczne enum, prawdopodobnie hash, którego nie było łatwo znaleźć przez google.

 4
Author: dlamblin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-04 20:47:49

Niedawno wydaliśmy gem , który implementuje Enums w Rubim . W moim poście znajdziesz odpowiedzi na swoje pytania. Opisałem tam również dlaczego nasza implementacja jest lepsza od istniejących (w rzeczywistości jest wiele implementacji tej funkcji w Ruby jeszcze jako perełki).

 3
Author: ka8725,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-28 17:53:47

Innym rozwiązaniem jest użycie OpenStruct. Jest całkiem prosto i czysto.

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'
 3
Author: Roger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-14 09:17:55

Symbole to sposób ruby. Czasami jednak trzeba porozmawiać z jakimś kodem C lub czymś w rodzaju Javy, które ujawniają pewne 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

To może być użyte w ten sposób


require 'server_roles'

sSymb, sEnum =EnumLike.server_role()

foreignvec[sEnum[:SERVER_WORKSTATION]]=8

To może być oczywiście abstrakcyjne i można rolować naszą własną klasę Enum

 2
Author: Jonke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-02 20:47:41

Zaimplementowałem takie enums

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

Następnie jego łatwe do wykonania operacje

EnumType.ENUM_1.label

...

enum = EnumType.find_by_id 1

...

valueArray = EnumType.values
 2
Author: Masuschi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-28 13:38:05
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)

Wyjście:

GOOD
 2
Author: Hossein,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-11 12:29:01

Wydaje się to trochę zbędne, ale jest to metodologia, której używałem kilka razy, szczególnie tam, gdzie integruję się z xml lub czymś takim.

#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

To daje mi rygor C # enum i jest związany z modelem.

 2
Author: jjk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-05 17:51:27

Większość ludzi używa symboli(to jest składnia :foo_bar). To coś w rodzaju unikalnych nieprzezroczystych wartości. Symbole nie należą do żadnego typu w stylu enum, więc nie są wierną reprezentacją typu enum C, ale jest to prawie tak dobre, jak to tylko możliwe.

 1
Author: Jan Krüger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-16 19:04:45
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

Wyjście:

1-a
2-B
3-c
4-D

 1
Author: Anu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-06 16:09:05

Czasami wszystko czego potrzebuję to być w stanie pobrać wartość enum i zidentyfikować jej nazwę podobną do java world.

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'

Dla mnie to służy celowi enum i sprawia, że jest on również bardzo rozszerzalny. Możesz dodać więcej metod do klasy Enum i otrzymać je za darmo we wszystkich zdefiniowanych enumach. na przykład. get_all_names i takie tam.

 1
Author: dark_src,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-21 21:54:19

Spró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

 1
Author: horun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-11 04:47:02

Innym podejściem jest użycie klasy Ruby z Hashem zawierającym nazwy i wartości, jak opisano w następującym po rubyfleebie blog post . Pozwala to na łatwą konwersję pomiędzy wartościami i stałymi (zwłaszcza jeśli dodasz metodę klasy, aby wyszukać nazwę dla danej wartości).

 0
Author: Philippe Monnet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-29 18:07:32

Myślę, że najlepszym sposobem na zaimplementowanie wyliczeń takich jak typy jest użycie symboli, ponieważ w zasadzie zachowują się jak liczba całkowita (jeśli chodzi o performace, object_id jest używany do porównań ); nie musisz się martwić o indeksowanie i wyglądają naprawdę schludnie w kodzie xD

 0
Author: goreorto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-04-03 23:57:49

Inny sposób naśladowania enum ze spójną obsługą równości (bezwstydnie przyjęty przez Dave ' a Thomasa). Pozwala na otwarte liczby (podobnie jak Symbole) i zamknięte (predefiniowane).

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
 0
Author: Daniel Doubleday,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-30 01:44:43