LoginSignup
9
9

More than 5 years have passed since last update.

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

Posted at

本日既に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 で列挙型使いたいなと思った時にどうぞ!

9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9