はじめに
Railsなどを中心に勉強中のエンジニア初心者が他の記事を参考にしたり、実際に実装してみたりして、アウトプットの一環としてまとめたものです。
本投稿は、文献を参考にしてまとめてみたものですが、テーブル設計(ER図)については筆者が独自に行なったため、間違っていることもあると思われるので、その際は指摘いただけると幸いです。
アソシエーション(関連付け)とは
アソシエーション(関連付け)とは、テーブル間のリレーションシップをモデル上の関係として操作できるようにする仕組みのこと。アソシエーションを利用することで、複数のテーブルにまたがるデータ操作が、より直感的に利用できるようになる。
ER図
下記のER図を前提として、各アソシエーションについてまとめる。
belongs_to(参照元テーブルから被参照テーブルにアクセスする)
belongs_toメソッドは、現在のモデルから相手先のモデルを参照するというアソシエーションを宣言する。
例えば、reservations → roomsテーブルのような関係性。
reservationsテーブルがroom_idを外部キーにして、roomsテーブルを参照している。
基本形
参照先のモデル名を単数形で指定する。
複数形で指定すると、エラーになるため注意。
Railsはアソシエーション宣言時の名前から自動的にモデルのクラス名を推測すため、宣言名が複数形になってしまっていると、そこから推測されるクラス名も複数形になってしまう。
# モデルの定義
belongs_to :単数形のモデル名
# 参照先テーブルへのアクセス
モデルオブジェクト.関連名
実装例
# モデルの定義
class Reservation < ApplicationRecord
belongs_to :room
end
# 参照先テーブルへのアクセス
@reservation = Reservation.find(1)
@reservation.room
備考
belongs_toのみでは、一方向のアソシエーションのみである。
そのためアソシエーションを完成させるためには、双方向にアソシエーションを行う必要がある。
相手側のモデルに対して、has_oneまたはhas_manyを指定する。
has_one(1 : 1の関連付けを宣言する)
has_oneメソッドは、1:1(被参照テーブル → 参照先テーブル)のアソシエーションを宣言する。
相手のモデルがこのモデルへの参照を持っていることを表す。
例えば、1件のユーザ情報(users)が0、または1件のプロフィール(profile)をもつような関係性。
基本形
# モデルの定義
has_one :単数形のモデル名
# 参照元テーブルへのアクセス
モデルオブジェクト.関連名
実装例
# Userモデルの定義
class User < ApplicationRecord
has_one :profile
end
# 参照元テーブルへのアクセス
@user = User.find(1)
@user.profile
注意
belongs_toメソッドとセットで利用することで、参照元/参照先テーブル双方向の関連を表現する。
1:1のアソシエーションにおいて、主となるモデルにhas_oneを宣言し、従となるモデルにbelongs_toを宣言するのが基本。
has_many(1 : nの関連を宣言する)
has_manyメソッドは、1:n(被参照テーブル → 参照元テーブル)のアソシエーションを宣言する。
相手のモデルとの1:多のつながりを表している
例えば、1件のroomが複数のreservationsを持つような関係を表している。
基本形
# モデルの定義
has_many :複数形のモデル名
# 参照元テーブルへのアクセス
モデルオブジェクト.関連名
実装例
# Postモデルの定義
class Room < ApplicationRecord
has_many :reservations
end
# 参照元テーブルへのアクセス
@room = Room.find(1)
@room.reservations
has_and_belongs_to_many(m : nの関連を宣言する、中間テーブル)
has_and_belongs_to_manyメソッドは、m:nのアソシエーションを宣言する。
相手モデルのと多対多のつながりを表している
リレーショナルデータベースでは、このような関係は直接表現できないため、中間テーブルを使って表すのが一般的。
例えば、roomsテーブルとcategoriesテーブルとを中間テーブルrooms_categoriesが仲介するような関係性。
この時、中間テーブルは主キーもタイムスタンプ(
created_atやupdated_at)などは持ってはいけない。
また、アプリケーション側で意識する必要はなく、モデルとして作成する必要もない。
基本形
# モデルの定義
has_and_belongs_to_many :複数形のモデル名
# 参照先テーブルへのアクセス
モデルオブジェクト.関連名
実装例
# Room、Categoryモデルの定義
class Room < ApplicationRecord
has_and_belongs_to_many :catogires
end
class Category < ApplicationRecord
has_and_belongs_to_many :rooms
end
# 参照先テーブルへのアクセス
@room = Room.find(1)
@room.categories
備考
hashas_and_belongs_to_manyメソッドは利用にあたって制限が多いため、できるだけhas_many :throughを優先して利用するのが望ましいらしい。
has_many :through(より複雑なm : nの関連を宣言する)
has_manyメソッドのthroughオプションを使用することで、より複雑なm:nのアソシエーションが宣言できる。
このアソシエーションでは、2つのモデルの間に「第3のモデル」が介在し、それを経由(through)して相手のモデルの「0個以上」のインスタンスとマッチする。
中間テーブルが外部キー以外の情報を持たない場合は
has_and_belongs_to_manyメソッドが利用できる。
例えば、usersテーブルとroomsテーブルを、中間テーブルreservationsが仲介するような関係性。
基本形
# 経由元のモデルの定義
has_many :複数形のモデル名
has_many :経由先のモデル名(複数形), throguh: :経由するモデル名(複数形)
# 経由するモデルの定義
belongs_to :経由元のモデル名(単数形)
belongs_to :経由先のモデル名(単数形)
# 経由先テーブルへのアクセス
モデルオブジェクト.関連名
実装例
# User、Reservation、Roomモデルの定義
class User < ApplicationRecord
has_many :reservations
has_many :rooms, through: :reservations
end
class Reservation < ApplicationRecord
belongs_to :user
belongs_to :room
end
class Room < ApplicationRecord
has_many :reservations
has_many :users, through: :reservations
end
# 経由先テーブルへのアクセス
@user = User.find(1)
@user.rooms # Reservationモデルを介さずにRoomの情報を取得可能
備考
has_many :throughによるアソシエーションは、ネストしたhas_manyのアソシエーションを介して「ショートカット」を設定することもできる。
例えば、1つの組織(organizaitons)に複数の提供者(suppliers)が所属しており、複数の提供者が複数の部屋(rooms)を提供している状況において、組織に紐づく部屋の情報を提供者をスキップして取得したい場合など。
その場合は、以下のように宣言することができる。
# Organization、Supplier、Roomモデルの定義
class Organization < ApplicationRecord
has_many :suppliers
has_many :rooms, through: :suppliers
end
class Supplier < ApplicationRecord
belongs_to :organization
has_many :rooms
end
class Room < ApplicationRecord
belongs_to :supplier
end
# 経由先テーブルへのアクセス
@organization = Organization.find(1)
@organization.rooms # Supplierモデルを介さずにRoomの情報を取得可能
has_one :through
省略。以下参照。
参考
最後に
いかがでしたでしょうか。
今回はRailsのモデル同士のアソシエーションについてまとめてみました。
ここ違うよ!でしたり、こうした方がいいよ!などがあればコメントいただけると幸いです。
他にも下記のような記事を投稿しております。
興味がありましたら、ぜひご覧ください。
