Как реализовать перечисления в Ruby?
Вопрос
Каков наилучший способ реализовать идиому 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
Мне это нравится за следующие преимущества:
- Он визуально группирует значения как единое целое
- Он выполняет некоторую проверку во время компиляции (в отличие от простого использования символов)
- Я могу легко получить доступ к списку всех возможных значений:просто
MY_ENUM
- Я могу легко получить доступ к различным значениям:
MY_VALUE_1
- Он может иметь значения любого типа, а не только Символ
Символы могут быть лучше, потому что вам не нужно писать имя внешнего класса, если вы используете его в другом классе (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)