1 関連付けを使用する理由
Railsでは、「関連付け(アソシエーション: association)」とは2つのActive Recordモデル同士のつながりを指します。モデルとモデルの間には関連付けを行なう必要がありますが、その理由を御存じでしょうか。それは、関連付けを行う事であなたのコードでの共通操作をよりシンプルで簡単にするからです。簡単なRailsアプリケーションを例にとって説明しましょう。このアプリケーションには著者用のモデル(Author)と書籍用のモデル(Book)があります。一人の著者は、複数の書籍を持っています。関連付けを設定していない状態では、モデルの宣言は以下のようになります。
class Author < ApplicationRecord end class Book < ApplicationRecord end
ここで、既存の著者が新しい書籍を1つ執筆したくなったとします。この場合、以下のようなコードを実行する必要があるでしょう。
@book = Book.create(published_at: Time.now, author_id: @author.id)
今度は著者を削除する場合を考えてみましょう。著者を削除するなら、以下のように、執筆した書籍も残らず削除されるようにしておかなければなりません。
@books = Book.where(author_id: @author.id) @books.each do |book| book.destroy end @author.destroy
Active Recordの関連付け機能を使用すると、2つのモデルの間につながりがあることを明示的にRailsに対して宣言することができ、それによってモデルの操作を一貫させることができます。著者と書籍の設定するコードを次のように書き直せます。
class Author < ApplicationRecord has_many :books, dependent: :destroy end class Book < ApplicationRecord belongs_to :author end
上のように関連付けを追加したことで、特定の著者用に新しい書籍を1つ追加する作業が以下のように一行でできるようになりました。
@book = @author.books.create(published_at: Time.now)
著者と、その著者の書籍をまとめて削除する作業はさらに簡単です。
@author.destroy
その他の関連付け方法については、次の節をお読みください。それに続いて、関連付けに関するさまざまなヒントや活用方法、Railsの関連付けメソッドとオプションの完全な参照物もご紹介します。
2 関連付けの種類
Railsでサポートされている関連付けは以下の6種類です。
belongs_to
has_one
has_many
has_many :through
has_one :through
has_and_belongs_to_many
関連付けは、一種のマクロ的な呼び出しとして実装されており、これによってモデル間の関連付けを宣言的に追加することができます。たとえば、あるモデルが他のモデルに従属している(belongs_to
)と宣言すると、2つのモデルのそれぞれのインスタンス間で「主キー - 外部キー」情報を保持しておくようにRailsに指示が伝わります。また、それに伴って幾つかの役立つメソッドもそのモデルに追加されます。
本ガイドではこの後、それぞれの関連付けの宣言方法と使用方法について詳しく解説します。その前に、それぞれの関連付けが適切となる状況について簡単にご紹介しましょう。
2.1 belongs_to
関連付け
あるモデルでbelongs_to
関連付けを行なうと、他方のモデルとの間に「1対1」のつながりが設定されます。このとき、宣言を行ったモデルのすべてのインスタンスは、他方のモデルのインスタンスに「従属(belongs to)」します。たとえば、Railsアプリケーションに著者(Author)と書籍(Book)情報が含まれており、1人の著者につき正確に1冊だけを割り当てたいのであれば、Bookモデルで以下のように宣言します。
class Book < ApplicationRecord belongs_to :author end
belongs_to
関連付けで指定するモデル名は必ず「単数形」にしなければなりません。上記の例で、Book
モデルのauthor
関連付けを複数形(authors
)にしてしまうと、「uninitialized constant Book::Authors」エラーが発生します。Railsは、関連付けの名前から自動的にモデルのクラス名を推測します。従って、関連付け名が誤って複数形になってしまっていると、そこから推測されるクラス名も誤った形の複数形になってしまいます。
上の関連付けに対応するマイグレーションは以下のような感じになります。
class CreateBooks < ActiveRecord::Migration[5.0] def change create_table :authors do |t| t.string :name t.timestamps end create_table :books do |t| t.belongs_to :author, index: true t.datetime :published_at t.timestamps end end end
2.2 has_one
関連付け
has_one
関連付けも、他方のモデルとの間に1対1の関連付けを設定します。しかし、その意味と結果はbelongs_to
とは若干異なります。has_one
関連付けの場合は、その宣言が行われているモデルのインスタンスが、他方のモデルのインスタンスを「まるごと含んでいる」または「所有している」ことを示します。たとえば、供給者(supplier)1人につきアカウント(account)を1つだけ持つという関係があるのであれば、以下のように宣言を行います。
class Supplier < ApplicationRecord has_one :account end
上の関連付けに対応するマイグレーションは以下のような感じになります。
class CreateSuppliers < ActiveRecord::Migration[5.0] def change create_table :suppliers do |t| t.string :name t.timestamps end create_table :accounts do |t| t.belongs_to :supplier, index: true t.string :account_number t.timestamps end end end
ユースケースにもよりますが、アカウントとの関連付けのために、供給者のカラムに一意のインデックスや外部キー制約を追加する必要もある場合もあります。その場合、カラムの定義は次のようになる可能性があります。
create_table :accounts do |t| t.belongs_to :supplier, index: { unique: true }, foreign_key: true # ... end
2.3 has_many
関連付け
has_many
関連付けは、他のモデルとの間に「1対多」のつながりがあることを示します。has_many
関連付けが使用されている場合、「反対側」のモデルではbelongs_to
が使用されることが多くあります。has_many
関連付けが使用されている場合、そのモデルのインスタンスは、反対側のモデルのインスタンスを「0個以上」所有します。たとえば、著者(Author)と書籍(Book)を含むRailsアプリケーションでは、著者のモデルを以下のように宣言することができます。
class Author < ApplicationRecord has_many :books end
has_many
関連付けを宣言する場合、相手のモデル名は「複数形」にする必要があります。
上の関連付けに対応するマイグレーションは以下のような感じになります。
class CreateAuthors < ActiveRecord::Migration[5.0] def change create_table :authors do |t| t.string :name t.timestamps end create_table :books do |t| t.belongs_to :author, index: true t.datetime :published_at t.timestamps end end end
2.4 has_many :through
関連付け
has_many :through
関連付けは、他方のモデルと「多対多」のつながりを設定する場合によく使われます。この関連付けは、2つのモデルの間に「第3のモデル」(結合モデル)が介在する点が特徴です。それによって、相手モデルの「0個以上」のインスタンスとマッチします。たとえば、患者(patient)が医師(physician)との診察予約(appointment)を取る医療業務を考えてみます。この場合、関連付けは次のような感じになるでしょう。
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
上の関連付けに対応するマイグレーションは以下のような感じになります。
class CreateAppointments < ActiveRecord::Migration[5.0] def change create_table :physicians do |t| t.string :name t.timestamps end create_table :patients do |t| t.string :name t.timestamps end create_table :appointments do |t| t.belongs_to :physician, index: true t.belongs_to :patient, index: true t.datetime :appointment_date t.timestamps end end end
結合モデル(join model)のコレクションは、has_many
経由で管理することができます。たとえば、以下のような割り当てを実行したとします。
physician.patients = patients
このとき、新たに関連付けられたオブジェクトについて、新しい結合モデルが自動的に作成されます。結合時に不足している部分があれば、その行は結合モデルから削除され、結合モデルに含まれなくなります。
モデル結合時の不足分自動削除は即座に行われます。さらに、その際にdestroyコールバックはトリガーされませんので注意が必要です。
has_many :through
関連付けは、ネストしたhas_many
関連付けを介して「ショートカット」を設定する場合にも便利です。たとえば、1つのドキュメントに多くの節(section)があり、1つの節の下に多くの段落(paragraph)がある状態で、節をスキップしてドキュメントの下のすべての段落の単純なコレクションが欲しいとします。その場合、以下の方法で設定できます。
class Document < ApplicationRecord has_many :sections has_many :paragraphs, through: :sections end class Section < ApplicationRecord belongs_to :document has_many :paragraphs end class Paragraph < ApplicationRecord belongs_to :section end
through: :sections
と指定することにより、Railsは以下の文を理解できるようになります。
@document.paragraphs
2.5 has_one :through
関連付け
has_one :through
関連付けは、他方のモデルに対して「1対1」のつながりを設定します。この関連付けは、2つのモデルの間に「第3のモデル」(結合モデル)が介在する点が特徴です。それによって、相手モデルの1つのインスタンスとマッチします。たとえば、1人の提供者(supplier)が1つのアカウントに関連付けられ、さらに1つのアカウントが1つのアカウント履歴に関連付けられる場合、supplierモデルは以下のような感じになります。
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
上の関連付けに対応するマイグレーションは以下のような感じになります。
class CreateAccountHistories < ActiveRecord::Migration[5.0] def change create_table :suppliers do |t| t.string :name t.timestamps end create_table :accounts do |t| t.belongs_to :supplier, index: true t.string :account_number t.timestamps end create_table :account_histories do |t| t.belongs_to :account, index: true t.integer :credit_rating t.timestamps end end end
2.6 has_and_belongs_to_many
関連付け
has_and_belongs_to_many
関連付けは、他方のモデルと「多対多」のつながりを作成しますが、through:
を指定した場合と異なり、第3のモデル(結合モデル)が介在しません(訳注: 後述するように結合用のテーブルは必要です)。たとえば、アプリケーションに完成品(assembly)と部品(part)があり、1つの完成品に多数の部品が対応し、逆に1つの部品にも多くの完成品が対応するのであれば、モデルの宣言は以下のようになります。
class Assembly < ApplicationRecord has_and_belongs_to_many :parts end class Part < ApplicationRecord has_and_belongs_to_many :assemblies end
上の関連付けに対応するマイグレーションは以下のような感じになります。
class CreateAssembliesAndParts < ActiveRecord::Migration[5.0] def change create_table :assemblies do |t| t.string :name t.timestamps end create_table :parts do |t| t.string :part_number t.timestamps end create_table :assemblies_parts, id: false do |t| t.belongs_to :assembly, index: true t.belongs_to :part, index: true end end end
2.7 belongs_to
とhas_one
のどちらを選ぶか
2つのモデルの間に1対1の関係を作りたいのであれば、いずれか一方のモデルにbelongs_to
を追加し、もう一方のモデルにhas_one
を追加する必要があります。どちらの関連付けをどちらのモデルに置けばよいのでしょうか。
区別の決め手となるのは外部キー(foreign key)をどちらに置くかです(外部キーは、belongs_to
を追加した方のモデルのテーブルに追加されます)。もちろんこれだけでは決められません。データの実際の意味についてもう少し考えてみる必要があります。has_one
というリレーションは、主語となるものが目的語となるものを「所有している」ということを表しています。そして、所有されている側(目的語)の方が、所有している側(主語)を指し示しているということも表しています。たとえば、「供給者がアカウントを持っている」とみなす方が、「アカウントが供給者を持っている」と考えるよりも自然です。つまり、この場合の正しい関係は以下のようになります。
class Supplier < ApplicationRecord has_one :account end class Account < ApplicationRecord belongs_to :supplier end
上の関連付けに対応するマイグレーションは以下のような感じになります。
class CreateSuppliers < ActiveRecord::Migration[5.0] def change create_table :suppliers do |t| t.string :name t.timestamps end create_table :accounts do |t| t.integer :supplier_id t.string :account_number t.timestamps end add_index :accounts, :supplier_id end end
マイグレーションでt.integer :supplier_id
のように「小文字のモデル名_id」と書くと、外部キーを明示的に指定できます。現在のバージョンのRailsでは、同じことをt.references :supplier
という方法で記述できます。こちらの方が実装の詳細が抽象化され、隠蔽されます。
2.8 has_many :through
とhas_and_belongs_to_many
のどちらを選ぶか
Railsでは、モデル間の多対多リレーションシップを宣言するのに2とおりの方法が使用できます。簡単なのはhas_and_belongs_to_many
を使用する方法です。この方法では関連付けを直接指定できます。
class Assembly < ApplicationRecord has_and_belongs_to_many :parts end class Part < ApplicationRecord has_and_belongs_to_many :assemblies end
多対多のリレーションシップを宣言するもう1つの方法はhas_many :through
です。こちらの場合は、結合モデルを使用した間接的な関連付けが使用されます。
class Assembly < ApplicationRecord has_many :manifests has_many :parts, through: :manifests end class Manifest < ApplicationRecord belongs_to :assembly belongs_to :part end class Part < ApplicationRecord has_many :manifests has_many :assemblies, through: :manifests end
どちらを使用するかについてですが、経験上、リレーションシップのモデルそれ自体を独立したエンティティとして扱いたい(両モデルの関係そのものについて処理を行いたい)のであれば、中間に結合モデルを使用するhas_many :through
リレーションシップを選ぶのが最もシンプルです。リレーションシップのモデルで何か特別なことをする必要がまったくないのであれば、結合モデルの不要なhas_and_belongs_to_many
リレーションシップを使用するのがシンプルです(ただし、こちらの場合は結合モデルが不要な代わりに、専用の結合テーブルを別途データベースに作成しておく必要がありますので、お忘れなきよう)。
結合モデルで検証(validation)、コールバック、追加の属性が必要なのであれば、has_many :through
を使用しましょう。
2.9 ポリモーフィック関連付け
ポリモーフィック関連付けは、関連付けのやや高度な応用です。ポリモーフィック関連付けを使用すると、ある1つのモデルが他の複数のモデルに属していることを、1つの関連付けだけで表現することができます。たとえば、写真(picture)モデルがあり、このモデルを従業員(employee)モデルと製品(product)モデルの両方に従属させたいとします。この場合は以下のように宣言します。
class Picture < ApplicationRecord belongs_to :imageable, polymorphic: true end class Employee < ApplicationRecord has_many :pictures, as: :imageable end class Product < ApplicationRecord has_many :pictures, as: :imageable end
ポリモーフィックなbelongs_to
は、他のあらゆるモデルから使用できる、(デザインパターンで言うところの)インターフェイスを設定する宣言とみなすこともできます。@employee.pictures
とすると、写真のコレクションをEmployee
モデルのインスタンスから取得できます。
同様に、@product.pictures
とすれば写真のコレクションをProduct
モデルのインスタンスから取得できます。
Picture
モデルのインスタンスがあれば、@picture.imageable
とすることで親を取得できます。これができるようにするためには、ポリモーフィックなインターフェイスを使用するモデルで、外部キーのカラムと型のカラムを両方とも宣言しておく必要があります。
class CreatePictures < ActiveRecord::Migration[5.0] def change create_table :pictures do |t| t.string :name t.integer :imageable_id t.string :imageable_type t.timestamps end add_index :pictures, [:imageable_type, :imageable_id] end end
t.references
という書式を使用するとさらにシンプルにできます。
class CreatePictures < ActiveRecord::Migration[5.0] def change create_table :pictures do |t| t.string :name t.references :imageable, polymorphic: true, index: true t.timestamps end end end
2.10 自己結合
データモデルを設計していると、時に自分自身に関連付けられる必要のあるモデルに出会うことがあります。たとえば、1つのデータベースモデルに全従業員を格納しておきたいが、マネージャーと部下(subordinate)の関係も追えるようにしておきたい場合が考えられます。この状況は、自己結合関連付けを使用してモデル化することができます。
class Employee < ApplicationRecord has_many :subordinates, class_name: "Employee", foreign_key: "manager_id" belongs_to :manager, class_name: "Employee" end
上のように宣言しておくと、@employee.subordinates
と@employee.manager
が使用できるようになります。
マイグレーションおよびスキーマでは、モデル自身にreferencesカラムを追加します。
class CreateEmployees < ActiveRecord::Migration[5.0] def change create_table :employees do |t| t.references :manager, index: true t.timestamps end end end
3 ヒントと注意事項
RailsアプリケーションでActive Recordの関連付けを効率的に使用するためには、以下について知っておく必要があります。
- キャッシュ制御
- 名前衝突の回避
- スキーマの更新
- 関連付けのスコープ制御
- 双方向関連付け
3.1 キャッシュ制御
関連付けのメソッドは、すべてキャッシュを中心に構築されています。最後に実行したクエリの結果はキャッシュに保持され、次回以降の操作で使用できます。このキャッシュはメソッド間でも共有されることに注意してください。例:
author.books # データベースからbooksを取得する author.books.size # booksのキャッシュコピーが使用される author.books.empty? # booksのキャッシュコピーが使用される
データがアプリケーションの他の部分によって更新されている可能性に対応するために、キャッシュを再読み込みするにはどうしたらよいでしょうか。その場合は関連付けのメソッド呼び出しでreload
を指定するだけで、キャッシュが破棄されてデータが再読み込みされます。
author.books # データベースからbooksを取得する author.books.size # booksのキャッシュコピーが使用される author.books.reload.empty? # booksのキャッシュコピーが破棄される # その後データベースから再度読み込まれる
3.2 名前衝突の回避
関連付けにはどんな名前でも使用できるとは限りません。関連付けを作成すると、モデルにその名前のメソッドが追加されます。従って、ActiveRecord::Base
のインスタンスで既に使用されているような名前を関連付けに使用するのは禁物です。そのような名前を関連付けに使用すると、基底メソッドが上書きされて不具合が生じる可能性があります。attributes
やconnection
は関連付けに使ってはならない名前の例です。
3.3 スキーマの更新
関連付けはきわめて便利ですが、残念ながら全自動の魔法ではありません。関連付けを使用するからには、関連付けの設定に合わせてデータベースのスキーマを常に更新しておく責任が生じます。作成した関連付けにもよりますが、具体的には次の2つの作業が必要になります。1. belongs_to
関連付けを使用する場合は、外部キーを作成する必要があります。2. has_and_belongs_to_many
関連付けを使用する場合は、適切な結合テーブルを作成する必要があります。
3.3.1 belongs_to
関連付けに対応する外部キーを作成する
belongs_to
関連付けを宣言したら、対応する外部キーを作成する必要があります。以下のモデルを例にとります。
class Book < ApplicationRecord belongs_to :author end
上の宣言は、以下のようにbooksテーブル上の外部キー宣言によって裏付けられている必要があります。
class CreateBooks < ActiveRecord::Migration[5.0] def change create_table :books do |t| t.datetime :published_at t.string :book_number t.integer :author_id end end end
モデルを先に作り、しばらく経過してから関連を追加で設定する場合は、add_column
マイグレーションを作成して、必要な外部キーをモデルのテーブルに追加するのを忘れないようにしてください。
クエリのパフォーマンスを向上させたり、外部キー制約が正しくデータを参照しているかを保証するために、インデックスを追加するのは良い方法です。
class CreateBooks < ActiveRecord::Migration[5.0] def change create_table :books do |t| t.datetime :published_at t.string :book_number t.integer :author_id end add_index :books, :author_id add_foreign_key :books, :authors end end
3.3.2 has_and_belongs_to_many
関連付けに対応する結合テーブルを作成する
has_and_belongs_to_many
関連付けを作成した場合は、それに対応する結合(join)テーブルを明示的に作成する必要があります。:join_table
オプションを使用して明示的に結合テーブルの名前が指定されていない場合、Active Recordは2つのクラス名を辞書の並び順に連結して、結合テーブル名を作成します。たとえばAuthorモデルとBookモデルを連結する場合、'a' は 'b' より辞書で先に出現するので "authors_books" というデフォルトの結合テーブル名が使用されます。
モデル名の並び順はString
クラスの<=>
演算子を使用して計算されます。これは、2つの文字列の長さが異なり、短い方が長い方の途中まで完全に一致しているような場合、長い方の文字列は短い方よりも辞書上の並び順が前として扱われるということです。たとえば、"paper_boxes" テーブルと "papers" テーブルがある場合、これらを結合すれば "papers_paper_boxes" となると推測されます。 "paper_boxes" の方が長いので、常識的には並び順が後ろになると予測できるからです。しかし実際の結合テーブル名は "paper_boxes_papers" になってしまいます。これはアンダースコア '_' の方が 's' よりも並びが前になっているためです。
生成された名前がどのようなものであれ、適切なマイグレーションを実行して結合テーブルを生成する必要があります。以下の関連付けを例にとって考えてみましょう。
class Assembly < ApplicationRecord has_and_belongs_to_many :parts end class Part < ApplicationRecord has_and_belongs_to_many :assemblies end
この関連付けに対応する assemblies_parts
テーブルをマイグレーションで作成し、裏付けておく必要があります。このテーブルには主キーを設定しないでください。
class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0] def change create_table :assemblies_parts, id: false do |t| t.integer :assembly_id t.integer :part_id end add_index :assemblies_parts, :assembly_id add_index :assemblies_parts, :part_id end end
このテーブルはモデルを表さないので、create_table
にid: false
を渡します。こうしておかないとこの関連付けは正常に動作しません。モデルのIDが破損する、IDの競合で例外が発生するなど、has_and_belongs_to_many
関連付けの動作が怪しい場合は、この設定を忘れていないかどうか再度確認してみてください。
create_join_table
メソッドを使用することも可能です。
class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0] def change create_join_table :assemblies, :parts do |t| t.index :assembly_id t.index :part_id end end end
3.4 関連付けのスコープ制御
デフォルトでは、関連付けによって探索されるオブジェクトは、現在のモジュールのスコープ内のものだけです。Active Recordモデルをモジュール内で宣言している場合、この点に注意する必要があります。例:
module MyApplication module Business class Supplier < ApplicationRecord has_one :account end class Account < ApplicationRecord belongs_to :supplier end end end
上のコードは正常に動作します。これは、Supplier
クラスとAccount
クラスが同じスコープ内で定義されているためです。しかし下のコードは動作しません。Supplier
クラスとAccount
クラスが異なるスコープ内で定義されているためです。
module MyApplication module Business class Supplier < ApplicationRecord has_one :account end end module Billing class Account < ApplicationRecord belongs_to :supplier end end end
あるモデルと異なる名前空間にあるモデルを関連付けるには、関連付けの宣言で完全なクラス名を指定する必要があります
module MyApplication module Business class Supplier < ApplicationRecord has_one :account, class_name: "MyApplication::Billing::Account" end end module Billing class Account < ApplicationRecord belongs_to :supplier, class_name: "MyApplication::Business::Supplier" end end end
3.5 双方向関連付け
関連付けは、通常双方向で設定します。2つのモデル両方に関連を定義する必要があります。
class Author < ApplicationRecord has_many :books end class Book < ApplicationRecord belongs_to :author end
Active Recordは関連付けの設定から、これら2つのモデルが双方向の関連を共有していることを自動的に認識します。以下に示すとおり、Active RecordはAuthor
オブジェクトのコピーを1つだけ読み出し、アプリケーションをより効率的かつ一貫性のあるデータに仕上げます。
a = Author.first b = a.books.first a.first_name == b.author.first_name # => true a.first_name = 'David' a.first_name == b.author.first_name # => true
Active Recordでは標準的な名前同士の関連付けのほとんどをサポートしていて、自動的に認識することができます。一方で、Active Recordで次のようなオプションを使った場合、双方向の関連付けが自動的に認識されないようにすることもできます。
:conditions
:through
:polymorphic
:class_name
:foreign_key
たとえば、次のようなモデルを宣言したケースを考えてみましょう。
class Author < ApplicationRecord has_many :books end class Book < ApplicationRecord belongs_to :writer, class_name: 'Author', foreign_key: 'author_id' end
この場合、Active Recordは双方向の関連付けを自動的に認識しません。
a = Author.first b = a.books.first a.first_name == b.writer.first_name # => true a.first_name = 'David' a.first_name == b.writer.first_name # => false
Active Recordは:inverse_of
オプションを提供していて、これを使うと双方向の関連付けを明示的に宣言することができます。
class Author < ApplicationRecord has_many :books, inverse_of: 'writer' end class Book < ApplicationRecord belongs_to :writer, class_name: 'Author', foreign_key: 'author_id' end
has_many
の関連付けを宣言するときに:inverse_of
オプションも含めることで、Active Recordは双方向の関連付けを認識するようになります。
a = Author.first b = a.books.first a.first_name == b.writer.first_name # => true a.first_name = 'David' a.first_name == b.writer.first_name # => true
4 関連付けの詳細情報
この節では、各関連付けの詳細を解説します。関連付けの宣言によって追加されるメソッドやオプションについても説明します。
4.1 belongs_to
関連付けの詳細
belongs_to
関連付けは、別のモデルとの間に1対1の関連付けを作成します。データベースの用語で説明すると、この関連付けが行われているクラスには外部キーがあるということです。外部キーが自分のクラスではなく相手のクラスにあるのであれば、belongs_to
ではなくhas_one
を使用する必要があります。
4.1.1 belongs_to
で追加されるメソッド
belongs_to
関連付けを宣言したクラスでは、以下の5つのメソッドを自動的に利用できるようになります。
association
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
reload_association
これらのメソッドのうち、association
の部分はプレースホルダであり、belongs_to
の最初の引数である関連付け名をシンボルにしたものに置き換えられます。例えば次のように宣言をした場合
class Book < ApplicationRecord belongs_to :author end
Book
モデルのインスタンスで以下のメソッドが使えるようになります。
author author= build_author create_author create_author! reload_author
新しく作成したhas_one
関連付けまたはbelongs_to
関連付けを初期化するには、build_
で始まるメソッドを使用する必要があります。この場合has_many
関連付けやhas_and_belongs_to_many
関連付けで使用されるassociation.build
メソッドは使用しないでください。作成するには、create_
で始まるメソッドを使用してください。
4.1.1.1 association
association
メソッドは関連付けられたオブジェクトを返します。関連付けられたオブジェクトがない場合はnil
を返します。
@author = @book.author
このオブジェクトに関連付けられたオブジェクトがデータベースから検索されたことがある場合は、キャッシュされたものを返します。この振る舞いをオーバーライドする(キャッシュを読み出さずにデータベースから直接読み込む)には、親オブジェクトが持つ#reload_association
メソッドを呼び出します。
@author = @book.reload_author
4.1.1.2 association=(associate)
association=
メソッドは、引数のオブジェクトをそのオブジェクトに関連付けます。その背後では、関連付けられたオブジェクトから主キーを取り出し、そのオブジェクトの外部キーにその同じ値を設定しています。
@book.author = @author
4.1.1.3 build_association(attributes = {})
build_association
メソッドは、関連付けられた型の新しいオブジェクトを返します。返されるオブジェクトは、渡された属性に基いてインスタンス化され、外部キーを経由するリンクが設定されます。関連付けられたオブジェクトは、値が返された時点ではまだ保存されていないことにご注意ください。
@author = @book.build_author(author_number: 123, author_name: "John Doe")
4.1.1.4 create_association(attributes = {})
create_association
メソッドは、関連付けられた型の新しいオブジェクトを返します。このオブジェクトは、渡された属性を使用してインスタンス化され、そのオブジェクトの外部キーを介してリンクが設定されます。そして、関連付けられたモデルで指定されている検証がすべてパスすると、この関連付けられたオブジェクトは保存されます。
@author = @book.create_author(author_number: 123, author_name: "John Doe")
4.1.1.5 create_association!(attributes = {})
上のcreate_association
と同じですが、レコードがinvalidの場合にActiveRecord::RecordInvalid
がraiseされる点が異なります。
4.1.2 belongs_to
のオプション
Railsのデフォルトのbelongs_to
関連付けは、ほとんどの場合カスタマイズ不要ですが、時には関連付けの動作をカスタマイズしたくなることもあると思います。これは、作成するときに渡すオプションとスコープブロックで簡単にカスタマイズできます。たとえば、以下のようなオプションを関連付けに追加できます。
class Book < ApplicationRecord belongs_to :author, dependent: :destroy, counter_cache: true end
belongs_to
関連付けでは以下のオプションがサポートされています。
:autosave
:class_name
:counter_cache
:dependent
:foreign_key
:primary_key
:inverse_of
:polymorphic
:touch
:validate
:optional
4.1.2.1 :autosave
:autosave
オプションをtrue
に設定すると、親オブジェクトが保存されるたびに、読み込まれているすべての関連付けメンバを保存し、destroyフラグが立っているメンバを破棄します。:autosave
をfalse
に設定することと、:autosave
オプションを未設定のままにしておくことは同じではありません。:autosave
が存在しない場合、関連付けられたオブジェクトのうち、新しいオブジェクトは保存されますが、更新されたオブジェクトは保存されません。
4.1.2.2 :class_name
関連名から関連相手のオブジェクト名を生成できない事情がある場合、:class_name
オプションを使用してモデル名を直接指定できます。たとえば、書籍(book)が著者(author)に従属しているが実際の著者のモデル名がPatron
である場合には、以下のように指定します。
class Book < ApplicationRecord belongs_to :author, class_name: "Patron" end
4.1.2.3 :counter_cache
:counter_cache
オプションは、従属しているオブジェクトの数の検索効率を向上させます。以下のモデルで説明します。
class Book < ApplicationRecord belongs_to :author end class Author < ApplicationRecord has_many :books end
上の宣言のままでは、@author.books.size
の値を知るためにデータベースに対してCOUNT(*)
クエリを実行する必要があります。この呼び出しを避けるために、「従属している方のモデル(belongs_to
を宣言している方のモデル)」にカウンタキャッシュを追加することができます。
class Book < ApplicationRecord belongs_to :author, counter_cache: true end class Author < ApplicationRecord has_many :books end
上のように宣言すると、キャッシュ値が最新の状態に保たれ、次にsize
メソッドが呼び出されたときにその値が返されます。
ここで1つ注意が必要です。:counter_cache
オプションはbelongs_to
宣言で指定しますが、実際に数を数えたいカラムは、相手のモデル(関連付けられているhas_many
モデル)の方に追加する必要があります。上の場合には、Author
モデルの方にbooks_count
カラムを追加する必要があります。
counter_cache
オプションでtrue
の代わりに任意のカラム名を設定すると、デフォルトのカラム名をオーバーライドできます。以下は、books_count
の代わりにcount_of_books
を設定した場合の例です。
class Book < ApplicationRecord belongs_to :author, counter_cache: :count_of_books end class Author < ApplicationRecord has_many :books end
belongs_to
の関連付けをする時に、:counter_cache
オプションを設定する必要があります。
カウンタキャッシュ用のカラムは、attr_readonly
によって読み出し専用属性となるモデルのリストに追加されます。
4.1.2.4 :dependent
もし :dependent
オプションの値が
-
:destroy
のときは、オブジェクトが削除された際に、関連付けられたオブジェクトのdestroy
メソッドが実行されます。 -
:delete
のときは、オブジェクトが削除された際に、関連付けられたオブジェクトが直接データベースから削除されます。destroy
メソッドは実行されません。
他のクラスのhas_many
関連付けとつながりのある belongs_to
関連付けに対してこのオプションを使用してはいけません。孤立したレコードがデータベースに残ってしまう可能性があります。
4.1.2.5 :foreign_key
Railsの慣例では、相手のモデルを指す外部キーを保持している結合テーブル上のカラム名については、そのモデル名にサフィックス _id
を追加した関連付け名が使用されることを前提とします。:foreign_key
オプションを使用すると外部キーの名前を直接指定することができます。
class Book < ApplicationRecord belongs_to :author, class_name: "Patron", foreign_key: "patron_id" end
Railsは外部キーのカラムを自動的に作ることはありません。外部キーを使用する場合には、マイグレーションで明示的に定義する必要があります。
4.1.2.6 :primary_key
Railsでは慣習として、id
カラムはそのテーブルの主キーとして使われます。:primary_key
オプションを指定すると、指定された別のカラムを主キーとして設定することができます
たとえば、 users
テーブルにguid
という主キーがあるとします。 todos
テーブルの外部キーであるuser_id
カラムをそのguid
カラムと紐づけたい場合は、次のようにprimary_key
を設定します。
class User < ApplicationRecord self.primary_key = 'guid' # 主キーが guid になります end class Todo < ApplicationRecord belongs_to :user, primary_key: 'guid' end
この時に@user.todos.create
を実行すると、@todo
レコードはuser_id
を@user
のguid
として持つようになります。
4.1.2.7 :inverse_of
:inverse_of
オプションは、その関連付けの逆関連付けとなるhas_many
関連付けまたはhas_one
関連付けの名前を指定します。:polymorphic
オプションと組み合わせた場合は無効です。
class Author < ApplicationRecord has_many :books, inverse_of: :author end class Book < ApplicationRecord belongs_to :author, inverse_of: :books end
4.1.2.8 :polymorphic
:polymorphic
オプションにtrue
を指定すると、ポリモーフィック関連付けを指定できます。ポリモーフィック関連付けの詳細についてはこのガイドの説明を参照してください。
4.1.2.9 :touch
:touch
オプションをtrue
に設定すると、関連付けられているオブジェクトが保存またはdestroyされるたびに、そのオブジェクトのupdated_at
またはupdated_on
タイムスタンプが現在時刻に設定されます。
class Book < ApplicationRecord belongs_to :author, touch: true end class Author < ApplicationRecord has_many :books end
上の例の場合、Bookクラスは、関連付けられているAuthorのタイムスタンプを保存時またはdestroy時に更新します。更新時に特定のタイムスタンプ属性を指定することもできます。
class Book < ApplicationRecord belongs_to :author, touch: :books_updated_at end
4.1.2.10 :validate
:validate
オプションをtrue
に設定すると、関連付けられたオブジェクトが保存時に必ず検証(validation)されます。デフォルトはfalse
であり、この場合関連付けられたオブジェクトは保存時に検証されません。
4.1.2.11 :optional
:optional
オプションをtrue
に設定すると、関連付けされたオブジェクトの存在性のバリデーションが実行されないようになります。デフォルトではこのオプションはfalse
となっています。
4.1.3 belongs_to
のスコープ
場合によってはbelongs_to
で使用されるクエリをカスタマイズしたくなることがあります。スコープブロックを使用してこのようなカスタマイズを行うことができます。以下に例を示します。
class Book < ApplicationRecord belongs_to :author, -> { where active: true }, dependent: :destroy end
スコープブロック内では標準のクエリメソッドをすべて使用できます。ここでは以下について説明します。
where
includes
readonly
select
4.1.3.1 where
where
は、関連付けられるオブジェクトが満たすべき条件を指定します。
class book < ApplicationRecord belongs_to :author, -> { where active: true } end
4.1.3.2 includes
includes
メソッドを使用すると、その関連付けが使用されるときにeager-load (訳注:preloadとは異なる)しておきたい第2関連付けを指定することができます。以下のモデルを例にとって考えてみましょう。
class LineItem < ApplicationRecord belongs_to :book end class Book < ApplicationRecord belongs_to :author has_many :line_items end class Author < ApplicationRecord has_many :books end
LineItemから著者名(Author)を@line_item.book.author
のように直接取り出す機会が頻繁にある場合は、LineItemとBookの関連付けを行なう時にAuthorをあらかじめincludeしておくことで無駄なクエリを減らし、効率を高めることができます。
class LineItem < ApplicationRecord belongs_to :book, -> { includes :author } end class Book < ApplicationRecord belongs_to :author has_many :line_items end class Author < ApplicationRecord has_many :books end
直接の関連付けではincludes
を使用する必要はありません。Book belongs_to :author
のような直接の関連付けでは必要に応じて自動的にeager-loadされます。
4.1.3.3 readonly
readonly
を指定すると、関連付けられたオブジェクトから取り出した内容は読み出し専用になります。
4.1.3.4 select
select
メソッドを使用すると、関連付けられたオブジェクトのデータ取り出しに使用されるSQLのSELECT
句を上書きします。Railsはデフォルトではすべてのカラムを取り出します。
select
をbelongs_to
関連付けで使用する場合、正しい結果を得るために:foreign_key
オプションを必ず設定してください。
4.1.4 関連付けられたオブジェクトが存在するかどうかを確認する
association.nil?
メソッドを使用して、関連付けられたオブジェクトが存在するかどうかを確認できます。
if @book.author.nil? @msg = "No author found for this book" end
4.1.5 オブジェクトが保存されるタイミング
オブジェクトをbelongs_to
関連付けに割り当てても、そのオブジェクトが自動的に保存されるわけではありません。関連付けられたオブジェクトが保存されることもありません。
4.2 has_one
関連付けの詳細
has_one
関連付けは他のモデルと1対1対応します。データベースの観点では、この関連付けでは相手のクラスが外部キーを持ちます。相手ではなく自分のクラスが外部キーを持っているのであれば、belongs_to
を使うべきです。
4.2.1 has_one
で追加されるメソッド
has_one
関連付けを宣言したクラスでは、以下の5つのメソッドを自動的に利用できるようになります。
association
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
これらのメソッドのうち、association
の部分はプレースホルダであり、has_one
の最初の引数である関連付け名をシンボルにしたものに置き換えられます。たとえば以下の宣言を見てみましょう。
class Supplier < ApplicationRecord has_one :account end
これにより、Supplier
モデルのインスタンスで以下のメソッドが使えるようになります。
account account= build_account create_account create_account!
新しく作成したhas_one
関連付けまたはbelongs_to
関連付けを初期化するには、build_
で始まるメソッドを使用する必要があります。この場合has_many
関連付けやhas_and_belongs_to_many
関連付けで使用されるassociation.build
メソッドは使用しないでください。作成するには、create_
で始まるメソッドを使用してください。
4.2.1.1 association
association
メソッドは関連付けられたオブジェクトを返します。関連付けられたオブジェクトがない場合はnil
を返します。
@account = @supplier.account
関連付けられたオブジェクトがデータベースから検索されたことがある場合は、キャッシュされたものを返します。キャッシュを読み出さずにデータベースから直接読み込ませたい場合は、親オブジェクトが持つ#reload
メソッドを呼び出します。
@account = @supplier.reload.account
4.2.1.2 association=(associate)
association=
メソッドは、引数のオブジェクトをそのオブジェクトに関連付けます。その背後では、そのオブジェクトから主キーを取り出し、関連付けるオブジェクトの外部キーの値をその主キーと同じ値にします。
@supplier.account = @account
4.2.1.3 build_association(attributes = {})
build_association
メソッドは、関連付けられた型の新しいオブジェクトを返します。このオブジェクトは、渡された属性でインスタンス化され、そのオブジェクトの外部キーを介してリンクが設定されます。ただし、関連付けられたオブジェクトはまだ保存されません。
@account = @supplier.build_account(terms: "Net 30")
4.2.1.4 create_association(attributes = {})
create_association
メソッドは、関連付けられた型の新しいオブジェクトを返します。このオブジェクトは、渡された属性を使用してインスタンス化され、そのオブジェクトの外部キーを介してリンクが設定されます。そして、関連付けられたモデルで指定されている検証がすべてパスすると、この関連付けられたオブジェクトは保存されます。
@account = @supplier.create_account(terms: "Net 30")
4.2.1.5 create_association!(attributes = {})
上のcreate_association
と同じですが、レコードがinvalidの場合にActiveRecord::RecordInvalid
がraiseされる点が異なります。
4.2.2 has_one
のオプション
Railsのデフォルトのhas_one
関連付けは、ほとんどの場合カスタマイズ不要ですが、時には関連付けの動作をカスタマイズしたくなることもあると思います。これは、作成するときにオプションを渡すことで簡単にカスタマイズできます。たとえば、以下のようなオプションを関連付けに追加できます。
class Supplier < ApplicationRecord has_one :account, class_name: "Billing", dependent: :nullify end
has_one
関連付けでは以下のオプションがサポートされます。
:as
:autosave
:class_name
:dependent
:foreign_key
:inverse_of
:primary_key
:source
:source_type
:through
:validate
4.2.2.1 :as
:as
オプションを設定すると、ポリモーフィック関連付けを指定できます。ポリモーフィック関連付けの詳細についてはこのガイドの説明を参照してください。
4.2.2.2 :autosave
:autosave
オプションをtrue
に設定すると、親オブジェクトが保存されるたびに、読み込まれているすべてのメンバを保存し、destroyフラグが立っているメンバを破棄します。:autosave
をfalse
に設定することと、:autosave
オプションを未設定のままにしておくことは同じではありません。:autosave
が存在しない場合、関連付けられたオブジェクトのうち、新しいオブジェクトは保存されますが、更新されたオブジェクトは保存されません。
4.2.2.3 :class_name
関連名から関連相手のオブジェクト名を生成できない事情がある場合、:class_name
オプションを使用してモデル名を直接指定できます。たとえば、Supplierにアカウントが1つあり、アカウントを含むモデルの実際の名前がAccount
ではなくBilling
になっている場合、以下のようにモデル名を指定できます。
class Supplier < ApplicationRecord has_one :account, class_name: "Billing" end
4.2.2.4 :dependent
オーナーオブジェクトがdestroyされた時に、それに関連付けられたオブジェクトをどうするかを制御します。
-
:destroy
を指定すると、関連付けられたオブジェクトも同時にdestroyされます。 -
:delete
を指定すると、関連付けられたオブジェクトはデータベースから直接削除されます。このときコールバックは実行されません。 -
:nullify
を指定すると、外部キーがNULL
に設定されます。このときコールバックは実行されません。 -
:restrict_with_exception
を指定すると、関連付けられたレコードがある場合に例外が発生します。 -
:restrict_with_error
を指定すると、関連付けられたオブジェクトがある場合にエラーがオーナーに追加されます。
NOT NULL
データベース制約のある関連付けでは、:nullify
オプションを与えないようにする必要があります。そのような関連付けをdestroyするdependent
を設定しなかった場合、関連付けられたオブジェクトを変更できなくなってしまいます。これは、最初に関連付けられたオブジェクトの外部キーがNULL
値になってしまい、この値は許されていないためです。
4.2.2.5 :foreign_key
Railsの慣例では、相手のモデル上の外部キーを保持しているカラム名については、そのモデル名にサフィックス _id
を追加した関連付け名が使用されることを前提とします。:foreign_key
オプションを使用すると外部キーの名前を直接指定することができます。
class Supplier < ApplicationRecord has_one :account, foreign_key: "supp_id" end
Railsは外部キーのカラムを自動的に作ることはありません。外部キーを使用する場合には、マイグレーションで明示的に定義する必要があります。
4.2.2.6 :inverse_of
:inverse_of
オプションは、その関連付けの逆関連付けとなるbelongs_to
関連付けの名前を指定します。:through
または:as
オプションと組み合わせた場合は無効です。
class Supplier < ApplicationRecord has_one :account, inverse_of: :supplier end class Account < ApplicationRecord belongs_to :supplier, inverse_of: :account end
4.2.2.7 :primary_key
Railsの慣例では、モデルの主キーはid
カラムに保存されていることを前提とします。:primary_key
オプションで主キーを明示的に指定することでこれを上書きすることができます。
4.2.2.8 :source
:source
オプションは、has_one :through
関連付けにおける「ソースの」関連付け名、つまり関連付け元の名前を指定します。
4.2.2.9 :source_type
:source_type
オプションは、ポリモーフィック関連付けを介して行われるhas_one :through
関連付けにおける「ソースの」関連付けタイプ、つまり関連付け元のタイプを指定します。
4.2.2.10 :through
:through
オプションは、このガイドで既に説明したhas_one :through
関連付けのクエリを実行する際に経由する結合モデルを指定します。
4.2.2.11 :validate
:validate
オプションをtrue
に設定すると、関連付けられたオブジェクトが保存時に必ず検証(validation)されます。デフォルトはfalse
であり、この場合関連付けられたオブジェクトは保存時に検証されません。
4.2.3 has_one
のスコープについて
場合によってはhas_one
で使用されるクエリをカスタマイズしたくなることがあります。スコープブロックを使用してこのようなカスタマイズを行うことができます。以下に例を示します。
class Supplier < ApplicationRecord has_one :account, -> { where active: true } end
スコープブロック内では標準のクエリメソッドをすべて使用できます。ここでは以下について説明します。
where
includes
readonly
select
4.2.3.1 where
where
は、関連付けられるオブジェクトが満たすべき条件を指定します。
class Supplier < ApplicationRecord has_one :account, -> { where "confirmed = 1" } end
4.2.3.2 includes
includes
メソッドを使用すると、その関連付けが使用されるときにeager-load (訳注:preloadとは異なる)しておきたい第2関連付けを指定することができます。以下のモデルを例にとって考えてみましょう。
class Supplier < ApplicationRecord has_one :account end class Account < ApplicationRecord belongs_to :supplier belongs_to :representative end class Representative < ApplicationRecord has_many :accounts end
上の例で、Supplierから代表(Representative)を@supplier.account.representative
のように直接取り出す機会が頻繁にあるのであれば、SupplierからAccountへの関連付けにRepresentativeをあらかじめincludeしておくことで無駄なクエリを減らし、効率を高めることができます。
class Supplier < ApplicationRecord has_one :account, -> { includes :representative } end class Account < ApplicationRecord belongs_to :supplier belongs_to :representative end class Representative < ApplicationRecord has_many :accounts end
4.2.3.3 readonly
readonly
を指定すると、関連付けられたオブジェクトを取り出すときに読み出し専用になります。
4.2.3.4 select
select
メソッドを使用すると、関連付けられたオブジェクトのデータ取り出しに使用されるSQLのSELECT
句を上書きします。Railsはデフォルトではすべてのカラムを取り出します。
4.2.4 関連付けられたオブジェクトが存在するかどうかを確認する
association.nil?
メソッドを使用して、関連付けられたオブジェクトが存在するかどうかを確認できます。
if @supplier.account.nil? @msg = "No account found for this supplier" end
4.2.5 オブジェクトが保存されるタイミング
has_one
関連付けにオブジェクトをアサインすると、外部キーを更新するためにそのオブジェクトは自動的に保存されます。さらに、置き換えられるオブジェクトは、これは外部キーが変更されたことによってすべて自動的に保存されます。
関連付けられているオブジェクト同士のいずれか一方が検証(validation)のために保存に失敗すると、アサインの状態からはfalse
が返され、アサインはキャンセルされます。
親オブジェクト(has_one
関連付けを宣言している側のオブジェクト)が保存されない場合(つまりnew_record?
がtrue
を返す場合)、子オブジェクトは追加時に保存されません。親オブジェクトが保存された場合は、子オブジェクトは保存されます。
has_one
関連付けにオブジェクトをアサインし、しかもそのオブジェクトを保存したくない場合、build_association
メソッドを使用してください。
4.3 has_many
関連付けの詳細
has_many
関連付けは、他のモデルとの間に「1対多」のつながりを作成します。データベースの観点では、この関連付けにおいては相手のクラスが外部キーを持ちます。この外部キーは相手のクラスのインスタンスを参照します。
4.3.1 has_many
で追加されるメソッド
has_many
関連付けを宣言したクラスでは、以下の16のメソッドを自動的に利用できるようになります。
collection
collection<<(object, ...)
collection.delete(object, ...)
collection.destroy(object, ...)
collection=(objects)
collection_singular_ids
collection_singular_ids=(ids)
collection.clear
collection.empty?
collection.size
collection.find(...)
collection.where(...)
collection.exists?(...)
collection.build(attributes = {}, ...)
collection.create(attributes = {})
collection.create!(attributes = {})
collection.reload
上のメソッドのcollection
の部分はプレースホルダであり、実際にはhas_many
への1番目の引数として渡されたシンボルに置き換えられます。また、collection_singular
の部分はシンボルの単数形に置き換えられます。たとえば以下の宣言を見てみましょう。
class Author < ApplicationRecord has_many :books end
これにより、Author
モデルのインスタンスで以下のメソッドが使えるようになります。
books books<<(object, ...) books.delete(object, ...) books.destroy(object, ...) books=(objects) book_ids book_ids=(ids) books.clear books.empty? books.size books.find(...) books.where(...) books.exists?(...) books.build(attributes = {}, ...) books.create(attributes = {}) books.create!(attributes = {}) books.reload
4.3.1.1 collection
collection
メソッドは、関連付けられたすべてのオブジェクトのリレーションを返します。関連付けられたオブジェクトがない場合は、空のリレーションを1つ返します。
@books = @author.books
4.3.1.2 collection<<(object, ...)
collection<<
メソッドは、1つ以上のオブジェクトをコレクションに追加します。このとき、追加されるオブジェクトの外部キーは、呼び出し側モデルの主キーに設定されます。
@author.books << @book1
4.3.1.3 collection.delete(object, ...)
collection.delete
メソッドは、外部キーをNULL
に設定することで、コレクションから1つまたは複数のオブジェクトを削除します。
@author.books.delete(@book1)
削除のされ方はこれだけではありません。オブジェクト同士がdependent: :destroy
で関連付けられている場合はdestroyされますが、オブジェクト同士がdependent: :delete_all
で関連付けられている場合はdeleteされますのでご注意ください。
4.3.1.4 collection.destroy(object, ...)
collection.destroy
は、コレクションに関連付けられているオブジェクトに対してdestroy
を実行することで、コレクションから1つまたは複数のオブジェクトを削除します。
@author.books.destroy(@book1)
この場合オブジェクトは無条件でデータベースから削除されます。このとき、:dependent
オプションがどのように設定されていても無視して削除が行われます。
4.3.1.5 collection=(objects)
collection=
メソッドは、指定したオブジェクトでそのコレクションの内容を置き換えます。元からあったオブジェクトは削除されます。この変更はデータベースの中で存続します。
4.3.1.6 collection_singular_ids
collection_singular_ids
メソッドは、そのコレクションに含まれるオブジェクトのidを配列にしたものを返します。
@book_ids = @author.book_ids
4.3.1.7 collection_singular_ids=(ids)
collection_singular_ids=
メソッドは、指定された主キーidを持つオブジェクトの集まりでコレクションの内容を置き換えます。元からあったオブジェクトは削除されます。この変更はデータベースで永続化されます。
4.3.1.8 collection.clear
collection.clear
メソッドは、dependent
オプションによって指定された手法に従って、コレクションからすべてのオブジェクトを削除します。もしオプションが渡されていなかった場合、デフォルトの手法に従います。デフォルトでは、has_many :through
の関連付けの場合はdelete_all
が渡され、has_many
の関連付けの場合は外部キーにNULL
がセットされます。
@author.books.clear
dependent: :delete_all
の場合と同様に、オブジェクトがdependent: :destroy
で関連付けされていた場合、それらのオブジェクトは削除されます。
4.3.1.9 collection.empty?
collection.empty?
メソッドは、関連付けられたオブジェクトがコレクションの中に1つもない場合にtrue
を返します。
<% if @author.books.empty? %> No Books Found <% end %>
4.3.1.10 collection.size
collection.size
メソッドは、コレクションに含まれるオブジェクトの数を返します。
@book_count = @author.books.size
4.3.1.11 collection.find(...)
collection.find
メソッドは、コレクションに含まれるオブジェクトを検索します。このメソッドで使用される文法は、ActiveRecord::Base.find
で使用されているものと同じです。
@available_book = @author.books.find(1)
4.3.1.12 collection.where(...)
collection.where
メソッドは、コレクションに含まれているメソッドを指定された条件に基いて検索します。このメソッドではオブジェクトは遅延読み込み(lazy load)される点にご注意ください。つまり、オブジェクトに実際にアクセスが行われる時にだけデータベースへのクエリが発生します。
@available_books = @author.books.where(available: true) # No query yet @available_book = @available_books.first # Now the database will be queried
4.3.1.13 collection.exists?(...)
collection.exists?
メソッドは、指定された条件に合うオブジェクトがコレクションの中に存在するかどうかをチェックします。このメソッドで使用される文法は、ActiveRecord::Base.exists?
で使用されているものと同じです。
4.3.1.14 collection.build(attributes = {}, ...)
collection.build
メソッドは、関連付けが行われた1つのオブジェクトまたはオブジェクトの配列を返します。返されるオブジェクトは、渡された属性に基いてインスタンス化され、外部キーを経由するリンクが作成されます。関連付けられたオブジェクトは、値が返された時点ではまだ保存されていないことにご注意ください。
@book = @author.books.build(published_at: Time.now, book_number: "A12345") @books = @author.books.build([ { published_at: Time.now, book_number: "A12346" }, { published_at: Time.now, book_number: "A12347" } ...
4.3.1.15 collection.create(attributes = {})
collection.create
メソッドは、関連付けが行われた新しい1つのオブジェクトまたは新しいオブジェクトの配列を返します。このオブジェクトは、渡された属性を使用してインスタンス化され、そのオブジェクトの外部キーを介してリンクが作成されます。そして、関連付けられたモデルで指定されている検証がすべてパスすると、この関連付けられたオブジェクトは保存されます。
@book = @author.books.create(published_at: Time.now, book_number: "A12345") @books = @author.books.create([ { published_at: Time.now, book_number: "A12346" }, { published_at: Time.now, book_number: "A12347" } ...
4.3.1.16 collection.create!(attributes = {})
上のcollection.create
と同じですが、レコードがinvalidの場合にActiveRecord::RecordInvalid
がraiseされる点が異なります。
4.3.1.17 collection.reload
collection.reload
は、関連付けられたすべてのオブジェクトのリレーションを返し、データベースから強制的に読み込みます。関連付けられたオブジェクトがない場合は、空のリレーションを1つ返します。
@books = @author.books.reload
4.3.2 has_many
のオプション
Railsのデフォルトのhas_many
関連付けは、ほとんどの場合カスタマイズ不要ですが、時には関連付けの動作をカスタマイズしたくなることもあると思います。これは、作成するときにオプションを渡すことで簡単にカスタマイズできます。たとえば、以下のようなオプションを関連付けに追加できます。
class Author < ApplicationRecord has_many :books, dependent: :delete_all, validate: false end
has_many
関連付けでは以下のオプションがサポートされます。
:as
:autosave
:class_name
:counter_cache
:dependent
:foreign_key
:inverse_of
:primary_key
:source
:source_type
:through
:validate
4.3.2.1 :as
:as
オプションを設定すると、ポリモーフィック関連付けであることが指定されます。(このガイドの説明を参照)
4.3.2.2 :autosave
:autosave
オプションをtrue
に設定すると、親オブジェクトが保存されるたびに、読み込まれているすべてのメンバを保存し、destroyフラグが立っているメンバを破棄します。:autosave
をfalse
に設定することと、:autosave
オプションを未設定のままにしておくことは同じではありません。:autosave
が存在しない場合、関連付けられたオブジェクトのうち、新しいオブジェクトは保存されますが、更新されたオブジェクトは保存されません。
4.3.2.3 :class_name
関連名から関連相手のオブジェクト名を生成できない事情がある場合、:class_name
オプションを使用してモデル名を直接指定できます。たとえば、1人の著者(author)が複数の書籍(books)を持っているが、実際の書籍モデル名がTransaction
である場合には以下のように指定します。
class Author < ApplicationRecord has_many :books, class_name: "Transaction" end
4.3.2.4 :counter_cache
このオプションは、:counter_cache
オプションを任意の名前に変更したい場合に使います。このオプションは、belongs_toの関連付けで:counter_cache
の名前を変更したときにのみ必要になります。
4.3.2.5 :dependent
オーナーオブジェクトがdestroyされたときに、オーナーに関連付けられたオブジェクトをどうするかを制御します。
-
:destroy
を指定すると、関連付けられたオブジェクトもすべて同時にdestroyされます。 -
:delete_all
を指定すると、関連付けられたオブジェクトはすべてデータベースから直接削除されます。このときコールバックは実行されません。 -
:nullify
を指定すると、外部キーはすべてNULL
に設定されます。このときコールバックは実行されません。 -
:restrict_with_exception
を指定すると、関連付けられたレコードが1つでもある場合に例外が発生します。 -
:restrict_with_error
を指定すると、関連付けられたオブジェクトが1つでもある場合にエラーがオーナーに追加されます。
4.3.2.6 :foreign_key
Railsの慣例では、相手のモデル上の外部キーを保持しているカラム名については、そのモデル名にサフィックス _id
を追加した関連付け名が使用されることを前提とします。:foreign_key
オプションを使用すると外部キーの名前を直接指定することができます。
class Author < ApplicationRecord has_many :books, foreign_key: "cust_id" end
Railsは外部キーのカラムを自動的に作ることはありません。外部キーを使用する場合には、マイグレーションで明示的に定義する必要があります。
4.3.2.7 :inverse_of
:inverse_of
オプションは、その関連付けの逆関連付けとなるbelongs_to
関連付けの名前を指定します。:through
または:as
オプションと組み合わせた場合は無効です。
class Author < ApplicationRecord has_many :books, inverse_of: :author end class Book < ApplicationRecord belongs_to :author, inverse_of: :books end
4.3.2.8 :primary_key
Railsの慣例では、関連付けの主キーはid
カラムに保存されていることを前提とします。:primary_key
オプションで主キーを明示的に指定することでこれを上書きすることができます。
users
テーブルに主キーとしてid
カラムがあり、その他にguid
カラムもあるとします。要件として、todos
テーブルが (id
ではなく) guid
カラムの値を外部キーとして使いたいとします。これは以下のようにすることで実現できます。
class User < ApplicationRecord has_many :todos, primary_key: :guid end
このとき @todo = @user.todos.create
を実行すると、@todo
レコードのuser_id
の値は @user
のguid
になります。
4.3.2.9 :source
:source
オプションは、has_many :through
関連付けにおける「ソースの」関連付け名、つまり関連付け元の名前を指定します。このオプションは、関連付け名から関連付け元の名前が自動的に推論できない場合以外には使用する必要はありません。
4.3.2.10 :source_type
:source_type
オプションは、ポリモーフィック関連付けを介して行われるhas_many :through
関連付けにおける「ソースの」関連付けタイプ、つまり関連付け元のタイプを指定します。
4.3.2.11 :through
:through
オプションは、このガイドで既に説明したhas_one :through
関連付けのクエリを実行する際に経由する結合モデルを指定します。
4.3.2.12 :validate
:validate
オプションをfalse
に設定すると、関連付けられたオブジェクトは保存時に検証(validation)されません。デフォルトはtrue
であり、この場合関連付けられたオブジェクトは保存時に検証されます。
4.3.3 has_many
のスコープについて
場合によってはhas_many
で使用されるクエリをカスタマイズしたくなることがあります。スコープブロックを使用してこのようなカスタマイズを行うことができます。以下に例を示します。
class Author < ApplicationRecord has_many :books, -> { where processed: true } end
スコープブロック内では標準のクエリメソッドをすべて使用できます。ここでは以下について説明します。
where
extending
group
includes
limit
offset
order
readonly
select
distinct
4.3.3.1 where
where
は、関連付けられるオブジェクトが満たすべき条件を指定します。
class Author < ApplicationRecord has_many :confirmed_books, -> { where "confirmed = 1" }, class_name: "Book" end
条件はハッシュを使用して指定することもできます。
class Author < ApplicationRecord has_many :confirmed_books, -> { where confirmed: true }, class_name: "Book" end
where
オプションでハッシュを使用した場合、この関連付けで作成されたレコードは自動的にこのハッシュを使用したスコープに含まれるようになります。この例の場合、@author.confirmed_books.create
または@author.confirmed_books.build
を実行すると、confirmedカラムの値がtrue
の書籍(book)が常に作成されます。
4.3.3.2 extending
extending
メソッドは、関連付けプロキシを拡張する名前付きモジュールを指定します。関連付けの拡張については後述します。
4.3.3.3 group
group
メソッドは、結果をグループ化する際の属性名を1つ指定します。内部的にはSQLのGROUP BY
句が使用されます。
class Author < ApplicationRecord has_many :line_items, -> { group 'books.id' }, through: :books end
4.3.3.4 includes
includes
メソッドを使用すると、その関連付けが使用されるときにeager-load (訳注:preloadとは異なる)しておきたい第2関連付けを指定することができます。以下のモデルを例にとって考えてみましょう。
class Author < ApplicationRecord has_many :books end class Book < ApplicationRecord belongs_to :author has_many :line_items end class LineItem < ApplicationRecord belongs_to :book end
著者名(Author)からLineItemを@author.books.line_items
のように直接取り出す機会が頻繁にあるのであれば、AuthorとBookの関連付けを行なう時にLineItemをあらかじめincludeしておくことで無駄なクエリを減らし、効率を高めることができます。
class Author < ApplicationRecord has_many :books, -> { includes :line_items } end class Book < ApplicationRecord belongs_to :author has_many :line_items end class LineItem < ApplicationRecord belongs_to :book end
4.3.3.5 limit
limit
メソッドは、関連付けを使用して取得できるオブジェクトの総数を制限するのに使用します。
class Author < ApplicationRecord has_many :recent_books, -> { order('published_at desc').limit(100) }, class_name: "Book" end
4.3.3.6 offset
offset
メソッドは、関連付けを使用してオブジェクトを取得する際の開始オフセットを指定します。たとえば、-> { offset(11) }
と指定すると、最初の11レコードはスキップされ、12レコード目から返されるようになります。
4.3.3.7 order
order
メソッドは、関連付けられたオブジェクトに与えられる順序を指定します。内部的にはSQLのORDER BY
句が使用されます。
class Author < ApplicationRecord has_many :books, -> { order "date_confirmed DESC" } end
4.3.3.8 readonly
readonly
を指定すると、関連付けられたオブジェクトを取り出すときに読み出し専用になります。
4.3.3.9 select
select
メソッドを使用すると、関連付けられたオブジェクトのデータ取り出しに使用されるSQLのSELECT
句を上書きします。Railsはデフォルトではすべてのカラムを取り出します。
独自のselect
メソッドを使用する場合には、関連付けられているモデルの主キーカラムと外部キーカラムを必ず含めておいてください。これを行わなかった場合、Railsでエラーが発生します。
4.3.3.10 distinct
distinct
メソッドは、コレクション内で重複が発生しないようにします。
このメソッドは:through
オプションと併用するときに特に便利です。
class Person < ApplicationRecord has_many :readings has_many :articles, through: :readings end person = Person.create(name: 'John') article = Article.create(name: 'a1') person.articles << article person.articles << article person.articles.inspect # => [#<Article id: 5, name: "a1">, #<Article id: 5, name: "a1">] Reading.all.inspect # => [#<Reading id: 12, person_id: 5, article_id: 5>, #<Reading id: 13, person_id: 5, article_id: 5>]
上の例の場合、readingが2つあって重複しており、person.articles
を実行すると、どちらも同じ記事を指しているにもかかわらず、両方とも取り出されてしまいます。
今度はdistinct
を設定してみましょう。
class Person has_many :readings has_many :articles, -> { distinct }, through: :readings end person = Person.create(name: 'Honda') article = Article.create(name: 'a1') person.articles << article person.articles << article person.articles.inspect # => [#<Article id: 7, name: "a1">] Reading.all.inspect # => [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>]
上の例でもreadingは2つあって重複しています。一方で、person.articles
を実行すると、1つのarticleのみを表示します。これはコレクションが一意のレコードのみを読み出しているからです。
挿入時にも同様に、現在残っているすべてのレコードが一意であるようにする(関連付けを検査したときに重複レコードが決して発生しないようにする)には、テーブル自体に一意のインデックスを追加する必要があります。たとえば、readings
というテーブルがあり、その記事を1人の人物(person)にのみ追加できるようにしたい場合、マイグレーションに以下を追加します。
add_index :readings, [:person_id, :article_id], unique: true
一度ユニークなインデックスを持つと、ある記事をpersonに2回追加したときに
ActiveRecord::RecordNotUnique
エラーが発生するようになります
person = Person.create(name: 'Honda') article = Article.create(name: 'a1') person.articles << article person.articles << article # => ActiveRecord::RecordNotUnique
なお、include?
などを使用して一意性をチェックすると競合が発生しやすいので注意が必要です。関連付けで強制的に一意になるようにするためにinclude?
を使用しないでください。たとえば上のarticleを例にとると、以下のコードでは競合が発生しやすくなります。これは、複数のユーザーが同時にこのコードを実行する可能性があるためです。
person.articles << article unless person.articles.include?(article)
4.3.4 オブジェクトが保存されるタイミング
has_many
関連付けにオブジェクトをアサインすると、外部キーを更新するためにそのオブジェクトは自動的に保存されます。1つの文で複数のオブジェクトをアサインすると、それらはすべて保存されます。
関連付けられているオブジェクトの1つでも検証(validation)エラーで保存に失敗すると、アサインの式からはfalse
が返され、アサインはキャンセルされます。
親オブジェクト(has_many
関連付けを宣言している側のオブジェクト)が保存されない場合(つまりnew_record?
がtrue
を返す場合)、子オブジェクトは追加時に保存されません。親オブジェクトが保存されると、関連付けられていたオブジェクトのうち保存されていなかったメンバはすべて保存されます。
has_many
関連付けにオブジェクトをアサインし、しかもそのオブジェクトを保存したくない場合、collection.build
メソッドを使用してください。
4.4 has_and_belongs_to_many
関連付けの詳細
has_and_belongs_to_many
関連付けは、他のモデルとの間に「多対多」のつながりを作成します。データベースの観点では、2つのクラスは中間で結合テーブルを介して関連付けられます。この結合テーブルには、両方のクラスを指す外部キーがそれぞれ含まれます。
4.4.1 has_and_belongs_to_many
で追加されるメソッド
has_and_belongs_to_many
関連付けを宣言したクラスでは、以下の16のメソッドを自動的に利用できるようになります。
collection
collection<<(object, ...)
collection.delete(object, ...)
collection.destroy(object, ...)
collection=(objects)
collection_singular_ids
collection_singular_ids=(ids)
collection.clear
collection.empty?
collection.size
collection.find(...)
collection.where(...)
collection.exists?(...)
collection.build(attributes = {})
collection.create(attributes = {})
collection.create!(attributes = {})
collection.reload
上のメソッドのcollection
の部分はプレースホルダであり、実際にはhas_and_belongs_to_many
への1番目の引数として渡されたシンボルに置き換えられます。また、collection_singular
の部分はシンボルの単数形に置き換えられます。たとえば以下の宣言を見てみましょう。
class Part < ApplicationRecord has_and_belongs_to_many :assemblies end
これにより、Part
モデルのインスタンスで以下のメソッドが使えるようになります。
assemblies assemblies<<(object, ...) assemblies.delete(object, ...) assemblies.destroy(object, ...) assemblies=(objects) assembly_ids assembly_ids=(ids) assemblies.clear assemblies.empty? assemblies.size assemblies.find(...) assemblies.where(...) assemblies.exists?(...) assemblies.build(attributes = {}, ...) assemblies.create(attributes = {}) assemblies.create!(attributes = {}) assemblies.reload
4.4.1.1 追加のカラムメソッド
has_and_belongs_to_many
関連付けで使用している中間の結合テーブルが、2つの外部キー以外に何かカラムを含んでいる場合、これらのカラムは関連付けを介して取り出されるレコードに属性として追加されます。属性が追加されたレコードは常に読み出し専用になります。このようにして読み出された属性に対する変更は保存できないためです。
has_and_belongs_to_many
関連付けで使用する結合テーブルにこのような余分なカラムを追加することはお勧めできません。2つのモデルを多対多で結合する結合テーブルでこのような複雑な振る舞いが必要になるのであれば、has_and_belongs_to_many
ではなくhas_many :through
を使用してください。
4.4.1.2 collection
collection
メソッドは、関連付けられたすべてのオブジェクトのリレーションを返します。関連付けられたオブジェクトがない場合は、空のリレーションを1つ返します。
@assemblies = @part.assemblies
4.4.1.3 collection<<(object, ...)
collection<<
メソッドは、結合テーブル上でレコードを作成し、それによって1つまたは複数のオブジェクトをコレクションに追加します。
@part.assemblies << @assembly1
このメソッドはcollection.concat
およびcollection.push
のエイリアスです。
4.4.1.4 collection.delete(object, ...)
collection.delete
メソッドは、結合テーブル上のレコードを削除し、それによって1つまたは複数のオブジェクトをコレクションから削除します。このメソッドを実行してもオブジェクトはdestroyされません。
@part.assemblies.delete(@assembly1)
4.4.1.5 collection.destroy(object, ...)
collection.destroy
メソッドは、結合テーブル上のレコードを削除することで、1つまたは複数のオブジェクトをコレクションから削除します。このメソッドを実行してもオブジェクトはdestroyされません。
@part.assemblies.destroy(@assembly1)
4.4.1.6 collection=(objects)
collection=
メソッドは、指定したオブジェクトでそのコレクションの内容を置き換えます。元からあったオブジェクトは削除されます。この変更はデータベースで永続化されます。
4.4.1.7 collection_singular_ids
collection_singular_ids
メソッドは、そのコレクションに含まれるオブジェクトのidを配列にしたものを返します。
@assembly_ids = @part.assembly_ids
4.4.1.8 collection_singular_ids=(ids)
collection_singular_ids=
メソッドは、指定された主キーidを持つオブジェクトの集まりでコレクションの内容を置き換えます。元からあったオブジェクトは削除されます。この変更はデータベースで永続化されます。
4.4.1.9 collection.clear
collection.clear
メソッドは、結合テーブル上のレコードを削除し、それによってすべてのオブジェクトをコレクションから削除します。このメソッドを実行しても、関連付けられたオブジェクトはdestroyされません。
4.4.1.10 collection.empty?
collection.empty?
メソッドは、関連付けられたオブジェクトがコレクションに含まれていない場合にtrue
を返します。
<% if @part.assemblies.empty? %> ※この部分はどのアセンブリでも使用されません。 <% end %>
4.4.1.11 collection.size
collection.size
メソッドは、コレクションに含まれるオブジェクトの数を返します。
@assembly_count = @part.assemblies.size
4.4.1.12 collection.find(...)
collection.find
メソッドは、コレクションに含まれるオブジェクトを検索します。このメソッドで使用される文法は、ActiveRecord::Base.find
で使用されているものと同じです。このメソッドでは、オブジェクトがコレクション内で従う必要のある追加条件も加味されます。
@assembly = @part.assemblies.find(1)
4.4.1.13 collection.where(...)
collection.where
メソッドは、コレクションに含まれているメソッドを指定された条件に基いて検索します。このメソッドではオブジェクトは遅延読み込み(lazy load)される点にご注意ください。つまり、オブジェクトに実際にアクセスが行われる時にだけデータベースへのクエリが発生します。このメソッドでは、オブジェクトがコレクション内で従う必要のある追加条件も加味されます。
@new_assemblies = @part.assemblies.where("created_at > ?", 2.days.ago)
4.4.1.14 collection.exists?(...)
collection.exists?
メソッドは、指定された条件に合うオブジェクトがコレクションの中に存在するかどうかをチェックします。このメソッドで使用される文法は、ActiveRecord::Base.exists?
で使用されているものと同じです。
4.4.1.15 collection.build(attributes = {})
collection.build
メソッドは、関連付けが行われたオブジェクトを1つ返します。このオブジェクトは、渡された属性でインスタンス化され、その結合テーブルを介してリンクが作成されます。ただし、関連付けられたオブジェクトはこの時点では保存されていないことにご注意ください。
@assembly = @part.assemblies.build({assembly_name: "Transmission housing"})
4.4.1.16 collection.create(attributes = {})
collection.create
メソッドは、関連付けが行われたオブジェクトを1つ返します。このオブジェクトは、渡された属性を使用してインスタンス化され、結合テーブルを介してリンクが作成されます。そして、関連付けられたモデルで指定されている検証がすべてパスすると、この関連付けられたオブジェクトは保存されます。
@assembly = @part.assemblies.create({assembly_name: "Transmission housing"})
4.4.1.17 collection.create!(attributes = {})
上のcollection.create
と同じですが、レコードがinvalidの場合にActiveRecord::RecordInvalid
がraiseされる点が異なります。
4.4.1.18 collection.reload
collection.reload
は、関連付けられたすべてのオブジェクトのリレーションを返し、データベースから強制的に読み込みます。関連付けられたオブジェクトがない場合は、空のリレーションを1つ返します。
@assemblies = @part.assemblies.reload
4.4.2 has_and_belongs_to_many
のオプション
Railsのデフォルトのhas_and_belongs_to_many
関連付けは、ほとんどの場合カスタマイズ不要ですが、時には関連付けの動作をカスタマイズしたくなることもあると思います。これは、作成するときにオプションを渡すことで簡単にカスタマイズできます。たとえば、以下のようなオプションを関連付けに追加できます。
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { readonly }, autosave: true end
has_and_belongs_to_many
関連付けでは以下のオプションがサポートされます。
:association_foreign_key
:autosave
:class_name
:foreign_key
:join_table
:validate
4.4.2.1 :association_foreign_key
Railsの慣例では、相手のモデルを指す外部キーを保持している結合テーブル上のカラム名については、そのモデル名にサフィックス _id
を追加した名前が使用されることを前提とします。:association_foreign_key
オプションを使用すると外部キーの名前を直接指定することができます。
:foreign_key
オプションおよび:association_foreign_key
オプションは、多対多の自己結合を行いたいときに便利です。以下に例を示します。
class User < ApplicationRecord has_and_belongs_to_many :friends, class_name: "User", foreign_key: "this_user_id", association_foreign_key: "other_user_id" end
4.4.2.2 :autosave
:autosave
オプションをtrue
に設定すると、親オブジェクトが保存されるたびに、読み込まれているすべての関連付けられたメンバを保存し、destroyフラグが立っているメンバを破棄します。:autosave
をfalse
に設定することと、:autosave
オプションを未設定のままにしておくことは同じではありません。:autosave
が存在しない場合、関連付けられたオブジェクトのうち、新しいオブジェクトは保存されますが、更新されたオブジェクトは保存されません。
4.4.2.3 :class_name
関連名から関連相手のオブジェクト名を生成できない事情がある場合、:class_name
オプションを使用してモデル名を直接指定できます。たとえば、1つの部品(Part)が複数の組み立て(Assembly)で使用され、組み立てを含む実際のモデル名がGadget
である場合、次のように設定します。
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, class_name: "Gadget" end
4.4.2.4 :foreign_key
Railsの慣例では、そのモデルを指す外部キーを保持している結合テーブル上のカラム名については、そのモデル名にサフィックス _id
を追加した名前が使用されることを前提とします。:foreign_key
オプションを使用すると外部キーの名前を直接指定することができます。
class User < ApplicationRecord has_and_belongs_to_many :friends, class_name: "User", foreign_key: "this_user_id", association_foreign_key: "other_user_id" end
4.4.2.5 :join_table
辞書順に基いて生成された結合テーブルのデフォルト名が気に入らない場合、:join_table
オプションを使用してデフォルトのテーブル名を上書きできます。
4.4.2.6 :validate
:validate
オプションをfalse
に設定すると、関連付けられたオブジェクトは保存時に検証(validation)されません。デフォルトはtrue
であり、この場合関連付けられたオブジェクトは保存時に検証されます。
4.4.3 has_and_belongs_to_many
のスコープについて
場合によってはhas_and_belongs_to_many
で使用されるクエリをカスタマイズしたくなることがあります。スコープブロックを使用してこのようなカスタマイズを行うことができます。以下に例を示します。
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { where active: true } end
スコープブロック内では標準のクエリメソッドをすべて使用できます。ここでは以下について説明します。
where
extending
group
includes
limit
offset
order
readonly
select
distinct
4.4.3.1 where
where
は、関連付けられるオブジェクトが満たすべき条件を指定します。
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { where "factory = 'Seattle'" } end
条件はハッシュを使用して指定することもできます。
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { where factory: 'Seattle' } end
where
オプションでハッシュを使用した場合、この関連付けで作成されたレコードは自動的にこのハッシュを使用したスコープに含まれるようになります。この例の場合、@parts.assemblies.create
または@parts.assemblies.build
を実行すると、factory
カラムにSeattle
を持つオブジェクトが作成されます。
4.4.3.2 extending
extending
メソッドは、関連付けプロキシを拡張する名前付きモジュールを指定します。関連付けの拡張については後述します。
4.4.3.3 group
group
メソッドは、結果をグループ化する際の属性名を1つ指定します。内部的にはSQLのGROUP BY
句が使用されます。
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { group "factory" } end
4.4.3.4 includes
includes
メソッドを使用すると、その関連付けが使用されるときにeager-load (訳注:preloadとは異なる)しておきたい第2関連付けを指定することができます。
4.4.3.5 limit
limit
メソッドは、関連付けを使用して取得できるオブジェクトの総数を制限するのに使用します。
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { order("created_at DESC").limit(50) } end
4.4.3.6 offset
offset
メソッドは、関連付けを使用してオブジェクトを取得する際の開始オフセットを指定します。たとえばoffset(11)と指定すると、最初の11レコードはスキップされ、12レコード目から返されるようになります。
4.4.3.7 order
order
メソッドは、関連付けられたオブジェクトに与えられる順序を指定します。内部的にはSQLのORDER BY
句が使用されます。
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { order "assembly_name ASC" } end
4.4.3.8 readonly
readonly
を指定すると、関連付けられたオブジェクトを取り出すときに読み出し専用になります。
4.4.3.9 select
select
メソッドを使用すると、関連付けられたオブジェクトのデータ取り出しに使用されるSQLのSELECT
句を上書きします。Railsはデフォルトではすべてのカラムを取り出します。
4.4.3.10 distinct
distinct
メソッドは、コレクション内の重複を削除します。
4.4.4 オブジェクトが保存されるタイミング
has_and_belongs_to_many
関連付けにオブジェクトをアサインすると、結合テーブルを更新するためにそのオブジェクトは自動的に保存されます。1つの文で複数のオブジェクトをアサインすると、それらはすべて保存されます。
関連付けられているオブジェクト同士の1つでも検証(validation)のために保存に失敗すると、アサインの状態からはfalse
が返され、アサインはキャンセルされます。
親オブジェクト(has_and_belongs_to_many
関連付けを宣言している側のオブジェクト)が保存されない場合(つまりnew_record?
がtrue
を返す場合)、子オブジェクトは追加時に保存されません。親オブジェクトが保存されると、関連付けられていたオブジェクトのうち保存されていなかったメンバはすべて保存されます。
has_and_belongs_to_many
関連付けにオブジェクトをアサインし、しかもそのオブジェクトを保存したくない場合、collection.build
メソッドを使用してください。
4.5 関連付けのコールバック
通常のコールバックは、Active Recordオブジェクトのライフサイクルの中でフックされます。これにより、オブジェクトのさまざまな場所でコールバックを実行できます。たとえば、:before_save
コールバックを使用して、オブジェクトが保存される直前に何かを実行することができます。
関連付けのコールバックも、上のような通常のコールバックとだいたい同じですが、(Active Recordオブジェクトではなく)コレクションのライフサイクルによってイベントがトリガされる点が異なります。以下の4つの関連付けコールバックを使用できます。
before_add
after_add
before_remove
after_remove
これらのオプションを関連付けの宣言に追加することで、関連付けコールバックを定義できます。以下に例を示します。
class Author < ApplicationRecord has_many :books, before_add: :check_credit_limit def check_credit_limit(book) ... end end
Railsは、追加されるオブジェクトや削除されるオブジェクトをコールバックに(引数として)渡します。
1つのイベントで複数のコールバックを使用したい場合には、配列を使用して渡します。
class Author < ApplicationRecord has_many :books, before_add: [:check_credit_limit, :calculate_shipping_charges] def check_credit_limit(book) ... end def calculate_shipping_charges(book) ... end end
before_add
コールバックが例外を発生した場合、オブジェクトはコレクションに追加されません。同様に、before_remove
で例外が発生した場合も、オブジェクトはコレクションに削除されません。
4.6 関連付けの拡張
Railsは自動的に関連付けのプロキシオブジェクトをビルドしますが、開発者はこれをカスタマイズすることができます。無名モジュール(anonymous module)を使用してこれらのオブジェクトを拡張(検索、作成などのメソッドを追加)することができます。以下に例を示します。
class Author < ApplicationRecord has_many :books do def find_by_book_prefix(book_number) find_by(category_id: book_number[0..2]) end end end
拡張を多くの関連付けで共有したい場合は、名前付きの拡張モジュールを使用することもできます。以下に例を示します。
module FindRecentExtension def find_recent where("created_at > ?", 5.days.ago) end end class Author < ApplicationRecord has_many :books, -> { extending FindRecentExtension } end class Supplier < ApplicationRecord has_many :deliveries, -> { extending FindRecentExtension } end
関連付けプロキシの内部を参照するには、proxy_association
アクセサにある以下の3つの属性を使用します。
-
proxy_association.owner
は、関連付けを所有するオブジェクトを返します。 -
proxy_association.reflection
は、関連付けを記述するリフレクションオブジェクトを返します。 -
proxy_association.target
は、belongs_to
またはhas_one
関連付けのオブジェクトを返すか、has_many
またはhas_and_belongs_to_many
関連付けオブジェクトのコレクションを返します。
5 シングルテーブル継承
異なるモデル間でフィールドや振る舞いを共有したい場合があります。Car
モデル、Motorcycle
モデル、Bicycle
モデルを持っていた場合を考えてみましょう。このときcolor
やprice
といったフィールド、そしていくつかの関連メソッドを共有したい場合が考えられます。しかし、モデルごとに振る舞いやコントローラーが異なります。
Railsではこのような状況にも簡単に対応できます。まず、各モデルのベースとなるVehicle
モデルを生成します。
$ rails generate model vehicle type:string color:string price:decimal{10.2}
"type"フィールドを追加している点にご注目ください。すべてのモデルはデータベース上のテーブルに保存されるため、Railsはこのカラムに該当するモデル名を保存します。この例では、カラムには "Car"、"Motorcycle"、もしくは"Bicycle"が保存されます。今回のシングルテーブル継承(STI: Single Table Inheritance)ではテーブルにこの"type"フィールドがないとうまく動きません。
次に、Vehicle
モデルを継承して3つの各モデルを生成します。このとき、--parent=PARENT
オプションを使って特定の親モデルを継承している点にご注目ください。このオプションを使うと(該当するテーブルが既に存在しているため)マイグレーションファイルを生成せずに済みます。
たとえばCar
モデルの場合は以下のようになります。
$ rails generate model car --parent=Vehicle
このときに生成されるモデルは次のとおりです。
class Car < Vehicle end
これによってVehicle
モデルに追加されたすべての振る舞いがCar
モデルにも追加されるようになります。関連付けやpublicメソッドなども同様に追加されます。
この状態で新しく作成したCar
を保存すると、type
フィールドに"Car"が代入されたデータがvehicles
テーブルに追加されます。
Car.create(color: 'Red', price: 10000)
なお、実際に発行されるSQLは次のようになります。
INSERT INTO "vehicles" ("type", "color", "price") VALUES ('Car', 'Red', 10000)
Car
のレコードを取得するクエリを送信すると、vehiclesテーブル中のCar
が検索されるようになります。
Car.all
実際のクエリは次のようになります。
SELECT "vehicles".* FROM "vehicles" WHERE "vehicles"."type" IN ('Car')