<Ruby on Rails> Active Record の関連付け
railsガイドを参考に、関連付けについて記載する。
自分のための振り返りメモなので、随時変更する可能性あり。
参考:Active Record の関連付け - Railsガイド
belongs_to
1対1の繋がりが設定される。
この宣言を行ったモデルのインスタンスは他方のモデルのインスタンスに従属(=belongs to)する。
例えば掲示板の投稿(board)とユーザー(user)で、一つの投稿に対して一人のユーザーが存在するので1対1の関係となる。
class board < ApplicationRecord belongs_to :user end
belongs_toで指定するモデルは必ず単数形。
関連付けの名前から自動的にモデルのクラス名を推測するので、複数形にしてしまうと推測されるクラス名も誤ったものになり、uninitialized constant(名前が間違っている)エラーがでる。
また、belongs_toを記載したモデルは、従属するモデルに対応する外部キーを作成する必要がある(今回の場合はuser_idカラムををboardモデルに追加)。
rails g migrationを実行する時、'従属される側のモデル:references'を追記すると外部キーのforeign_key:trueが従属モデルのmigrationファイルに追加され、modelにはbelongs_toが追加される。(公式では:belongs_toでも可とある)
例:userモデルとboardモデルの関連付け
# userモデル作成 $ rails g model user # boardモデル作成 $ rails g model board user:references $ rails db:migrate
作成されるモデルクラス
# app/models/user.rb class User < ApplicationRecord end # app/models/board.rb class Board < ApplicationRecord belongs_to :user end
作成されるmigrationファイル
# db/migrate/...create_users.rb class CreateUsers < ActiveRecord::Migration[6.0] def change create_table :users do |t| t.timestamps end end end # db/migrate/...create_boards.rb class CreateBoards < ActiveRecord::Migration[6.0] def change create_table :boards do |t| t.references :user, null: false, foreign_key: true t.timestamps end end end
作成されるschemaファイル
ActiveRecord::Schema.define(version: 2020_11_22_120323) do create_table "boards", force: :cascade do |t| t.integer "user_id", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["user_id"], name: "index_boards_on_user_id" end create_table "users", force: :cascade do |t| t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end add_foreign_key "boards", "users" end
参考ページ: Active Record マイグレーション - Railsガイド
has_one
1対1の繋がりが設定される。
belongs_toとの違いは、宣言が行われているモデルのインスタンスが、他方のインスタンスを丸ごと含んでいる、または所有していること。
所有する側に従属するモデルのidがモデル名_idという形で関連付けられる。
例えば、一人の供給者(supplier)が一つのアカウント(account)を持つため、1対1の関係になる。
class Supplier < ApplicationRecord has_one :account end
宣言が行われているsupplierが一人につき、一つのaccountを所有しているため、has_oneを使用する。
この場合、accountモデルにはbelongs_toが設定される。
has_one :through
他方のモデルに対して「1対1」のつながりを設定する。
2つのモデルの間に「第3のモデル」(joinモデル)が介在する点が特徴。
相手モデルの1つのインスタンスとマッチする。
例えば1人の提供者(supplier)が1つのアカウントを持ち、アカウントが1つのアカウント履歴を持つ場合、
supplierはaccountを一つ所有(has_one)しており、accountを通して一つのaccount履歴を所有(has_one)していると言える。
class Supplier < ApplicationRecord has_one :account has_one :account_history, through: :account end class Account < ApplicationRecord belongs_to :supplier has_one :account_history end class AccountHistory < ApplicationRecord belongs_to :account end
has_many
他モデルとの間が1対多の繋がりがある。
belongs_toの例で使用したユーザー(user)と掲示板(board)の場合、ユーザー一人は複数の投稿ができるため、1対多の関係と言える。
Userモデルにはhas_manyを記載。
class User < ApplicationRecord has_many :boards end
has_manyで関連付けするモデルは必ず複数形。
has_many :through
他方のモデルと「多対多」のつながりを設定する。
2つのモデルの間に「第3のモデル」(joinモデル)が介在する点が特徴。
それによって、相手モデルの「0個以上」のインスタンスとマッチする。
例えば患者(patient)が医師(physician)との診察予約(appointment)を取る医療業務で考えると、
一人の患者が診察予約を一人の医師に対して行うことから、診察予約の視点だとpatientとphysicianにappointmentは従属している(belongs_to)、と言える。
一方、患者や医師の視点で見ると、複数の診察予約を持つことができるので、1対多となるhas_manyが設定される。
医師と患者を結びつけるために:throughオプションにappointmentsが記載される。
class Physician < ApplicationRecord has_many :appointments has_many :patients, through: :appointments end class Appointment < ApplicationRecord belongs_to :physician belongs_to :patient end class Patient < ApplicationRecord has_many :appointments has_many :physicians, through: :appointments end
絵にすると下図の通り。
Physician視点
Appointment視点
Patient視点
has_and_belongs_to_many
他方のモデルと「多対多」のつながりを作成する。
through:オプションを使用する場合と異なり、第3のモデル(joinモデル)が介在しないが、結合テーブルは必須。
例えば、アプリケーションに完成品(assembly)と部品(part)があり、1つの完成品に多数の部品が対応し、逆に1つの部品にも多くの完成品が対応する場合は以下のようになる。
class Assembly < ApplicationRecord has_and_belongs_to_many :parts end class Part < ApplicationRecord has_and_belongs_to_many :assemblies end
has_and_belongs_to_manyとhas_many :throughの使い分けについて
Active Record の関連付け - Railsガイド
双方の関連付け
関連付けは基本的には双方向で定義される。
class User < ApplicationRecord has_many :boards end class Board < ApplicationRecord belongs_to :user end
Userモデル(ユーザー)とBoardモデル(掲示板)があり、上記のように関連付けされている場合、以下のようにデータの関連を共有していることがわかる。
a = User.first b = a.Boards.first a.first_name == b.user.first_name # => true a.first_name = 'David' a.first_name == b.user.first_name # => true
ただし、Active Recordでスコープやthroughオプションやforeign_keyを使った場合、双方向の関連付けは自動的に認識されないため、その時は:inverse_ofオプションを使用する。
詳しくはRailsガイドの'ActiveRecordの関連付け:双方関連付け'を参照。