Edited at

Ruby (on Rails) で使える enumeration 実装を比較してみた

More than 1 year has passed since last update.

去年の社のアドベントカレンダーで書いた記事をこちらでも共有。


Ruby, とくに Rails で使える enumeration 実装


  • Enumerize

  • ActiveRecord::Enum

の機能を比較してみました。

以下のコード例では require 'enumerize' しているものとします。また、userUser のインスタンスとします。


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 も使えるようになっていました。

興味を持った方は下記の公式ドキュメントをご参照ください。