RubyでInteger値を持つEnum、Inumを作ってみた。
この記事はバージョン2.x系の記事で、現行バージョンの3.xとは互換性がありません。
RailsのEnumと相性が悪い人向けのInumをバージョンアップさせた話。 - 波打際のブログさん
こちらの記事がバージョン3.x系の記事となります。
はじめに
最近Rubyを使用する機会が増えてきたのですが、Rubyに列挙型の実装がありませんでした。そこで、Gemを探してみたものの、C言語ライクなInteger値を持つEnumで、思ったとおりの事が可能なGemが存在しなかったので、自前で実装する機会がありました。その実装の仕組みを参考に汎用的に使用可能なEnum(Inum)を作成してGemとして公開しました。
- 2013年11月28日、機能追加に伴い追記しました。
- 2013年12月03日、バージョン2.0.0リリースに伴い記事を修正しました。
- 2013年12月10日、バージョン2.0.5リリースに伴い記事を修正しました。
使い方
列挙型の定義
Inum::Baseを継承したクラス内で、define_enumメソッドを呼び出して列挙型を定義します。define_enumの第一引数には列挙型のラベル(名前)をSymbolで、第二引数には列挙型の値をIntegerでそれぞれ渡します。第二引数の列挙型の値は省略可能で、省略された場合0から自動的にインクリメントされます。
class AnimeType < Inum::Base define_enum :NyarukoSan, 0 define_enum :KiniroMosaic, 1 define_enum :KillMeBaby, 2 end
列挙型の使用方法
Inumを継承した列挙型は次のように使用出来ます。
p AnimeType::NyarukoSan # => NyarukoSan p AnimeType::NyarukoSan.to_i # => 0 # 数値や、シンボルから列挙型へのパーズが可能です。 type = AnimeType::parse(1) p type.equal?(AnimeType::KiniroMosaic) # => true # eql?メソッドを使用すると、比較対象のオブジェクトをパーズ後比較します。 p AnimeType::KillMeBaby.eql?('KillMeBaby') # => true # underscoreメソッドを使用すると、Enumのラベルをアンダースコアつなぎの文字列にします。 p AnimeType::NyarukoSan.underscore # => nyaruko_san # +と-の単項演算子も定義されています。 p AnimeType::NyarukoSan + 1 # => KiniroMosaic p AnimeType::KillMeBaby - 1 # => KiniroMosaic # eachによる繰り返しもできます。 AnimeType::each do |enum| p enum end
Enumerableモジュールをextendし、Comparableモジュールをincludeしていますので、mapなどのメソッドが利用可能です。
その他のメソッドについてはドキュメント(Class: Inum::Base — Documentation for alfa-jpn/inum (master))を参照してください。
その他の使い方
i18nを利用する
Enum(Inum)で to_t メソッドを利用することで、i18nでローカライズされた文字列を取得することが可能です。
次のような多言語化ファイルが存在した場合
ja: inum: anime_type: nyaruko_san: "這いよれ!ニャル子さん" kiniro_mosaic: "きんいろモザイク" kill_me_baby: "キルミーベイベー"
translateメソッドを用いると次の結果になります。
p AnimeType::NyarukoSan.translate # => 這いよれ!ニャル子さん p AnimeType::KiniroMosaic.translate # => きんいろモザイク p AnimeType::KillMeBaby.translate # => キルミーベイベー
i18nの辞書定義はデフォルトでは inum.{名前空間(モジュール名)}.{クラス名}.{メンバー名} を参照しますが、i18n_keyクラスメソッドをオーバーライドすることで参照先を書き換えることも可能です。
class AnimeType < Inum::Base define_enum :NyarukoSan, 0 define_enum :KiniroMosaic, 1 define_enum :KillMeBaby, 2 # i18n_keyをオーバーライド(内容はデフォルトの定義です。) def self.i18n_key(label) # underscoreメソッドでキャメルをアンダースコアつなぎにした上で、::を.に置き換えています。 Inum::Utils::underscore("inum::#{self.name}::#{label}") end end
Enumをクラスの変数に結びつける
DefineCheckMethodモジュールを利用すると、クラスの変数とEnumを関連付けし、判別用メソッドを自動的に定義することが可能です。
class Anime extend Inum::DefineCheckMethod attr_accessor :type define_check_method :type, AnimeType end
define_check_methodにカラム名と関連付けさせるInumを継承したEnumクラスを渡して判別用メソッドを自動的に定義しています。
anime = Anime.new anime.type = AnimeType::NyarukoSan p anime.nyaruko_san? # => true p anime.kiniro_mosaic? # => false # typeにはパーズ可能なメソッドなら何でも入ります。 anime.type = 1 p anime.kiniro_mosaic? # => true
また、define_check_methodの第三引数は省略可能オプションになっていて、prefixの設定が可能です。prefixを指定すると定義されるメソッドの先頭にprefixが付与されます。
class Anime extend Inum::DefineCheckMethod attr_accessor :type define_check_method :type, AnimeType, 'anime' end
prefixにanimeを指定すると、定義されるメソッドは次のようになります。
anime = Anime.new anime.type = AnimeType::NyarukoSan p anime.anime_nyaruko_san? # => true
おわりに
何か改善案あればpullrequest、issue気軽に投げてください、githubアカウントお持ちでない方はこのブログのコメント欄にもお気軽にどうぞ。