読者です 読者をやめる 読者になる 読者になる

【拡張】論理和できるenumを書いてみた【gem】

新年あけましておめでとうございます! MUGENUP の osada です。 2014 年は挑戦の年、ということで、MUGENUP でも新しい事業を初めています。

ゲームライター・編集者募集中|みなゲー

f:id:mgnup:20140113031124p:plain

みんなで作るゲーマー向けの攻略サイト「みなゲー」。 その攻略記事を書いてくださる方を募集しています。 スマホゲームなら俺に任せろ!という豪の方、攻略記事を書いてみませんか?

開発部でも、新しい挑戦としてRuby 2.1Rails 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]と表現していくため、 manager0palyer には 1が入ります。

一方、bitwise_enum では、manager1playerには2が入ります。 プレイングマネージャーを表現したいときは3にすればOK、ということですね。

これはビット(2進数)に直すと、1桁目がmanager、2桁目がplayerを表現していることになります。 このようにすることで、複数の要素を同時に表現することが可能です。

f:id:mgnup:20140113171345p:plain

使い方

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 にしてみました。

osdakira/bitwise_enum

gem を作るのは簡単でしたが、テスト環境の構築に迷いました。rails に載せずに環境を作るのは難しいですね。

まとめ

  • 列挙した要素の値を、重複して持つことができる bitwise_enumを実装しました。
  • enum のコードにビット演算を追加して実現しています。

皆さんはenumをどう使われているでしょうか?是非使い方を教えて下さい!

宣伝

MUGENUP では、rails を使いたいエンジニアを募集中です。 無限流開発、ご一緒しませんか?

大きな裁量で自社サービス開発!Rubyエンジニアをウォンテッド! - 株式会社MUGENUPの求人 - Wantedly

f:id:mgnup:20131118023234p:plain