何をしたか
Railsの課題を実施しています。User
のフォロー機能を実装することになりました。
この機能は前に実装したことはあって、**「確かあのアソシエーションが難しいやつ」**ぐらいには覚えていました。
たまたま、この機能の実装を人に教える機会もありそうだったので、しっかり教えられるように手順を細かく記すことにしました。
ただ、かなりの長文になってしまったので前後編に分けています。この記事ではDB設計〜アソシエーションまでの話をして、続きは下記の記事になります。
Railsでユーザーフォロー機能を実装する(Ajax使うよ)②
なお、実行環境は以下の通りです。
Rails 5.2.3
Ruby 2.6.0
ゴール
今回、作るものはユーザーのフォロー機能です。ボタンを押すと、ユーザーをフォロー、もう一度ボタンを押すと、フォロー解除できます。
また、この機能のために作成したモデルは下記の通りです。
謎すぎる形態をしていますよね
いきなり、上記のモデルに行き着くのは難しいので、ひとつひとつ分解して考えていきます。
なお、以下はあくまでも**「私はこう考えた」**考え方ですので、人によっては他の考え方の方がしっくりくるかもしれません。
実装
分解して考える
まずは、「ユーザー」と「フォロワー」という登場人物を作った方がわかりやすいので、
User
モデルを**(頭の中で)**User
モデルとFollower
モデルに分解します。
この中で、一番簡単そうなUserがたくさんの人をフォローしている状況から考えていきましょう。
has_many: followings, through: :relationships
というアソシエーションを**(頭の中で)**仮置きします。ただし、Userモデル = Followerモデルであることは忘れないでください。
上記の関係を、マイグレーションファイルで表すとこうなります。
class CreateRelationships < ActiveRecord::Migration[5.2]
def change
create_table :relationships do |t|
t.references :user
t.references :follower, foreign_key: { to_table: :users }
t.timestamps
t.index [:user_id, :follower_id], unique: true
end
end
end
followers
テーブルは実際には存在しないので、foreign_key: { to_table: :users }
で、**「followerを探すときはusersテーブル(のfollower_id)を見てね」**とRailsに伝えています。
この辺りの実装は、↓この記事が大変参考になりました。
マイグレーションにおいて参照先テーブル名を自動で推定できないカラムを外部キーとして指定する方法
また、同じ人を2回フォローできないように
t.index [:user_id, :follow_id], unique: true
user_id
カラムとfollower_id
のカラムの組み合わせに、重複した値が入らないようにする制約を加えています。
アソシエーションの記載
Usersモデル
この時、User
モデルのアソシエーションの記述は下記のようになります。
class User < ApplicationRecord
has_many :relationships, dependent: :destroy
has_many :followings, through: :relationships, source: :follower
end
ポイントは、以下の通りです。
-
has_many :followings
で、アソシエーションに別名をつける -
source: :follower
で、follower
モデルを見てねとRailsに教える
若干複雑になってきましたね...
難しそうなところにはリンクも貼りましたので、ひとつづつ読み解いてみてください。
ところで、Follower
モデルなんてないのに、どうやってFollower
モデルを参照するの??という件については、次のRelationship
モデルのアソシエーションをご覧ください。
Relationshipモデル
Relationship
モデルの記載内容は下記の通りです。
class Relationship < ApplicationRecord
belongs_to :follower, class_name: 'User'
end
このように記載することで、Follower
モデルの参照がきたらUser
モデルを参照するようにRailsに伝えています。
余談: source
とclass_name
の違いについて
ところで、さっきはsource
で別のモデルを参照したのに、今度はなんでclass_name
なの?って思いませんでしたか?(私は思いました)
その理由は、こちらに書いてあって、
Rails: difference between :source => ?? and :class_name => ?? in models
-
source ...
has_many :****, through: :****
の時に使う -
class_name ...
has_many :***
の時に使う
だそうです知らなかった!!
確認
ここで、一回きちんとアソシエーションが定義できているか確かめることをお勧めします。私は、こんなふうに確かめました。
$ rails c
> user = User.first
> user.relationships
# => []
> user.relationships.create!(follower_id: 2)
# => Relationship のレコードが表示
> user.followings
# => フォローしているUser(user_idが2のユーザー)のレコードが表示
フォロー中のユーザーのレコードが呼び出されたので、id:1
のユーザーはid:2
のユーザーをフォローできていると言えます。
逆の関係も考える
さて、それでは今度はユーザーがたくさんのフォロワーにフォローされているという、先程どは逆の状況も考えてみたいと思います。
まず、ゴールなのですが、先程の逆の矢印を定義できれば良いです。
具体的に図に書き込んでみます。
follower
がたくさんのuser
をrelationships
を通じて持っている、こんな図が出来上がります。
でも、Followerモデルは実際には存在せず、Userモデルとイコールでしたね。
そのため、主語をUserに書き換えてみます。
User
はrelationships
を通じて、followers
をたくさん持っている。
言葉としてはわかりやすいのですが、今度は、頭の中に作ったFollow
モデルが邪魔になってきました。
followers
テーブルを置いたために考えるのが難しくなってきているので、Follow
モデルとUser
モデルを一つにまとめました。
だいぶ、一番初めに提示したER図に近づいてきましたね
ついでに、先程の赤い矢印も復活させてみました。
すると、今度は**through: :relationships
の部分で、名前が重複している**のがわかります。
そこで、今度は片方の名前を変えて、user
はfollower
にフォローされている、と言う意味でpassive_relationships
と名付けました。
これで、最初に提示したER図と同じになっています
アソシエーションの記載
Userモデル
では、この関係を実際にモデルに表していきます。
まず、User
モデルはこうなります。
class User < ApplicationRecord
has_many :relationships, dependent: :destroy
has_many :followings, through: :relationships, source: :follower
has_many :passive_relationships, class_name: 'Relationship', foreign_key: 'follower_id', dependent: :destroy # 追記1
has_many :followers, through: :passive_relationships, source: :user # 追記2
end
めっちゃ複雑になってきましたねひとつひとつ読みといていくと、まず、追記1の部分は
-
User
はたくさんのpassive_relationships
を持っています。 - この時、参照して欲しいクラスは
Relationship
です。 - 外部キーとして
follower_id
を使います。
というのを表しています。
次に、追記2の部分は
-
User
はたくさんのfollowers
を、passive_relationships
を通じて持っています。 -
passive_relationships
は、直前の定義によりRelationship、
クラスを参照するようになっています。
複雑すぎて憤死しそうです
ワンステップずつ丁寧に書いたつもりなので、丁寧に読んでみてください
Relarionshipモデル
最後に、Relationship
モデルに追記した内容を紹介します。
class Relationship < ApplicationRecord
belongs_to :user # 追記
belongs_to :follower, class_name: 'User'
end
こちらは逆にシンプルすぎて心配になるレベルですが、こちらはこれで完了です
確認
ここで、1回確認をしてみます。先程、コンソールでid:1のユーザーがid:2のユーザーをフォローすると言うデータは作りましたので、id:2のユーザーは、id:1のユーザーにフォローされているはずです。
それを、コンソールで試してみます。
$ rails c
> user2 = User.second
> user2.followers
# => id:1のUserのレコードが表示される
user2をフォローしているユーザーが取得できました!!成功です
to be contenued ...
これからいよいよviewやcontrollerを実装!...となっていくのですが、
ここからは次回に続きます。
Ajax出てこないじゃん!と思った方、ごめんなさい。次回に出てきます。。。
▼この話の続編はこちら
Railsでユーザーフォロー機能を実装する(Ajax使うよ)②