YASD-TECH
YASD TECH
# Rails

inverse_of はいつ必要か(双方向関連の自動推測)

投稿日:2026/8/5

更新日:2026/8/5

ttitleImage

inverse_of はいつ必要か(双方向関連の自動推測)

inverse_of: は「この関連の裏返しは、相手モデルのこの関連だよ」と手で教える指定。
普段は Rails が名前から自動推測してくれるので書かなくていいが、推測が効かない条件があり、そこだけ明示が要る。

「スコープを書いたら inverse_of もセット」と覚えている人が多いが、Rails 7.0 でそのルールは変わっている。

結論

  • inverse_of を書かないと、親子が別インスタンスになる(無駄な SELECT が飛ぶ・変更が同期しない・未保存の親に build した子が落ちる)。
  • :through:foreign_key を指定した関連は、バージョンを問わず自動推測されない。ここは常に明示。
  • スコープ付き(-> { ... })は Rails 6.1 以前は自動推測されない。Rails 7.0 以降は config.load_defaults 7.0 以上なら推測される。
  • 古いアプリからのアップグレードは load_defaults が上がっていないことがあるので、迷ったら書く。書いても害はない。

何をしている指定か

プロジェクトとタスクの1対多で見る。

ruby
class Project < ApplicationRecord
  has_many :tasks,
           -> { order(position: :asc) },
           dependent: :destroy,
           inverse_of: :project   # ← Task 側の belongs_to :project を指している
end

class Task < ApplicationRecord
  belongs_to :project
end

inverse_of: :projectTask の関連名を書く。DB の話は一切していない。メモリ上のオブジェクトを繋いでよいかを Rails に教えているだけ。

flowchart LR
    subgraph mem["メモリ上"]
        P["project<br/>(Project インスタンス)"]
        T["task<br/>(Task インスタンス)"]
    end
    P -->|"project.tasks"| T
    T -->|"task.project<br/>← これを P に繋ぐのが inverse_of"| P

書かないと何が起きるか

① 無駄なクエリが飛ぶ

ruby
project = Project.first
task    = project.tasks.first

task.project
# inverse_of あり → メモリ上の project をそのまま返す(クエリ 0 回)
# inverse_of なし → SELECT * FROM projects WHERE id = ... LIMIT 1

一覧をループで回すと、そのままタスク件数ぶんの N+1 になる。

② オブジェクトの同一性が崩れる

同じ行なのに別インスタンスになるので、片方への変更がもう片方に見えない。

ruby
project.name = "改名後"

task.project.name          # あり → "改名後" / なし → "改名前"
task.project.equal?(project)  # あり → true    / なし → false

これは「保存したのに反映されない」系のバグとして出てくる。デバッグ時に最も気づきにくいのがこれ。

③ 未保存の親に子を積むと落ちる

新規の親に build して一括保存するパターン。

ruby
project = Project.new(name: "新規プロジェクト")
task    = project.tasks.build(title: "最初のタスク")

task.project   # あり → project(未保存でも辿れる) / なし → nil
project.save   # なし → Task の "Project must exist" で失敗

belongs_to は Rails 5 以降デフォルトで必須。親がまだ id を持たない時点で子のバリデーションが走るため、FK ではなくオブジェクト参照で親を見つけられないと落ちる。逆に inverse_of が効いていれば、関連経由で new / build した子は最初から親を持っている。

ruby
# フォームオブジェクトなどでの典型
record = project.tasks.new   # ← この時点で record.project が入る

自動推測のルール

Rails は has_many :tasks から「相手は Taskproject だろう」と、クラス名を単数スネークケースにした名前で探しに行く。当たれば自動で繋ぐ。

判定は ActiveRecord::Reflectioncan_find_inverse_of_automatically? に集約されている。

ruby
def can_find_inverse_of_automatically?(reflection, inverse_reflection = false)
  reflection.options[:inverse_of] != false &&
    !reflection.options[:through] &&
    !reflection.options[:foreign_key] &&
    scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
end
flowchart TD
    Start["has_many / belongs_to を辿る"] --> A{"inverse_of: false ?"}
    A -->|"yes"| NG["自動推測しない"]
    A -->|"no"| B{":through がある ?"}
    B -->|"yes"| NG
    B -->|"no"| C{":foreign_key がある ?"}
    C -->|"yes"| NG
    C -->|"no"| D{"スコープがある ?"}
    D -->|"no"| OK["名前から推測して繋ぐ"]
    D -->|"yes"| E{"automatic_scope_inversing<br/>が true ?"}
    E -->|"yes"| OK
    E -->|"no"| NG
条件 自動推測 明示が要るか
素の has_many :tasks される 不要
-> { order(...) } 付き Rails 7.0+設定次第 迷ったら書く
foreign_key: :owner_id 指定 されない 必須
has_many :xxx, through: :yyy されない そもそも inverse_of は使えない
class_name だけ変更 される(as/クラス名から解決) 不要
inverse_of: false しない(明示的な無効化)

:foreign_key を書いた瞬間に自動推測が切れるのは見落としやすい。FK をカスタムしている関連は全部疑ってよい。


スコープ付きが Rails 7.0 で変わった

かつては「スコープが付いた関連は自動推測されない」で一律だった。理由は、スコープが親自身を除外してしまう可能性があるから。

ruby
# 相手側のスコープが親を弾くと、繋いだ結果が嘘になる
has_many :tasks, -> { where(archived: false) }

Rails 7.0 で config.active_record.automatic_scope_inversing が入り、判定が2方向に分かれた。

ruby
def scope_allows_automatic_inverse_of?(reflection, inverse_reflection)
  if inverse_reflection
    !reflection.scope                                        # 相手側にスコープ → 常に NG
  else
    !reflection.scope || reflection.klass.automatic_scope_inversing  # 自分側 → 設定次第
  end
end
  • 自分側のスコープProject.has_many :tasks, -> { ... } から task.project を繋ぐ方向)は、設定が true なら許可される。
  • 相手側のスコープTask.belongs_to :project の裏返しを探して has_many にスコープがある方向)は、今も一律で NG。

automatic_scope_inversingconfig.load_defaults 7.0 以上で true になる。新規アプリは有効だが、古いアプリを上げてきた場合は load_defaults が 6.x のままということがよくある。

ruby
# 現物で確認する
Task.automatic_scope_inversing                          # => true / false
Project.reflect_on_association(:tasks).inverse_of&.name # => :project なら繋がっている / nil なら繋がっていない

reflect_on_association(...).inverse_ofnil なら、そのまま上の①〜③を踏む。コンソールで1行叩けば確定するので、悩む前にこれを見る。


何を書くか迷ったら

コストは「1行増える」だけで、書いて壊れることはない(inverse_of: false と違い、正しい関連名を書く限り挙動は自動推測時と同じ)。判断は次で足りる。

状況 方針
:through / :foreign_key 付き 必ず書く(:through は書けないので設計側で対処)
スコープ付き 書く。設定に依存させない
素の関連 書かない。ノイズになる
既存コードが書いている 揃える。同じファイル内で有無が混ざる方が読みにくい

ハマりどころ

  • inverse_of は関連名を書く。クラス名ではない。 inverse_of: :Projectinverse_of: :projects は解決に失敗する(belongs_to :project なら :project)。
  • has_many :through には指定できない。 中間を経由するので裏返しが一意に決まらない。N+1 が気になるなら preload 側で対処する(eager_load・preload・includesの違いと使い分け)。
  • ポリモーフィック関連は as: の名前で推測される。 has_many :comments, as: :commentable なら相手の belongs_to :commentable が自動で見つかる。
  • inverse_of: false は「無効化」であって「未指定」ではない。 意図的に切りたいとき以外は書かない。
  • dependent: とは無関係。 同じ行に並ぶので混同しやすいが、dependent: は DB の削除の話、inverse_of: はメモリ上の参照の話。

関連

参考

Index

  • inverse_of はいつ必要か(双方向関連の自動推測)
  • 結論
  • 何をしている指定か
  • 書かないと何が起きるか
  • ① 無駄なクエリが飛ぶ
  • ② オブジェクトの同一性が崩れる
  • ③ 未保存の親に子を積むと落ちる
  • 自動推測のルール
  • スコープ付きが Rails 7.0 で変わった
  • 何を書くか迷ったら
  • ハマりどころ
  • 関連
  • 参考