去年の社のアドベントカレンダーで書いた記事をこちらでも共有。
Ruby, とくに Rails で使える enumeration 実装
- Enumerize
ActiveRecord::Enum
の機能を比較してみました。
以下のコード例では require 'enumerize'
しているものとします。また、user
は User
のインスタンスとします。
Enumerize 概要
The Ruby Toolbox の "ActiveRecord Enumeration" カテゴリでは 1 位になっている gem です。
O/R マッパとは独立した gem なので、Active Record オブジェクトはもちろんのこと、プレーンな Ruby オブジェクトに対してもふつうに使えます。
次のようなコードで、クラスに enumeration を組み込めます。
class User
extend Enumerize
enumerize :membership, in: [:free, :regular, :premium]
end
ActiveRecord::Enum
概要
※名前が長いので以下 enum
とします
Rails 4.1 から入った機能です。名前空間が示すとおり、Active Record オブジェクトで使うのが前提となっています。
Active Record を継承したクラスへ次のように enumeration を組み込めます。
class User < ActiveRecord::Base
enum membership: [:free, :regular, :premium]
end
比較
上で例示した User
のインスタンス user
を使い、両者の主な機能について表にまとめてみました。
機能 | Enumerize | ActiveRecord::Enum |
---|---|---|
値の更新 | user.membership = :free |
user.free! |
文字列化 | user.membership.text |
user.membership |
デフォルト値の設定 |
default: :free (lambda も渡せる) |
DB のデフォルト値設定が別途必要 |
i18n 対応 | デフォルトで対応 | enum_help が別途必要 |
predicate メソッド生成 |
predicate: true で生成 |
デフォルトで生成 |
scope 生成 |
scope: true で生成し、名前変更も可能 |
デフォルトで生成 |
prefix, suffix 生成 | predicate だけ生成可能 | predicate, 更新メソッドに生成可能(ただし Rails 5.0 から) |
序数の取得 | user.membership.find_value(:free).value |
user.membership[:free] |
複数値の保持 | 可能 | 不可能 |
テスト用のマッチャ | あり | なし |
以下、かいつまんで見ていきます。
値の更新
enum
は bang メソッドで更新できるのでちょっと短く書けて Ruby ぽいです。
user.membership = :regular # Enumerize
user.regular! # enum
デフォルト値の設定
Enumerize は enumeration の宣言部分でデフォルト値をオプションとして渡すことができます。
class User
extend Enumerize
enumerize :membership, in: [:free, :regular, :premium], default: :free
end
一方、enum
ではマイグレーションの中でデフォルト値を設定する必要があります。
class AddMembershipToUsers < ActiveRecord::Migration
def change
add_column :users, :memebership, :integer, default: 0
end
end
predicate, scope メソッドの生成
Enumerize は enumerize の宣言部分で predicate と Active Record の scope メソッドを生成するオプションを渡せます。scope は名前も変えられます。
class User < ActiveRecord::Base
extend Enumerize
enumerize :membership, in: [:free, :regular, :premium], predicates: true, scope: true
end
user.regular? # predicate
User.with_membership(:premium) # premium user だけ取得
# 自分で scope 名を指定できる
class User < ActiveRecord::Base
extend Enumerize
enumerize :membership, in: [:free, :regular, :premium], scope: :having_membership
end
User.having_membership(:free) # free user だけ取得
一方、enum
は自動で predicate, scope の両メソッドを生成してくれています。ただし scope の名前は変えられません。そのぶんシンプルともいえます。
class User < ActiveRecord::Base
enum membership: [:free, :regular, :premium]
end
user.regular? # predicate
User.premium # premium user 取得
プレフィックス/サフィックス生成
Enumerize は enumeration の宣言部分で predicate メソッド生成時に prefix
, suffix
オプションを渡すと、predicate メソッドのプレフィックス、サフィックスを生成できます。
class User
extend Enumerize
enumerize :membership, in: [:free, :regular, :premium], predicates: { prefix: true }
end
user.membership_free?
# 自分でプレフィックス名を指定できる
class User
extend Enumerize
enumerize :membership, in: [:free, :regular, :premium], predicates: { prefix: 'member' }
end
user.member_free?
enum
にも Rails 5.0 からプレフィックス/サフィックス生成機能が入りました。_prefix
, _suffix
というアンダースコア付きのオプションを渡すと predicate, 更新メソッドの両方にプレフィックス/サフィックスを生成します。
class User < ActiveRecord::Base
enum membership: [:free, :regular, :premium], _prefix: true
end
user.membership_free! # :free に更新
# 自分でプレフィックスを指定できる
class User < ActiveRecord::Base
enum membership: [:free, :regular, :premium], _prefix: :member
end
user.member_premium?
複数値の保持
Enumerize は複数の値を保持できます。つまり、次のように、multiple
オプションを渡すと、ある enumeration について複数状態を持てます。
require 'active_support/core_ext/object/blank' # << の中で blank? 使っているので必要
class User
extend Enumerize
enumerize :subscriptions, in: [:newspaper, :magazine, :podcast], multiple: true
end
user.subscriptions << :newspaper
user.subscriptions << :podcast
user.subscriptions #=> #<Enumerize::Set {newspaper, podcast}>
テスト用のマッチャ
Enumerize では RSpec などのマッチャが使えます。
describe User do
it { is_expected.to enumerized(:membership) }
end
まとめ
Enumerize のほうが ActiveRecord::Enum
よりまだ機能は多いということがわかりました。とくに、Enumerize にはデフォルトの i18n 対応、デフォルト値設定や scope 名設定の柔軟さがあります。独立した gem であることから、プレーン Ruby オブジェクトや Mongoid オブジェクトで使えるようにもなっています。
一方、Rails 4.1 以降では、ActiveRecord::Enum
で Rails の機能の一部として enumeraion の基本機能や predicate や scope の生成と言った機能が使えるようになっていることがわかりました。プロジェクトによっては、これぐらいのシンプルさで十分ということもあると思います。Rails 5.0 へのアップデートが必要になるものの、_prefix
, _suffix
も使えるようになっていました。
興味を持った方は下記の公式ドキュメントをご参照ください。