【拡張】論理和できるenumを書いてみた【gem】
新年あけましておめでとうございます! MUGENUP の osada です。 2014 年は挑戦の年、ということで、MUGENUP でも新しい事業を初めています。
みんなで作るゲーマー向けの攻略サイト「みなゲー」。 その攻略記事を書いてくださる方を募集しています。 スマホゲームなら俺に任せろ!という豪の方、攻略記事を書いてみませんか?
開発部でも、新しい挑戦としてRuby 2.1
、Rails 4.1
で開発を初めています!
新機能 enum
Rails 4.1 に enum という新機能が実装されました。
Ruby on Rails 4.1 Release Notes — Ruby on Rails Guides
class Conversation < ActiveRecord::Base enum status: [ :active, :archived ] end conversation.archived! conversation.active? # => false conversation.status # => "archived" Conversation.archived # => Relation for all archived Conversations
status など、「特定のグループの中のどれか1つの値を持つ要素」を便利に書けるようになりました。
enum では プレイングマネージャー を表現できない
enum はグループの間でどれか一つの要素を表現します。各要素は排他関係です。:active
と :archived
が同時に存在することはありません。
そう聞くと、グループの複数の値が共存して欲しい。 そう思うことはないでしょうか?
例えば、野球で、監督兼選手は、プレイングマネージャーと呼ばれるそうです。
class User < ActiveRecord::Base enum role: [ :manager, :player ] end
しかしこのとき、enum ではプレイングマネージャーを表現できません。User
がなれるのは、manager(監督)か、player(選手)しかないからです。
論理和できる列挙型、bitwise_enum を作ってみた
bitwise_enum/lib/bitwise_enum.rb at master · osdakira/bitwise_enum
とうことで複数の要素を表現できる、bitwise_enum
を作ってみました。
通常のenum
は、要素を[0, 1, 2]
と表現していくため、
manager
は 0
、palyer
には 1
が入ります。
一方、bitwise_enum
では、manager
は1
、player
には2
が入ります。
プレイングマネージャーを表現したいときは3
にすればOK、ということですね。
これはビット(2進数)に直すと、1桁目がmanager
、2桁目がplayer
を表現していることになります。
このようにすることで、複数の要素を同時に表現することが可能です。
使い方
class User < ActiveRecord::Base bitwise_enum role: [ :manager, :player ] end
基本的な使い方はenum
と同様です。
user = User.new user.manager! user.manager? # => true user.role # => "['manager']" user = User.new user.player! user.player? # => true user.role # => "['player']"
違いとしては、要素が共存できるということです。
user.manager! user.manager? # => true user.player! user.player? # => true user.role # => "['manager', 'player']"
プレイングマネージャーが表現出来ました!
一方で、今までは要素は上書きしていましたが、
bitwise_enum
では、明示的に削除する必要があります。
そのためにnot_xx()
というメソッドがあります。
user.manager! user.manager? # => true user.not_manager! user.manager? # => false
また、全てをリセットするためのreset_xx()
というメソッドがあります。
user.manager! # => ['manager'] user.reset_role # => nil user.role = []
enum
の実装も同じなのですが、manager!
を呼んだ時点で、
update!
が動いているため、即座にSQLが動いてしまいます。
このままでは error ハンドリングするときに困るので、代入するときにもビット演算を行うようになっています。
user.role = :manager user.role # => ['manager'] user.role = :admin user.role # => ['manager', 'admin']
scope も、通常の値ではなく、ビット演算の値でSQLを発行します。
User.manager # => SELECT `users`.* FROM `users` WHERE (role & 1 = 1)
ただコレに関しでは実装部分が、arel
を使わない未熟なものです。
klass.scope value, -> { klass.where("#{name} & #{bit} = #{bit}") }
どなたかarel
の使い方について、情報を教えていただけないでしょうか!
以上が、プレイングマネージャーが実装できるbitwise_enum
の説明です。
gem にしてみた
せっかくなので gem にしてみました。
gem を作るのは簡単でしたが、テスト環境の構築に迷いました。rails に載せずに環境を作るのは難しいですね。
まとめ
- 列挙した要素の値を、重複して持つことができる
bitwise_enum
を実装しました。 - enum のコードにビット演算を追加して実現しています。
皆さんはenum
をどう使われているでしょうか?是非使い方を教えて下さい!
宣伝
MUGENUP では、rails を使いたいエンジニアを募集中です。 無限流開発、ご一緒しませんか?
大きな裁量で自社サービス開発!Rubyエンジニアをウォンテッド! - 株式会社MUGENUPの求人 - Wantedly