Railsのアソシエーションについて

こんにちは。今回はRailsの多対多の自己結合の実装について説明していきたいと思います。

この記事にたどり着いている人は大体アソシエーションの基本を理解している人だと思いますが、始めたての人のために簡単に紹介しておきます。
そんなのどうでも良いぜって人は次のセクションから見ていただければと思います。

アソシエーションというのは、Model間の関連付けのことを言います。 Twitterで例を出すと、ユーザーモデルと投稿モデルの二つを関連させるみたいな感じですね。 この投稿をしたのはこのユーザーであるというのを関連づける訳ですね。

has_manyとかbelongs_toとかがそれに当たります。 ここから説明すると長くなってしまうので、「rails has_many」とかで検索してみて下さい。

多対多自己結合について

さてここからは多対多の自己結合について話していこうと思います。 まずはmodelの関係性をどんな感じにしたいのかをイメージ持って欲しいので、画像で簡単に説明してしまいます。

modelの関係性

実装したらこんな感じでオススメプランを引っ張ってきたい感じですね。

実装例

こんな感じになります。 あるプランを紹介するページの下部他に類似したオススメプランを紹介するためにこのようなmodelの関係性を組む必要があります。

今回厄介なのが、中間テーブルが結びつけるmodelが両方同じ内容になっていることです。

プランModelが中間テーブルを介してプランModelの内容を持ってくるという風になっております。

一番最初に紹介したTwitterの例だと

ユーザーモデル : 中間テーブル : 投稿モデル

だったのが

プランモデル : 中間テーブル : プランモデル(オススメプラン) というような関係性になっているのです。

twitterの例で言うなら

ユーザーモデルに

has_many :中間テーブル
has_many :投稿モデル, through: :中間テーブル

中間テーブル

belongs_to :ユーザーモデル
belongs_to :投稿モデル

投稿モデル

has_many :中間テーブル
has_many :ユーザーモデル, through: :中間テーブル

こんな感じで実装すれば良いのですが、 今回はプランモデルにユーザーモデルに書いている内容と、投稿モデルに書いている内容をプランモデルにひとまとめにしないといけません。

しかし、 プランモデル

has_many :中間テーブル
has_many :プランモデル

has_many :中間テーブル
has_many :オススメプランモデル

中間テーブル

belongs_to :プランモデル
belongs_to :オススメプランモデル

こんな感じの実装にしないといけないです。 ここで重要なのが、オススメプランがないのにどうやって関連付けを実現するのかということですね。 次のセクションでそれについて説明していけたら良いかなと思います。

実装方法

Railsのアソシエーションでは

has_many :自由に分かる名前, class_name: "モデル名", foreign_key: :外部キー名

や

belongs_to :自由に分かる名前, class_name: "モデル名"

アソシエーションをする際にclass_nameを後ろで指定すれば自由に名前をつけることができます。 これによってclass_name: “プランモデル”にしてあげればプランとオススメプランの二つを両立することができますね。

プラン.rbにて

has_many :relationship, class_name: 中間テーブル, foreign_key: :プランid
has_many :オススメプラン, through: :relationship, source: :オススメプラン


中間テーブル.rbにて

belongs_to :プラン
belongs_to :オススメプラン、 class_name: "プランモデル"

こんな感じにすれば、コントローラーの方で望んだ結果を得る事ができます。

@plan = Plan.find(params[:id])
@recommend_plans = @plan.recommend_plans

のようにすれば、@recommend_plansのなかにオススメプランの情報が配列として返ってくるようになりました。

今回はプランからオススメプランを取ってくるだけだったので片方向の実装ですみましたが、Twitterのフォロー、フォロワーとかになってくると双方向のアクセスが必要になるケースもあります。 そんな時は中間テーブルとのアソシエーションを増やしてあげると良いです。

has_many :active_relationship, class_name: "中間テーブル", foreign_key: :プランid
has_many :passive_relationship, class_name: "中間テーブル", foreign_key: :オススメプランid

あとは先ほどと同じようにhas_manyで実装して中間テーブルを介してアソシエーションを完成させてあげれば大丈夫です! いろいろ試しながら考えてみてくださいね!

感想、その他

ちゃんと説明できているか心配です。 自分が務めている会社のプロジェクト内容を全部晒すわけにはいかないので部分だけ取り除いて説明するのが難しいですね。 というか多分わかりにくいし、cssの装飾もろくにやっていないのでかなり見難いと思います。ごめんなさい。