YASD-TECH
YASD TECH
# Rails

enum が生成するメソッドと prefix・suffix(grep しても定義が見つからない理由)

投稿日:2026/8/13

更新日:2026/8/13

ttitleImage

enum が生成するメソッドと prefix・suffix(grep しても定義が見つからない理由)

コードを読んでいて audience_reader? という見慣れないメソッドに出くわす。定義元を探そうと git grep "def audience_reader?" を叩いても 0 件。それでも動いている。

これは enum が動的に生やしたメソッドで、しかも prefix: true によって名前が変形されている。

結論

  • enum の値ごとに、述語メソッド・バング更新メソッド・スコープが自動生成される。def は書かれていないので grep では見つからない
  • prefix: true を付けると、属性名がメソッド名の頭に付くreader?audience_reader?
  • prefix を付ける理由は「同名の関連やメソッドと並べたときに読めなくなる/衝突する」から
  • 定義元を追いたいときは grep ではなく method(:名前).ownersource_location を使う

最小の例

ブログサービスのメール配信ログを考える。配信対象は「読者・執筆者・編集者」の 3 種類。

ruby
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 が生成するもの

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 を返す"]

実際に呼ぶとこうなる。

ruby
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 を自分で書く。


prefix・suffix でメソッド名がどう変わるか

同じ 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 を渡すと属性名が使われる
  • シンボル/文字列を渡すとその文字列が使われる
  • prefixsuffix は同時指定もできる(prefix: true, suffix: :targetaudience_reader_target?

なぜ prefix を付けるのか

理由 1:関連名と並ぶと読めなくなる

冒頭のモデルは belongs_to :reader を持っている。ここで prefix なしの enum を定義すると、こうなる。

ruby
delivery.reader    # => #<Reader id: 1>   関連レコード
delivery.reader?   # => true              audience が reader かどうか
delivery.reader!   # audience を reader に更新して保存(!)

? が付くかどうかで、まったく別の話になる。 特に reader! は「関連を取ってくる強い版」に見えて、実際は audience カラムを書き換えて save! する破壊的更新。レビューで見落とすと事故になる。

prefix: true を付ければ、名前の時点で二者が分離される。

ruby
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

理由 2:Rails / Ruby の既存メソッドと本当に衝突する

こちらは読みにくさでは済まず、起動時にエラーになる

ruby
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 になるので回避できる。

ruby
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"]

なので、探すなら実行時に聞くのが早い。

ruby
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 の中を指していれば確定。

エディタの定義ジャンプが効かないのも同じ理由なので、「ジャンプできない = 存在しない」ではない。


ハマりどころ

記法が 2 種類ある

grep するときは両方を疑う。

ruby
# 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: と書いても効かないので、移行時に静かに壊れる。

bash
# 定義側を探すならこう
git grep -n "enum :audience\|enum audience:"

prefix の付け外しは全呼び出し箇所に波及する

prefix: true を後から足すと、reader? を使っている箇所がすべて NoMethodError になる。逆も同じ。テストが薄いところは実行時まで気づけないので、変えるなら呼び出し側を一括で追う。

bash
git grep -n "audience_reader\|\breader?"

述語は「保存済みの値」を見ていない

audience_reader? が見るのはメモリ上の属性。代入しただけで保存していなくても true を返す。

ruby
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: :任意名 で文字列指定も可
  • prefix を付ける動機は、関連名との紛らわしさ(エラーにはならない)と、none / all など ActiveRecord の既存メソッドとの衝突(起動時に ArgumentError)
  • 定義元は method(:名前).owner.source_location で追う。owner が無名モジュールなら動的定義

参考

Index

  • enum が生成するメソッドと prefix・suffix(grep しても定義が見つからない理由)
  • 結論
  • 最小の例
  • enum が生成するもの
  • prefix・suffix でメソッド名がどう変わるか
  • なぜ prefix を付けるのか
  • 理由 1:関連名と並ぶと読めなくなる
  • 理由 2:Rails / Ruby の既存メソッドと本当に衝突する
  • 定義元の探し方
  • ハマりどころ
  • 記法が 2 種類ある
  • prefix の付け外しは全呼び出し箇所に波及する
  • 述語は「保存済みの値」を見ていない
  • バング版は保存する
  • 種類ごとに振る舞いを変えたくなったら
  • まとめ
  • 参考