今回はRailsのアソシエーションについて記載します。
アソシエーションとは?
Railsでは、アソシエーションという機能が存在し、モデルとモデルを関連付けすることによって他モデルのデータも合わせて操作することができます。
例えば、userの投稿したpostを全件取得したい場合、アソシエーションの設定を行っていない場合には、whereメソッドを用いる必要があります。
user = User.find(1) posts = Post.where(user_id: user.id)
これがアソシエーションの設定を行っていた場合、以下のように書くことができます。
user = User.find(1) posts = user.posts
使用できるアソシエーション
ここからは設定できるアソシエーションについて確認していきます。
belongs_to
belongs_to
は他のモデルに所属している場合に使用します。
あるモデルでbelongs_to関連付けを行なうと、宣言を行ったモデルの各インスタンスは、他方のモデルのインスタンスに所属します。
belongs_toの説明のために商品とレビューの関係について説明します。
例えば、イヤフォンの商品説明ページがあるとします。そこに1件のレビューされていたとして、belongs_toは商品につけるのでしょうか?もしくはレビューにつけるのでしょうか?
belongs_toはレビューに設定します。レビューが商品に必ず属しています。レビューは商品がなければ存在しないので、商品にレビューが所属していると考えます。
モデルは下記のように表します。
class Product < ApplicationRecord end class Review < ApplicationRecord belongs_to :product end
belongs_to :productのように所属先のモデルを単数で指定します。
どちらにbelongs_toを設定するか迷った時には外部キーをどちらに置くか考えるといいと思います。
外部キーを持つ方にbelongs_toを設定するのが望ましいです。
実際にテーブルを検討すると下記のようになると思います。
・Productテーブル
id | product_name |
---|---|
1 | イヤフォンA |
2 | イヤフォンA |
・Reviewテーブル
id | product_id | review_point |
---|---|---|
1 | 1 | 100 |
2 | 1 | 70 |
2 | 2 | 30 |
Reviewテーブルには外部キーのproduct_idを作成してどの商品のレビューかわかるようにしています。 なので、今回はReviewにbelongs_toを設定します。
has_one
has_one
はモデル同士が1対1の関係の場合に使用します。
class User < ApplicationRecord has_one :profile end class Profile < ApplicationRecord belongs_to :user end
例えば、上記のようなuser各々がprofileを持っている場合です。
外部キーを持つ側にbelongs_to
を使用し、外部キーを持たない側がhas_one
を使用するというのが基本的な使い分けになります。
下記のようにすることでプロフィールの情報を取得できます。
user = User.find(1) // プロフィール情報の取得 user.profile
has_many
has_many
はモデル同士が1対多の関係の場合に使用します。
belongs_toのときに使った商品とレビューの例をもとに説明します。
belongs_toの説明の際には商品1つに対してレビューが1つという条件で説明しました。 実際には商品1つに対して、レビューは複数のパターンはありえます。
1対多の関係の場合にはhas_many
を下記のように設定します。
class Product < ApplicationRecord has_many :reviews end class Review < ApplicationRecord belongs_to :product end
has_many関連付けを宣言する場合、相手のモデル名は複数形で指定する必要があるので、注意が必要です。
商品に紐づいたレビューの情報の一覧は下記方法で取得できます。
product = Product.find(1) product.reviews
has_one :through
has_one :through
は1対1のつながりの2つのモデルの間に第3のモデルが紐づいており、それを経由して相手モデルの1個のインスタンスとマッチします。
例えば、userは一つのpostを持ち、postは一つのtagを持つとします。 その場合以下のように構造を表現することが出来ます。
class User < ApplicationRecord has_one :post has_one :tag, through: :post end class Post < ApplicationRecord belongs_to :user has_one :tag end class Tag < ApplicationRecord belongs_to :post end
user has one postとpost has one tagという関連性がまず存在します。
そして、Userはhas_one :tag, through: :post
というようにPostを経由してTagが1つ紐づいていることを表します。
こうすることで、Userから直接紐づいているTagを取得できます。
user = User.find(1) tag = user.tag
PostモデルがUserとTagの間の仲介モデルとして機能しています。 Postモデルを介してUserとTag間の間接的な一対一関係を確立しています。
has_many :through
has_many :through
は、1対多のつながりの2つのモデルの間に第3のモデルが紐づいており、それを経由して相手モデルの複数のインスタンスとマッチします。
1人の学生が複数の授業を受講し、1つの授業に複数の学生が参加するというパターンを考えます。 その場合以下のように構造を表現することが出来ます。
class Student < ApplicationRecord has_many :enrollments has_many :courses, through: :enrollments end class Enrollment < ApplicationRecord belongs_to :student belongs_to :course end class Course < ApplicationRecord has_many :enrollments has_many :students, through: :enrollments end
Enrollmentという中間テーブルを作成します。
中間テーブルには外部キーをそれぞれ設定します。今回の場合にはStudentとCourseの外部キーをそれぞれ登録して対象のデータを絞り込めるようにします。
StudentとCourseは中間テーブルと1対多の関係になっていて、Enrollmentという中間テーブルを経由して他テーブルのデータを取得します。
下記方法で生徒が受けているコースの一覧を取得できます。
student = Student.find(1) courses = student.courses
has_one :throughの場合には、PostモデルがUserとTagの間の仲介モデルとして機能していました。
has_many :throughの場合には、throughで指定されたPostモデルがUserとTagの間の中間テーブル
として機能しています。
中間テーブルはこちらの記事で説明しているので、合わせて確認してみてください!
has_and_belongs_to_many
has_and_belongs_to_many
はモデル同士が多対多の関係の場合に使用します。
has_many :throughとは違い、中間テーブルを作成しません。 has_many :throughで使用したStudentとCourseの例で考えます。
class Student < ApplicationRecord has_and_belongs_to_many :courses end class Course < ApplicationRecord has_and_belongs_to_many :students end
Studentは複数のCourseを受けて、Courseは複数のStudentが参加しているという多対多の関係をhas_and_belongs_to_many
で表しています。
ここで、has_many :throughとhas_and_belongs_to_manyは同じ関連付けをしていることがわかりますが、どちらを選択した方がいいでしょうか?
has_many :throughとhas_and_belongs_to_manyの大きな違いは中間モデルが存在するかどうかです。
has_and_belongs_to_many の場合、中間モデルは存在していません。 そのかわり、結合用のテーブルが存在するのですが、結合用のテーブルは外部キーのみ登録されるものです。
has_many :through はモデルに情報が全て明示的に残されています。 また、中間テーブルを作成するので、そこに外部キー以外の情報を含めることも可能です。(カスタマイズしやすいです)
個人的にはhas_many :throughで中間テーブルを作成する方法で優先的に考えたいと思っています。
理由としては、まず、id(プライマリーキー)をもたせる事ができないので、indexをはることができないので、レコード量が多いとき時間かかる可能性があるからです。 そして、開発していく中で、少なくとも結合テーブルの属性が増える可能性は十分にありえます。でもhas_and_belongs_to_many はカスタマイズできないので不安です。
まとめ
関連付けの方法をいくつか説明しました。 DBやモデルの設計をしていく中でどの方法を選択するかは決まっていくと思いますので、おおまかな方法を知っておく必要があると思います。