Webとデザインのあれこれ

プログラミングとUIデザインの学習記録です。

多対多の関係について

本記事の内容

フィヨルドブートキャンプでアプリにフォロー機能を実装するという課題がありました。

課題の趣旨として、フォロー機能の核となる多対多の関連付けを正しく実装するという目的があります。

本記事では課題を実装する上で理解するのが難しかったモデルの設計について備忘録としてまとめます。

モデルの関連付けについて

フォロー機能の厄介な点として、フォローするユーザーフォローされるユーザーが存在することが挙げられます。

このような場合、その関係は1対Nではなく、多対多となります。

1対多の関連付けについては、お馴染みですね。下記のようにhas_manybelongs_toを用いて親子関係を表現します。

class Author < ApplicationRecord
  has_many :books, dependent: :destroy
end
 
class Book < ApplicationRecord
  belongs_to :author
end

では、多対多の関係はどのように関連づけるのでしょうか。

まず、フォロー機能実装にあたりFriendshipというモデルを考えます。

これが中間テーブルの役割に該当するのですが、多対多をいきなり考えるとよくわからなくなる(少なくとも私はよくわからなくなりました)ので、まず1対多の関係を考慮します。

1人のユーザーが複数をフォローする場合、つまりフォローする側から考える能動的関係(active_relationship)を考慮します。

能動的関係(active_relationship)

id:1のユーザーが複数をフォローする図式が以下のようになりますね。

User
id 氏名 E-mail
1 山田太郎 yamada.tarou@gmail.com
active_relationships
follower_id followed_id
1 2
1 10
8 1
1 7
4 1
User.following
id 氏名 E-mail
2 ... ...
id 氏名 E-mail
10 ... ...

id 氏名 E-mail
7 ... ...

active_relationshipthrough(通して)、フォローされる側の情報(User.following)を所有する(has_many)というイメージです。

受動的関係(passive_friendship)

次は、フォローされる側(id:1)から見た受動的関係(passive_friendship)です。こちらも1対Nで考えましょう。 これ、先程のFriendshipモデルをひっくり返しただけですよね。

User
id 氏名 E-mail
1 山田太郎 yamada.tarou@gmail.com
passive_relationships
followed_id follower_id
2 1
10 1
1 8
7 1
1 4
User.followers
id 氏名 E-mail
8 ... ...
id 氏名 E-mail
4 ... ...

こちらも、passive_relationshipを通して(through)、フォローする側の情報(User.followers)を所有(has_many)するイメージです。

Friendshipという1種類のモデルを、2種類の視点から考慮するのですね。

Friendshipモデル

最後に、Friendshipモデルの関連付けは以下のようになります。

class Friendship < ApplicationRecord
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
end

user.rbで定義した外部キーfollowerfollowedをbelongs_toに記述しています。

問題ないですね。では、実装してみましょう!

実装

まず、User モデルはhas_manyで Friendship モデルを所有しています。

カラムにはどのUserに所有されるかを表す外部キーを持たなくてはなりません。

フォロー機能の場合、フォローする側フォローされる側という区別が必要ですので、異なる2種類の外部キーが必須となります。

外部キーはフォローする側のfollower_idとフォローされる側のfollowed_idの2種類となります。   
image.png

いずれも、本来のクラス名はFriendshipなのでclass_name: : "Friendship"というオプションを記載します。

また、ユーザーが削除されたときに自動的にフレンドシップも削除されるようにするため、dependent: :destroy をつけます。

次に、先程のテーブルで確認したようにFriendshipを介してユーザーを取得する処理を考えましょう。

f:id:b_leiu:20190619205104p:plain

フォローする側のユーザーは、所有するactive_friendshipsを通じてフォローされる側のユーザーfollowedを所有します。

逆に、フォローされる側のUserは、所有するpassive_friendshipsを介してフォローする側のユーザーfollowerを所有します。

sourceはモデルの参照元を明示します。

デフォルトのhas_many throughという関連付けにおいては、Railsはモデルのクラス名 (単数形) に対応する外部キーを探しますが、 今回はsource名に参照先を明示しているお陰で、followed_idfollower_idから該当ユーザーを取得できます。

source: :followed #フォローされる側
source: :follower #フォローする側

中間テーブルを通して、フォローされる側のユーザーを集めることをfollowing、フォローする側のユーザーを集めることをfollowersと定義しているだけですね。

このhas_many throughの関連付けのお陰で、User.followingUser.followersでユーザー情報を配列のように取得できます。

# app/models/user.rb

def follow(other_user)
  following << other_user
end

上記のメソッドでは、<<演算子を用いてユーザー情報をfollowingnの配列の最後に追記しています。簡単にフォロー機能が実装できますね。

最後に

こちらで記事は終了です。 参考文献:

Ruby on Rails チュートリアル:実例を使って Rails を学ぼう

qiita.com