投稿日:2026/8/13
更新日:2026/8/13

コードを読んでいて audience_reader? という見慣れないメソッドに出くわす。定義元を探そうと git grep "def audience_reader?" を叩いても 0 件。それでも動いている。
これは enum が動的に生やしたメソッドで、しかも prefix: true によって名前が変形されている。
enum の値ごとに、述語メソッド・バング更新メソッド・スコープが自動生成される。def は書かれていないので grep では見つからないprefix: true を付けると、属性名がメソッド名の頭に付く(reader? → audience_reader?)method(:名前).owner と source_location を使うブログサービスのメール配信ログを考える。配信対象は「読者・執筆者・編集者」の 3 種類。
class MailDelivery < ApplicationRecord
belongs_to :reader, optional: true
belongs_to :writer, optional: true
belongs_to :editor, optional: true
enum :audience, {
reader: 0, # 読者宛
writer: 1, # 執筆者宛
editor: 2, # 編集者宛
}, prefix: true
end
mail_deliveries
| id | audience | reader_id | writer_id | editor_id |
|---|---|---|---|---|
| 1 | 0 | 1 | NULL | NULL |
| 2 | 1 | NULL | 3 | NULL |
| 3 | 2 | NULL | NULL | 7 |
DB に入っているのは 0 / 1 / 2 という整数だけ。名前は Ruby 側のマッピングでしかない。
enum は 1 行の定義から、値の数だけメソッドを作る。
flowchart LR
E["enum :audience<br/>prefix: true"]
E --> I["インスタンスメソッド<br/>audience_reader?<br/>audience_reader!"]
E --> S["スコープ<br/>MailDelivery.audience_reader<br/>MailDelivery.not_audience_reader"]
E --> C["クラスメソッド<br/>MailDelivery.audiences<br/>マッピング Hash を返す"]
実際に呼ぶとこうなる。
delivery = MailDelivery.find(1)
delivery.audience # => "reader" 値そのもの(文字列)
delivery.audience_reader? # => true 述語
delivery.audience_writer? # => false
delivery.audience_writer! # audience を writer に更新して保存
MailDelivery.audience_reader # WHERE audience = 0
MailDelivery.not_audience_reader # WHERE audience != 0
MailDelivery.audiences # => {"reader"=>0, "writer"=>1, "editor"=>2}
複数形のクラスメソッド(audiences)だけは prefix の影響を受けず、常に属性名の複数形になる。使い道は enum の値を範囲で絞る(statuses と終端なし Range) を参照。
not_◯◯スコープは Rails 6.0 以降。それ以前はwhere.notを自分で書く。
同じ enum :audience, { reader: 0, ... } に対して、オプションだけを変えた比較。
| 指定 | 述語メソッド | スコープ |
|---|---|---|
| なし(デフォルト) | reader? |
MailDelivery.reader |
prefix: true |
audience_reader? |
MailDelivery.audience_reader |
suffix: true |
reader_audience? |
MailDelivery.reader_audience |
prefix: :sent_to |
sent_to_reader? |
MailDelivery.sent_to_reader |
suffix: :target |
reader_target? |
MailDelivery.reader_target |
true を渡すと属性名が使われるprefix と suffix は同時指定もできる(prefix: true, suffix: :target → audience_reader_target?)冒頭のモデルは belongs_to :reader を持っている。ここで prefix なしの enum を定義すると、こうなる。
delivery.reader # => #<Reader id: 1> 関連レコード
delivery.reader? # => true audience が reader かどうか
delivery.reader! # audience を reader に更新して保存(!)
? が付くかどうかで、まったく別の話になる。 特に reader! は「関連を取ってくる強い版」に見えて、実際は audience カラムを書き換えて save! する破壊的更新。レビューで見落とすと事故になる。
prefix: true を付ければ、名前の時点で二者が分離される。
delivery.reader # 関連
delivery.audience_reader? # enum の判定
delivery.audience_reader! # enum の更新(何を更新するか名前に出ている)
flowchart TD
subgraph NG["prefix なし"]
A1["delivery.reader"] --> A2["関連レコード"]
A3["delivery.reader?"] --> A4["audience の判定"]
A5["delivery.reader!"] --> A6["audience を更新して保存"]
end
subgraph OK["prefix: true"]
B1["delivery.reader"] --> B2["関連レコード"]
B3["delivery.audience_reader?"] --> B4["audience の判定"]
B5["delivery.audience_reader!"] --> B6["audience を更新して保存"]
end
こちらは読みにくさでは済まず、起動時にエラーになる。
class Reader < ApplicationRecord
enum :mail_policy, { none: 0, important_only: 1, all: 2 }
end
ArgumentError: You tried to define an enum named "mail_policy" on the model "Reader",
but this will generate a class method "none", which is already defined by Active Record.
Model.none(空のリレーションを返す ActiveRecord のクラスメソッド)とスコープ名がぶつかっている。all も同様。prefix: true にすれば Reader.mail_policy_none になるので回避できる。
enum :mail_policy, { none: 0, important_only: 1, all: 2 }, prefix: true
reader.mail_policy_none? # => メール配信をすべて止めている
enum のキーに
none/all/first/last/newのような一般語を使うなら、prefix はほぼ必須。
Rails は「ActiveRecord::Base 自身が定義しているメソッド」と「他の enum が定義したメソッド」との衝突は検知してくれるが、関連が定義したメソッドとの衝突は検知しない(理由 1 がエラーにならないのはこのため)。名前の設計は自分でやる必要がある。
def audience_reader? はどこにも書かれていない。enum の内部で define_method によって、モデルに include される無名モジュールに定義されるため。
flowchart TD
Call["delivery.audience_reader?"] --> M1["MailDelivery<br/>モデルのソースに def は無い"]
M1 -->|継承チェーンを上る| M2["enum が作った無名モジュール<br/>define_method で動的に定義"]
M2 --> M3["ApplicationRecord"]
M3 --> M4["ActiveRecord::Base"]
なので、探すなら実行時に聞くのが早い。
m = MailDelivery.instance_method(:audience_reader?)
m.owner # => #<Module:0x00007f...> ← enum の無名モジュール
m.source_location # => [".../activerecord/lib/active_record/enum.rb", 240]
owner が無名モジュール(#<Module:0x...>)になっていたら、まず動的定義を疑う。source_location が gem の中を指していれば確定。
エディタの定義ジャンプが効かないのも同じ理由なので、「ジャンプできない = 存在しない」ではない。
grep するときは両方を疑う。
# Rails 7.0 以降
enum :audience, { reader: 0, writer: 1 }, prefix: true
# Rails 6 以前の書き方(7.x でも動く)
enum audience: { reader: 0, writer: 1 }, _prefix: true
オプション名が prefix と _prefix で違う。新記法で _prefix: と書いても効かないので、移行時に静かに壊れる。
# 定義側を探すならこう
git grep -n "enum :audience\|enum audience:"
prefix: true を後から足すと、reader? を使っている箇所がすべて NoMethodError になる。逆も同じ。テストが薄いところは実行時まで気づけないので、変えるなら呼び出し側を一括で追う。
git grep -n "audience_reader\|\breader?"
audience_reader? が見るのはメモリ上の属性。代入しただけで保存していなくても true を返す。
delivery.audience = :reader
delivery.audience_reader? # => true(まだ DB は更新されていない)
audience_writer! は update!(audience: :writer) 相当。バリデーションに引っかかれば例外になる。「判定のつもりで ! を付けた」が一番危ない事故。
case audience when ... end が増えてきたら、enum ではなく別の設計を検討する合図。→ STI(単一テーブル継承)を3種類の通知で理解する
enum は値ごとに ◯◯? / ◯◯! / スコープを動的に生成する。def は存在しないので grep では出てこないprefix: true は生成メソッド名の頭に属性名を付ける。prefix: :任意名 で文字列指定も可none / all など ActiveRecord の既存メソッドとの衝突(起動時に ArgumentError)method(:名前).owner と .source_location で追う。owner が無名モジュールなら動的定義