Как реализовать Enums в Ruby? - PullRequest
       81

Как реализовать Enums в Ruby?

301 голосов
/ 16 сентября 2008

Какой лучший способ реализовать идиому enum в Ruby? Я ищу что-то, что я могу использовать (почти), например перечисления Java / C #.

Ответы [ 26 ]

295 голосов
/ 16 сентября 2008

Два пути. Символы (:foo обозначение) или константы (FOO обозначение).

Символы подходят, если вы хотите улучшить читаемость, не засоряя код буквальными строками.

postal_code[:minnesota] = "MN"
postal_code[:new_york] = "NY"

Константы подходят, когда у вас есть базовое значение, которое важно. Просто объявите модуль для хранения ваших констант, а затем объявите константы в этом.

module Foo
  BAR = 1
  BAZ = 2
  BIZ = 4
end

flags = Foo::BAR | Foo::BAZ # flags = 3
55 голосов
/ 30 мая 2011

Я удивлен, что никто не предлагал что-то вроде следующего (получено из 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

Что можно использовать так:

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

Пример:

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

Это хорошо работает в сценариях базы данных или при работе с константами / перечислениями стиля C (как в случае использования FFI , который RAPI широко использует).

Кроме того, вам не нужно беспокоиться о том, что опечатки могут вызвать сбои в режиме без вывода сообщений, как если бы вы использовали решение типа хеш-функции.

52 голосов
/ 16 сентября 2008

Самый идиоматический способ сделать это - использовать символы. Например, вместо:

enum {
  FOO,
  BAR,
  BAZ
}

myFunc(FOO);

... вы можете просто использовать символы:

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

Это немного более открытый, чем перечисления, но он хорошо сочетается с духом Руби

Символы также работают очень хорошо. Например, сравнение двух символов на равенство намного быстрее, чем сравнение двух строк.

32 голосов
/ 15 апреля 2011

Я использую следующий подход:

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

Мне нравятся следующие преимущества:

  1. Визуально группирует значения как одно целое
  2. Выполняет некоторую проверку во время компиляции (в отличие от просто использования символов)
  3. Я могу легко получить доступ к списку всех возможных значений: просто MY_ENUM
  4. Я могу легко получить доступ к различным значениям: MY_VALUE_1
  5. Может иметь значения любого типа, не только Symbol

Символы могут быть лучше, потому что вам не нужно писать имя внешнего класса, если вы используете его в другом классе (MyClass::MY_VALUE_1)

18 голосов
/ 08 декабря 2014

Если вы используете Rails 4.2 или выше, вы можете использовать перечисления Rails.

В Rails теперь есть перечисления по умолчанию, без необходимости включать какие-либо драгоценные камни.

Это очень похоже (и больше с функциями) на Java, перечисления C ++.

Цитируется из 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
8 голосов
/ 12 июля 2012

Это мой подход к перечислениям в Ruby. Я собирался кратко и сладко, не обязательно самый C-like. Есть мысли?

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 голосов
/ 17 марта 2011

Проверьте драгоценный камень 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
7 голосов
/ 07 декабря 2012

Я знаю, что прошло уже много времени с тех пор, как парень отправил этот вопрос, но у меня был тот же вопрос, и этот пост не дал мне ответа. Мне нужен был простой способ увидеть, что представляет число, простое сравнение и, прежде всего, поддержка ActiveRecord для поиска с использованием столбца, представляющего перечисление.

Я ничего не нашел, поэтому сделал потрясающую реализацию под названием yinum , которая позволила все, что я искал. Сделал тонну спецификаций, так что я уверен, что это безопасно.

Некоторые примеры функций:

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
5 голосов
/ 20 декабря 2014

Возможно, лучший легкий подход будет

module MyConstants
  ABC = Class.new
  DEF = Class.new
  GHI = Class.new
end

Таким образом, значения имеют связанные имена, как в Java / C #:

MyConstants::ABC
=> MyConstants::ABC

Чтобы получить все значения, вы можете сделать

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

Если вы хотите порядковый номер перечисления, вы можете сделать

MyConstants.constants.index :GHI
=> 2
5 голосов
/ 17 марта 2011

Если вы беспокоитесь об опечатках с символами, убедитесь, что ваш код вызывает исключение при доступе к значению с несуществующим ключом. Вы можете сделать это, используя fetch вместо []:

my_value = my_hash.fetch(:key)

или, сделав хеш, по умолчанию создаст исключение, если вы предоставите несуществующий ключ:

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

Если хеш уже существует, вы можете добавить поведение, вызывающее исключения:

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

Обычно вам не нужно беспокоиться о безопасности опечаток с константами. Если вы неправильно напишите имя константы, обычно возникает исключение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...