Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails APIドキュメント: ActiveRecord::Enum(翻訳)

概要

MITライセンスに基づいて翻訳・公開いたします。

参考: 17 enum -- Active Record クエリインターフェイス - Railsガイド

なお、Rails 8.0ではenumをキーワード引数で定義する機能(Rails 7.2で非推奨化)が削除されました。

参考: Remove deprecated support for defining enum with keyword arguments · rails/rails@b9226a6
参考: Deprecate defining enums with keywords args by skipkayhil · Pull Request #50987 · rails/rails

Rails APIドキュメント: ActiveRecord::Enum(翻訳)

enum属性を宣言します。このenum属性は、データベースのinteger型の値に対応付けられますが、以下の例のようにenum名を指定してクエリを実行できます。

class Conversation < ActiveRecord::Base
  enum :status, [ :active, :archived ]
end
# conversation.update! status: 0
conversation.active!
conversation.active? # => true
conversation.status  # => "active"

# conversation.update! status: 1
conversation.archived!
conversation.archived? # => true
conversation.status    # => "archived"

# conversation.status = 1
conversation.status = "archived"

# (nilも設定可能)
conversation.status = nil
conversation.status.nil? # => true
conversation.status      # => nil

enumフィールドで許可されている値に基づいたスコープも提供されます。上の例の場合、以下のスコープが利用可能です。

Conversation.active
Conversation.not_active
Conversation.archived
Conversation.not_archived

もちろん、提供されるスコープではニーズを満たせない場合は、以下のように直接クエリを実行することも可能です。

Conversation.where(status: [:active, :archived])
Conversation.where.not(status: :active)

enumのスコープ定義は、:scopesオプションをfalseに設定することで無効にできます。

class Conversation < ActiveRecord::Base
  enum :status, [ :active, :archived ], scopes: false
end

モデルで以下のように:defaultオプションを指定することで、enum値のデフォルト値を設定できます。

class Conversation < ActiveRecord::Base
  enum :status, [ :active, :archived ], default: :active
end
conversation = Conversation.new
conversation.status # => "active"

属性とデータベースのinteger値の対応関係は、以下のようにハッシュ形式で明示的に指定できます。

class Conversation < ActiveRecord::Base
  enum :status, active: 0, archived: 1
end

最後に、列挙した値を以下のようにstringカラムで永続化することも可能です。ただし、この場合はデータベースクエリが遅くなる可能性があります。

class Conversation < ActiveRecord::Base
  enum :status, active: "active", archived: "archived"
end

ここで注意すべきは、enum定義で配列を使う場合、値とデータベースのinteger型の暗黙の対応付けは、配列内での出現順によって導出される点です。
上述の例では、:activeは配列の第1要素なので0に対応付けられ、:archivedは配列の第2要素なので1に対応付けられるといった具合です。一般に、配列のi番目の要素はデータベースのi-1番目の要素に対応付けられます。

すなわち、enum配列に値を追加するときは、配列内での出現順序を変えないよう注意しなければなりません。また、新しい値の追加場所は、enum配列の末尾に限定しておくべきです。
使われなくなった値を削除するときは、明示的にハッシュ構文を使う必要があります。

まれに、対応付けに直接アクセスする必要が生じることがあります。この対応付けは、以下のように属性名を複数形にしたクラスメソッド経由で公開されます。この対応付けはActiveSupport::HashWithIndifferentAccessクラスとして返されます。

Conversation.statuses[:active]    # => 0
Conversation.statuses["archived"] # => 1

enumの序数値(enum内で何番目にあるかという値)を知る必要がある場合は、このクラスメソッドを使います。たとえば、SQLクエリ文字列を手動でビルドするときに以下のような形で利用できます。

Conversation.where("status <> ?", Conversation.statuses[:archived])

複数のenumで同じ値を定義しなければならなくなった場合は、:prefixオプションや:suffixオプションを利用できます。:prefixオプションにtrueを指定すると、メソッド名にenum名がプレフィックスされ、:suffixオプションにtrueを指定すると、メソッド名にenum名がサフィックスされます。

class Conversation < ActiveRecord::Base
  enum :status, [ :active, :archived ], suffix: true
  enum :comments_status, [ :active, :inactive ], prefix: :comments
end

上の例では、enumに関連するスコープの!付きのメソッドや?付きの述語メソッドに、enum名が以下のようにサフィックス(またはプレフィックス)されます。

# statusがサフィックスされる
conversation.active_status!
conversation.archived_status? # => false

# commentsがプレフィックスされる
conversation.comments_inactive!
conversation.comments_active? # => false

モデルでインスタンスメソッドが自動生成されないようにしたい場合は、:instance_methodsオプションにfalseを指定できます。

class Conversation < ActiveRecord::Base
  enum :status, [ :active, :archived ], instance_methods: false
end

enum値を保存する前に必ずバリデーションしたい場合は、以下のように:validateオプションを指定します。

class Conversation < ActiveRecord::Base
  enum :status, [ :active, :archived ], validate: true
end
conversation = Conversation.new

conversation.status = :unknown
conversation.valid? # => false

conversation.status = nil
conversation.valid? # => false

conversation.status = :active
conversation.valid? # => true

以下のように:validateオプションに追加のバリデーションオプションを渡すこともできます。

class Conversation < ActiveRecord::Base
  enum :status, [ :active, :archived ], validate: { allow_nil: true }
end
conversation = Conversation.new

conversation.status = :unknown
conversation.valid? # => false

conversation.status = nil
conversation.valid? # => true

conversation.status = :active
conversation.valid? # => true

validate: { allow_nil: true }オプションを指定しないと、以下のようにArgumentErrorが発生します。

class Conversation < ActiveRecord::Base
  enum :status, [ :active, :archived ]
end
conversation = Conversation.new

conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError)

🔗 enum(name, values = nil, **options)

ソースコード: GitHub

関連記事

Rails API: ActiveSupport::CurrentAttributes(翻訳)

Rails API: ActiveModel::Dirty(翻訳)

Rails API: ActiveRecord::AutosaveAssociation(翻訳)

Rails API: ActiveRecord::NestedAttributes(翻訳)

Rails API: ActiveSupport::ConcernとModule::Concerning(翻訳)

Rails APIドキュメント: Active Recordのトランザクション(翻訳)

Rails 5.1〜7.2: 'form_with' APIドキュメント(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。