本日既に12/11ですが、1日目が空席でしたので参加します!よろしくお願いします!
概要
Rails 4.1 で導入されたActiveRecord::Enum
が気に入っており使いたかったのですが、悲しい事情により使えなかったため、旧バージョンの Rails や Rails 以外でも動くミニクローンを作ってみました。
昔話
昔話と書きましたが、今年の初めの話です。
Ruby を学び始めたばかりの頃、書きたいものが何も思い浮かばなかったので、とりあえず自分で定義した凄く簡単な BNF を元に、レキシカルアナライザやパーサを実装してみていました。
その際、トークン型を列挙型で定義しようとして「Ruby って列挙型ないんだ!」と知りました。
ActiveRecord::Enum
Ruby には列挙型はないんですが、Rails には4.1からActiveRecord::Enum
という列挙型が導入されました。
こいつがなかなか便利なやつでして、例えば、User オブジェクトにステータスを持たせるとします。
まずはActiveRecord::Enum
を使わず、愚直に書いてみます。
class User
STATUSES = {
active: 0,
inactive: 1
}
attr_accessor :status
def active?
@status == STATUSES[:active]
end
def inactive?
@status == STATUSES[:inactive]
end
end
User オブジェクトのステータスを更新しようと思ったら、
user.status = User::STATUSES[:active]
と書けば更新できますし、User のステータスがアクティブか確認したかったら、
user.active?
と、書くことができます。
さて、ここでActiveRecord::Enum
を使ってみます。
すると、
class User < ActiveRecord::Base
enum status: %i(active inactive)
end
と、書くことができます!短い!めっちゃ短い!
どんな風に動くか Rails Console で確認してみます。
# ステータスがアクティブかを確認
pry(main)> user.status
=> "active"
pry(main)> user.active?
=> true
pry(main)> user.inactive?
=> false
# シンボルを代入してステータスを更新
pry(main)> user.status = :inactive
=> :inactive
pry(main)> user.status
=> "inactive"
pry(main)> user.active?
=> false
pry(main)> user.inactive?
=> true
# 破壊的メソッドでステータスを更新
pry(main)> user.active!
=> true
pry(main)> user.active?
=> true
# ステータス一覧を取得
pry(main)> User.statuses
=> {"active"=>0, "inactive"=>1}
めっちゃ便利!凄い!
より詳しく挙動を知りたい方は、
をご覧ください。
悲しい世界
これを使ってリファクタリングできそうなソースを見つけたのですが、Rails が古かったため、使えませんでした。悲しかったです。
また、ActiveRecord::Enum
の機能なので、当然 ActiveRecord でしか使えません。
また、冒頭の昔話で述べたような、ただの Ruby プログラムに ActiveRecord 入れるのは……どうなんでしょう。
作ってみた
ないなら作ればいいじゃないかということで、試しに自分で作ってみました。
SimplerEnum という名前で公開中です。
簡素で質素な enum ですよという謙虚な気持ちで名前をつけたんですが、同僚に「他のあらゆる enum よりもシンプルだぞとアピールしてる感じが良い」と言われました……まじかよ。
((((;゚Д゚))))ガクガクブルブル
使い方を先ほどの User の例でご紹介します。
require 'simpler_enum'
class User
include SimplerEnum
simpler_enum status: %i(active inactive)
end
宣言は Rails の enum を丸パクリです!わはは!
さて、それでは動かしてみます。
# ステータスがアクティブかを確認
pry(main)> user.status
=> :active
pry(main)> user.active?
=> true
pry(main)> user.inactive?
=> false
# シンボルを代入してステータスを更新
pry(main)> user.status = :inactive
=> :inactive
pry(main)> user.status
=> :inactive
pry(main)> user.active?
=> false
pry(main)> user.inactive?
=> true
# 破壊的メソッドでステータスを更新
pry(main)> user.active!
=> :active
pry(main)> user.active?
=> true
# ステータス一覧を取得
pry(main)> User.statuses
=> {:active=>0, :inactive=>1}
ほとんど一緒!便利便利!
Github の README でもう少し詳しく書いていますので、よければそちらをご覧ください。
サポートバージョンは Ruby 1.9 以上としています。
Rails の enum との違い
ほとんど挙動が一緒ですが、Simpler と言ってるだけあって、かなり機能削減しています。
Rails は文字列を列挙型のキーとして使っており、更にシンボルでの操作に対応していますが、SimplerEnum ではシンボルをキーとして使っており、シンボルでの操作しか行うことができません。
また、SimplerEnum にはプレフィックスやサフィックスを付ける機能もありません。
シンプルに、
- 値一覧取得メソッド
- 状態問い合わせメソッド
- 状態変更メソッド
- シンボルでの入出力対応
のみを提供しています。
稼働実績
会社のプロダクションコードに、SimplerEnum をぶち込んでリファクタリングする PR をぶん投げてみたら、なんとびっくりレビュー通過して本番投入されました。
というわけで、とある Web service で絶賛稼働中です
Rails バージョンは秘密です
課題
全体的に作りが甘いです。テストも含めて。
まず、例外チェックが雑です。
他には、カバレッジは99%となっていますが、AcitveRecord で SimplerEnum を使用するテストケースは作成していません。手作業で確認してしまいました。
こう動けばいいなという、一番外側の振る舞いだけテストファーストで書いて一気に実装したので、E2E テストしかないような状態になっています。
まとめ
Ruby で列挙型使いたいなと思った時にどうぞ!