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
次のようなものを誰も提供していないことに驚いています( 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
次のように使用できます:
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が広範囲に使用する FFI を使用する場合のように)よく機能します。
また、ハッシュタイプのソリューションを使用する場合のように、タイプミスがサイレント障害を引き起こすことを心配する必要はありません。
これを行う最も慣用的な方法は、シンボルを使用することです。たとえば、次の代わりに:
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の精神によく合います。
シンボルも非常によく機能します。たとえば、2つのシンボルが等しいかどうかを比較する方が、2つの文字列を比較するよりもはるかに高速です。
私は次のアプローチを使用します。
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には、gemを含める必要なしに、デフォルトで列挙型が追加されました。
これは、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の列挙型に対する私のアプローチです。私は短くて甘いものを目指していましたが、必ずしも最も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 gem 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
おそらく、最良の軽量アプローチは
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
シンボルのタイプミスが心配な場合は、存在しないキーを持つ値にアクセスするときにコードで例外が発生することを確認してください。これを行うには、[]
ではなく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
通常、定数を使用してタイプミスの安全性を心配する必要はありません。定数名のスペルを間違えると、通常は例外が発生します。
誰かが先に進み、 Renum と呼ばれるRuby gemを書きました。最も近いJava/C#のような動作を得ると主張しています。個人的に私はまだRubyを学んでおり、特定のクラスに静的な列挙型、おそらくハッシュを含めるようにしたいときに少しショックを受けました。
それはすべて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"}>
これは少し不必要に思えますが、これは私が数回使用した方法論であり、特に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#列挙型の厳密さが得られ、モデルに関連付けられます。
シンボルは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
別の解決策は、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の列挙型の忠実な表現ではありませんが、これは得られるものとほとんど同じです。
必要なのは、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クラスにメソッドを追加すると、定義されているすべての列挙型でビオラが無料で取得できます。例えば。 get_all_namesなど。
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
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
早くて汚い、C#のように感じる:
class FeelsLikeAnEnum
def self.Option_1() :option_1 end
def self.Option_2() :option_2 end
def self.Option_3() :option_3 end
end
Enumを使用するように使用します。
method_that_needs_options(FeelsLikeAnEnum.Option_1)
型のような列挙を実装する最良の方法はシンボルを使用することだと思います(ほとんどの場合、パフォーマンスに関しては、object_idが比較に使用されます)。インデックス作成について心配する必要はなく、コードxDで本当にきれいに見える
別のアプローチは、次のように名前と値を含むハッシュでRubyクラスを使用することです RubyFleebieブログ投稿 。これにより、値と定数を簡単に変換できます(特に、特定の値の名前を検索するクラスメソッドを追加する場合)。
一貫性のある等価性処理を使用して列挙型を模倣する別の方法(Dave Thomasから恥知らずに採用)。開いている列挙型(シンボルとほぼ同じ)と閉じた(定義済み)列挙型を許可します。
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