プログラミング 美徳の不幸

Ruby, Rails, JavaScriptなどのプログラミングまとめ、解説、備忘録。

自己参照の関連

Railsのサンプルってほぼ必ずミニブログ=Twitterなんだけど、Twitterのfollowing(フォローの対象となった人)/follower(フォローしている側)のリレーションって考えると意外とめんどくさい。

ざっくり、UserとUserが多対多で結ばれることは想像がつくが、その中間はどう実装すればいいのか。。。


答えはこちら。
◯app/models/user.rb

class User < ActiveRecord::Base
    has_many :following_ships, foreign_key: :follwer_id
    has_many :followings, through: :following_ships, class_name: "User"
    has_many :inverse_following_ships, foreign_key: :following_id, class_name: "FollowingShip"
    has_many :followers, through: :inverse_following_ships, class_name: "User"
end

class FollowingShip < ActiveRecord::Base
    belongs_to :follower, class_name: "User"
    belongs_to :following, class_name: "User"
end

テーブルはusersとfollowing_ships, クラスもUserとFollowingShipの2つしかないわけだが、関係を二重に定義づけることが重要。inverse_following_shipsは答えをしらないとなかなか思いつかないと思う。

ここでオプションの確認をすると、foreign_keyは関連における参照元の外部キーを指定するもの。ちょっと混同してたんだけど参照元というのはこの場合followingshipのほうで、一般にA has_many BsだとAが参照先、Bが参照元。「A has many Bs」という文章からは逆のような気もするけど、キーを持っているのはBのほうなのだから確かにBが参照元である。

で、通常だとこのキーはuser_idになるんだけど、今回はUserと二重に対応させるのだからuserの外部キーが2つ必要。これをfollower_idとfollowing_idとした。

で、@userが自分のid=follower_idであるFollowingShipを集めると、それは自分がフォローしている側のfollowingshipが集められる。つまり外部キーをfollower_idとした場合の関連はfollowingsである。

一方で、@userがid=following_idであるFollowingShipを集めると、それは自分のことをフォローしてくれているfollowingshipが集められる。この場合の関連はfollowersである。

どちらの関連も必要なのだけど、has_many :following_ship, foreign_key: [:follower_id, :following_id]のようなことはできないから、後者はクラスは同じだが異なる関連として定義する。

これで、@user.followersと@user.followingsでそれぞれフォロワーとフォローしている人を取得できるようになりました。




◯参考記事
ほぼそのままです。
http://asciicasts.com/episodes/163-self-referential-association