Який найкращий спосіб реалізувати ідіому ендуму в Рубі? Я шукаю те, що можу (майже) використовувати, як переписи Java / C #.
Який найкращий спосіб реалізувати ідіому ендуму в Рубі? Я шукаю те, що можу (майже) використовувати, як переписи 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
:minnesota.to_s
під час збереження в базі даних для збереження рядкової версії символу. В Рейлі, я вважаю, є кілька допоміжних методів, щоб вирішити щось із цього.
Я здивований, що ніхто не запропонував щось подібне (зібране з дорогоцінного каміння 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 широко користується).
Крім того, вам не доведеться турбуватися про помилки, що спричиняють тихі збої, як це було б із використанням рішення хеш-типу.
Найбільш ідіоматичний спосіб зробити це - використовувати символи. Наприклад, замість:
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)
Це трохи більш відкрито, ніж переліки, але добре поєднується з духом Рубі.
Символи також дуже добре працюють. Порівняння двох символів для рівності, наприклад, набагато швидше, ніж порівняння двох рядків.
Я використовую такий підхід:
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
Conversation
класу - я вважаю, що він повинен дозволяти лише 1 екземпляр.
Це мій підхід до перерахунків у Рубі. Я збирався коротким і солодким, не обов’язково самим C-подібним. Будь-які думки?
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
Мабуть, найкращий легкий підхід був би
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
class ABC; end
Я знаю, що минуло давно, як хлопець опублікував це питання, але у мене було те саме питання, і ця публікація не дала мені відповіді. Я хотів простий спосіб зрозуміти, що представляє число, просте порівняння і найбільше підтримка 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
Зазвичай про безпеку друку з константами не потрібно турбуватися. Якщо ви неправильно написали постійне ім’я, зазвичай це призведе до виключення.
FOO_VALUES = {missing: 0, something: 1, something_else: 2, ...}
Це визначає ключові символи. missing
, something
І т.д., а також робить їх порівнянними з допомогою відповідних значень.)
Хтось пішов вперед і написав рубінову дорогоцінну каменю під назвою Renum . Він стверджує, що наближається до Java / C # подібної поведінки. Особисто я все ще вивчаю Рубі, і я був трохи шокований, коли хотів, щоб конкретний клас містив статичний перелік, можливо, хеш, що його не було легко знайти через 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"}>
Set[:a, :b, :c]
?
Нещодавно ми випустили дорогоцінний камінь, який реалізує Enums в Ruby . У моєму дописі ви знайдете відповіді на свої запитання. Також я там описав, чому наша реалізація краща за існуючі (насправді існує багато реалізацій цієї функції в Ruby, але як дорогоцінні камені).
Іншим рішенням є використання 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'
Символи - рубіновий шлях. Однак іноді потрібно поговорити з якимось кодом С або чимось таким чином, або з 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
server_Symb
) з певної причини? Якщо немає конкретної причини, для змінних snake_case_with_all_lower_case
та символів це ідіоматично :lower_case
.
server_Symb.each_with_index { |e,i| server_Enum[e] = i}
. Не потрібно i = 0
.
Я реалізував подібні переліки
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
Це дає мені суворість ac # enum і вона прив'язана до моделі.
:VAL
. Було б краще почати з масиву та побудувати хеш, використовуючи.map.with_index
.key
або , .invert
а не :VAL
ключем ( stackoverflow.com/a/10989394/2208016 )
key
invert
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 - a
2 - b
3 - c
4 - d
to_enum
дає вам enumera Tor , в той час як enum
в C # / Java сенс є enumera ції
Іноді все, що мені потрібно, - це вміти знайти значення перерахунку та визначити його ім’я, подібне до світу 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'
Це для мене відповідає цілі перерахунку, і він також дуже розширюється. Ви можете додати більше методів до класу Enum, а віола отримати їх безкоштовно у всіх визначених переліках. наприклад. 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
Спробуйте 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
дивіться більше https://github.com/alfa-jpn/inum#usage