🔗
Railsで自己結合(同一テーブル)アソシエーションを実装する
こんにちは。
株式会社スペースマーケットでエンジニアをやっているtchmrです。
2023年も残るところ僅かとなってきました。
今年は公私ともにいろいろなチャレンジをしてとても刺激的な一年でしたが、来年も変化に富んだ楽しい日々を過ごしていきたいと思います。
本記事の内容
AテーブルとBテーブルでアソシエーションを定義するのがオーソドックスですが、本記事では同一テーブル内でアソシエーションを定義する方法を見ていきたいと思います。
実現したいこと
以下の要件を実現するための一環としてレンタルスペースのホストに親子の概念を持たせたいという要件があるとします。
- 子ホストは自身のスペースのみを管理することができる。
- 親ホストは自身に加えて子ホストのスペースを管理することができる。
実現方法
結論だけ知りたいという方はここだけご覧ください。
owners
id | name |
---|---|
1 | 親ホスト |
2 | 子ホストA |
3 | 子ホストB |
owner_relations
id | parent_owner_id | child_owner_id |
---|---|---|
1 | 1 | 2 |
2 | 1 | 3 |
アソシエーション実装
# app/model/owner.rb
class Owner < ApplicationRecord
has_many :child_owner_relations, class_name: 'OwnerRelation', foreign_key: 'parent_owner_id'
has_one :parent_owner_relation, class_name: 'OwnerRelation', foreign_key: 'child_owner_id'
has_many :child_owners, through: :child_owner_relations, source: :child_owner
has_one :parent_owner, through: :parent_owner_relation, source: :parent_owner
end
Owner.find(1).child_owners.ids
=> [2,3]
Owner.find(2).parent_owner.id
=> 1
解説・考察
アソシエーションの各オプションの意味について(ざっくり)
- class_name: アソシエーション名が関連モデル名と異なる場合に検索するモデル(テーブル)を明示的に指定する
- source: 多対多の関連先のモデルを指定する
- foreign_key: アソシエーション名が関連モデル名と異なる場合に外部キーを明示的に指定する
- through: 中間テーブルのモデルを経由する際に指定する(結合が実行される)
発行されるSQL
Owner.find(1).child_owners
# SELECT `owners`.* FROM `owners` INNER JOIN `owner_relations`
# ON `owners`.`id` = `owner_relations`.`child_owner_id`
# WHERE `owner_relations`.`parent_owner_id` = 1
Owner.find(2).parent_owner
# SELECT `owners`.* FROM `owners` INNER JOIN `owner_relations`
# ON `owners`.`id` = `owner_relations`.`parent_owner_id`
# WHERE `owner_relations`.`child_owner_id` = 2 LIMIT 1
外部キーや結合キーなどをオプションで指定できていることが見て取れますね。
ちなみに、よく定義するhas_many :spaces
のようなシンプルなアソシエーションはアソシエーション名が関連モデル名(Railsのルール通り)なのでclass_name
やforeign_key
が省略されています。
# app/model/owner.rb
class Owner < ApplicationRecord
has_many :spaces
# 以下と同義
# has_many :spaces, class_name: 'Space', foreign_key: 'owner_id'
end
まとめ
同一モデル内でアソシエーションを設定する方法について見てきました。
いろいろなオプションが出てきてそこそこ複雑になりやすいので、実装時は発行されるクエリを確認しながらアソシエーションを定義していくとやりやすいかなと思います。
スペースを簡単に貸し借りできるサービス「スペースマーケット」のエンジニアによる公式ブログです。 弊社採用技術スタックはこちら -> whatweuse.dev/company/spacemarket
Discussion