Вопрос

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

Это было полезно?

Решение

Есть два способа.Символы (: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

Другие советы

Я удивлен, что никто не предложил что-то вроде следующего (собранного из РАПИ драгоценный камень):

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 (как в случае использования ФФИ, который широко использует RAPI).

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

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

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)

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

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

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

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

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

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

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

Если вы используете 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

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

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

Посмотрите на драгоценный камень 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

Я знаю, что прошло много времени с тех пор, как парень опубликовал этот вопрос, но у меня был тот же вопрос, и этот пост не дал мне ответа.Я хотел простой способ увидеть, что представляет собой число, простое сравнение и, прежде всего, поддержку 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

Если вас беспокоят опечатки с символами, убедитесь, что ваш код вызывает исключение при обращении к значению с несуществующим ключом.Вы можете сделать это с помощью 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

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

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

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

Кто-то пошел дальше и написал рубиновый самоцвет под названием Увеличить.Он утверждает, что получает наиболее близкое поведение, подобное Java / C #.Лично я все еще изучаю Ruby, и я был немного шокирован, когда захотел сделать так, чтобы определенный класс содержал статическое перечисление, возможно, хэш, что его было нелегко найти через Google.

Все зависит от того, как вы используете перечисления Java или C #.То, как вы его используете, будет определять решение, которое вы выберете в Ruby.

Попробуйте родной Set введите, например:

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

Недавно мы выпустили драгоценный камень это реализует Перечисления в Ruby.В моем Публикация вы найдете ответы на свои вопросы.Также я описал там, почему наша реализация лучше существующих (на самом деле в Ruby еще много реализаций этой функции в виде драгоценных камней).

Символы - это путь ruby.Однако иногда нужно обратиться к какому-нибудь C-коду или чему-то еще, или Java, которые предоставляют некоторое перечисление для различных вещей.


#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

Затем это можно использовать следующим образом


require 'server_roles'

sSymb, sEnum =EnumLike.server_role()

foreignvec[sEnum[:SERVER_WORKSTATION]]=8

Это, конечно, можно сделать абстрактным, и вы можете создать наш собственный класс Enum

Я реализовал подобные перечисления

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

тогда его легко выполнять.

EnumType.ENUM_1.label

...

enum = EnumType.find_by_id 1

...

valueArray = EnumType.values

Это кажется немного излишним, но это методология, которую я использовал несколько раз, особенно там, где я интегрируюсь с xml или чем-то подобным.

#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

Это придает мне строгость перечисления c #, и оно привязано к модели.

Другим решением является использование OpenStruct.Это довольно прямолинейно и чисто.

https://ruby-doc.org/stdlib-2.3.1/libdoc/ostruct/rdoc/OpenStruct.html

Пример:

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

Большинство людей используют символы (это :foo_bar синтаксис).Это своего рода уникальные непрозрачные значения.Символы не принадлежат ни к одному типу в стиле перечисления, поэтому на самом деле они не являются точным представлением типа перечисления C, но это почти настолько хорошо, насколько это возможно.

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

Выходной сигнал:

1 - а
2 - б
3 - с
4 - d

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)

Выходной сигнал:

GOOD

Иногда все, что мне нужно, это иметь возможность извлекать значение enum и идентифицировать его имя, аналогичное 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'

На мой взгляд, это служит цели enum и делает его также очень расширяемым.Вы можете добавить дополнительные методы в класс Enum, и viola получит их бесплатно во всех определенных перечислениях.например.get_all_names и тому подобное.

Другой подход заключается в использовании класса Ruby с хэшем, содержащим имена и значения, как описано ниже Запись в блоге RubyFleebie.Это позволяет вам легко выполнять преобразование между значениями и константами (особенно если вы добавляете метод класса для поиска имени для данного значения).

Я думаю, что лучший способ реализовать перечисление, подобное типам, - это использовать символы, поскольку они в значительной степени ведут себя как целое число (когда дело доходит до производительности, object_id используется для сравнения);вам не нужно беспокоиться об индексации, и они выглядят действительно аккуратно в вашем коде xD

Еще один способ имитировать перечисление с последовательной обработкой равенства (бесстыдно перенят у Дейва Томаса).Разрешает открытые перечисления (очень похожие на символы) и закрытые (предопределенные) перечисления.

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

Попробуй инум.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

Показать еще https://github.com/alfa-jpn/inum#usage

Быстро и грязно, по ощущениям как С#:

class FeelsLikeAnEnum
  def self.Option_1() :option_1 end
  def self.Option_2() :option_2 end
  def self.Option_3() :option_3 end
end

Используйте это так, как вы бы использовали перечисление:

method_that_needs_options(FeelsLikeAnEnum.Option_1)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top