Ruby
Rails

Rails4.1のActiveRecord::Enumを旧バージョンのRailsやRails以外でも使いたい

More than 1 year has passed since last update.

本日既に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 で絶賛稼働中です:laughing:

Rails バージョンは秘密です:innocent:

課題

全体的に作りが甘いです。テストも含めて。

まず、例外チェックが雑です。

他には、カバレッジは99%となっていますが、AcitveRecord で SimplerEnum を使用するテストケースは作成していません。手作業で確認してしまいました。

こう動けばいいなという、一番外側の振る舞いだけテストファーストで書いて一気に実装したので、E2E テストしかないような状態になっています。

まとめ

Ruby で列挙型使いたいなと思った時にどうぞ!