基本はhas_manyとbelongs_to
User/Relationshipの関連付けは、以下の条件が基本となります。
- 1人のユーザーには1対多(
has_many)のリレーションシップが存在する - 当該リレーションシップには、フォローしているユーザーとフォローされているユーザー両方が属する(
belongs_to)
最終的には、「userとactive_relationshipに関して以下のようなメソッドが使えるようになる」というのが完成像です。
active_relationship.useruser.active_relationshipsuser.active_relationships.create(followed_id: ...)user.active_relationships.create!(followed_id: ...)user.active_relationships.build(followed_id: ...)user.active_relationships.find_by(id: 1)
「モデル名と関係名が異なる」という要件
しかしながら、今回は「Relationshipモデルに対してactive_relationship(ならびにpassive_relationship)という関係性を定義する」というのが要件です。1対多の関係の多側において、モデル名と関係名が異なります。
「モデル名と関係名が異なる」という要件の存在ゆえに、Userモデルにおけるhas_many、およびRelationshipモデルにおけるbelongs_toの記法は、モデル名と関係名が同じ場合とは異なります。
1対多の1側において、多側のモデル名と関係名が異なる場合のhas_manyの書き方
まずはUserモデルにおけるhas_manyの書き方についてです。
- has_many :active_relationships
上記のような書き方では、active_relationshipsという関係性を正しく定義できません。「ActiveRelationshipモデルを参照しようとしてしまい、Relationshipモデルを参照できない」ためです。「Relationshipモデルを参照する」ということを、has_manyに明確に渡さなければなりません。
「特定のモデルを参照する」ということをhas_manyに渡すためには、class_nameというオプションハッシュに、当該モデルのクラス名を文字列として渡せばOKです。Relationshipモデルのクラス名は"Relationship"なので、今回の事例の場合、has_manyの書き方は以下のようになります。
has_many :active_relationships, class_name: "Relationship"
1対多の多側において、外部キー名と対応するモデル名が異なる場合のbelongs_toの書き方
続いてはRelathionshipモデルにおけるbelongs_toの書き方についてです。前提条件は以下です。
-
active_relationshipという関係性は、relationshipsテーブルのfollowed_idカラムに紐付けしたい -
followed_idは外部キーであり、その参照先はUserモデルのidカラムである
このような関係性をRelationshipモデルに定義したい場合、belongs_toの書き方は以下のようになります。
belongs_to :followed, class_name: "User"
Relationshipモデルのfollowed_idに関係性を紐付けるので、第1引数に与えるシンボルは:followedとなります。
オプションハッシュclass_nameに、参照先モデルのクラス名を文字列として与えています。Userモデルのクラス名はUserであるため、今回だと"User"という文字列である…という次第ですね。
上記を踏まえ、UserモデルおよびRelationshipモデルに必要な変更の全体像はどうなるか
Userモデルに必要な変更の全体像
class User < ApplicationRecord
has_many :microposts, dependent: :destroy
+ has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
...略
end
第1引数の:active_relationshipsとclass_nameオプション、foreign_keyオプションの説明は上記のとおりです。ユーザーが削除されたら関連するリレーションシップも削除されるべきであるので、dependent: :destroyというオプションを与えています。
Relationshipモデルに必要な変更の全体像
class Relationship < ApplicationRecord
+ belongs_to :follower, class_name: "User"
+ belongs_to :followed, class_name: "User"
end
belongs_to :followerのほうは、あとで実装する「受動的関係(passive_relationship)で用いる準備としての実装であり、現時点で直ちに使うものではありません。
これらの実装により使えるようになるメソッド
ここまでの実装により、active_relationshipsに関する以下のメソッドを使えるようになります。
| メソッド | 用途 |
|---|---|
active_relationship.follower |
フォロワーを返す |
active_relationship.followed |
フォローしているユーザーを返す |
user.active_relationships.create(followed_id: other_id) |
userと紐付いた能動的関係を作成し、RDBに保存する(失敗時にnilを返す) |
user.active_relationships.create!(followed_id: other_id) |
userと紐付いた能動的関係を作成し、RDBに保存する(失敗時にエラーを返す) |
user.active_relationships.build(followed_id: other_id) |
userと紐付いた新しいRelationshipオブジェクトを返す(この時点ではRDBには保存されない) |