本ガイドでは、Active Recordの関連付け機能(アソシエーション)について解説します。
このガイドの内容:
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)
今度は著者を1人削除する場合を考えてみましょう。著者を削除するときは、その著者の書籍もすべて削除されるようにしておきます。
@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冊追加する作業が以下のように1行で書けるようになりました。
@book = @author.books.create(published_at: Time.now)
著者と、その著者の書籍をまとめて削除する作業はずっと簡単です。
@author.destroy
その他の関連付け方法については、次のセクションをお読みください。その後で、関連付けに関するさまざまなヒントや活用方法、Railsの関連付けメソッドとオプションの完全な参考情報も紹介します。
Railsでは6種類の関連付けをサポートしています。それぞれの関連付けは、特定の用途に特化しています。
以下は、Railsでサポートされている全種類の関連付けのリストです。リストはAPIドキュメントにリンクされているので、詳しい情報や利用方法、メソッドパラメータなどはリンク先を参照してください。
関連付けは、一種のマクロ的な呼び出しとして実装されており、これによってモデル間の関連付けを宣言的に追加できます。たとえば、あるモデルが他のモデルに従属している(belongs_to
)と宣言すると、2つのモデルのそれぞれのインスタンス間で「主キー - 外部キー」情報を保持しておくようにRailsに指示します。同時に、いくつかの便利なメソッドもそのモデルに追加されます。
本ガイドではこの後、それぞれの関連付けの宣言方法と利用方法について詳しく解説します。その前に、それぞれの関連付けが適切となる状況について簡単にご紹介します。
belongs_to
関連付けあるモデルでbelongs_to
関連付けを行なうと、宣言を行ったモデルの各インスタンスは、他方のモデルのインスタンスに文字どおり「従属(belongs to)」します。たとえば、Railsアプリケーションに著者(Author)と書籍(Book)情報が含まれており、書籍1冊につき正確に1人の著者を割り当てたいのであれば、Bookモデルで以下のように宣言します。
class Book < ApplicationRecord belongs_to :author end
belongs_to
関連付けで指定するモデル名は必ず「単数形」にしなければなりません。上記の例で、Book
モデルのauthor
関連付けを複数形(authors
)にしてからBook.create(authors: @author)
でインスタンスを作成しようとすると、uninitialized constant Book::Authors
エラーが発生します。Railsは、関連付けの名前から自動的にモデルのクラス名を推測します。従って、関連付け名が誤って複数形になってしまっていると、そこから推測されるクラス名も誤った形の複数形になってしまいます。
上の関連付けに対応するマイグレーションは以下のような感じになります。
class CreateBooks < ActiveRecord::Migration[7.1] def change create_table :authors do |t| t.string :name t.timestamps end create_table :books do |t| t.belongs_to :author t.datetime :published_at t.timestamps end end end
belongs_to
を単独で利用すると、一方向のみの「1対1」つながりが生成されます。つまり上の例で言うと、「個別の書籍はその著者を知っている」状態になりますが、「著者は自分の書籍について知らない」状態になります。
双方向関連付けをセットアップするには、belongs_to
関連付けを使うときに相手側のモデル(ここではAuthorモデル)にhas_one
またはhas_many
関連付けを指定します。
optional
がtrueに設定されている場合、belongs_to
では「参照の一貫性」が担保されません。そのため、ユースケースによっては以下のように参照カラムでデータベースレベルの外部キー制約(foreign_key: true
)を追加する必要があります。
create_table :books do |t| t.belongs_to :author, foreign_key: true # ... end
has_one
関連付けhas_one
関連付けは、相手側の1つのモデルがこのモデルへの参照を持っていることを示します。相手側のモデルは、この関連付けを経由してフェッチできます。
class Supplier < ApplicationRecord has_one :account end
belongs_to
との主な違いは、リンクカラムsupplier_id
が相手側のテーブルにあることです。
上の関連付けに対応するマイグレーションは以下のような感じになります。
class CreateSuppliers < ActiveRecord::Migration[7.1] def change create_table :suppliers do |t| t.string :name t.timestamps end create_table :accounts do |t| t.belongs_to :supplier t.string :account_number t.timestamps end end end
ユースケースによっては、accountsテーブルとの関連付けのために、supplierカラムにuniqueインデックスか外部キー制約を追加する必要が生じることもあります。その場合、カラムの定義は次のようになるでしょう。
create_table :accounts do |t| t.belongs_to :supplier, index: { unique: true }, foreign_key: true # ... end
このリレーションは、相手側のモデルでbelongs_to
関連付けも設定することで双方向関連付けになります。
has_many
関連付けhas_many
関連付けは、has_one
と似ていますが、相手のモデルとの「1対多」のつながりを表す点が異なります。has_many
関連付けは、多くの場合belongs_to
の反対側で使われます。
has_many
関連付けは、そのモデルの各インスタンスが、相手のモデルのインスタンスを0個以上持っていることを示します。たとえば、さまざまな著者や書籍を含むアプリケーションでは、Author(著者)モデルを以下のように宣言できます。
class Author < ApplicationRecord has_many :books end
has_many
関連付けを宣言する場合、相手のモデル名は「複数形」で指定する必要があります。
上の関連付けに対応するマイグレーションは以下のような感じになります。
class CreateAuthors < ActiveRecord::Migration[7.1] def change create_table :authors do |t| t.string :name t.timestamps end create_table :books do |t| t.belongs_to :author t.datetime :published_at t.timestamps end end end
ユースケースにもよりますが、通常はこのbooksテーブルのauthorカラムに「一意でない」インデックスを追加し、オプションで外部キー制約を作成するのがよいでしょう。
create_table :books do |t| t.belongs_to :author, index: true, foreign_key: true # ... end
has_many :through
関連付けhas_many :through
関連付けは、他方のモデルと「多対多」のつながりを設定する場合によく使われます。
この関連付けでは、2つのモデルの間に「第3のモデル」(joinモデル)が介在し、それを経由(through)して相手のモデルの「0個以上」のインスタンスとマッチします。たとえば、患者(patients)が医師(physicians)との診察予約(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
上の関連付けに対応するマイグレーションは以下のような感じになります。
class CreateAppointments < ActiveRecord::Migration[7.1] 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 t.belongs_to :patient t.datetime :appointment_date t.timestamps end end end
joinモデルのコレクションは、has_many
関連付けメソッド経由で管理できます。たとえば、以下のような割り当てを実行したとします。
physician.patients = patients
このとき、新たに関連付けられたオブジェクトについて、新しいjoinモデルが自動的に作成されます。以前あった行がなくなった場合は、その行はjoinモデルから自動的に削除され、joinモデルに含まれなくなります。
joinモデルでは、以前あった行がなくなった場合の自動削除は即座に行われます。しかも、そのときに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
has_one :through
関連付けhas_one :through
関連付けは、他方のモデルに対して「1対1」のつながりを設定します。この関連付けは、2つのモデルの間に「第3のモデル」(joinモデル)が介在し、それを経由(through)して相手モデルの1個のインスタンスとマッチします。たとえば、個別の供給者(supplier)が1個のアカウント(account)を持ち、さらに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[7.1] def change create_table :suppliers do |t| t.string :name t.timestamps end create_table :accounts do |t| t.belongs_to :supplier t.string :account_number t.timestamps end create_table :account_histories do |t| t.belongs_to :account t.integer :credit_rating t.timestamps end end end
has_and_belongs_to_many
関連付けhas_and_belongs_to_many
関連付けは、他方のモデルと「多対多」のつながりを作成しますが、through:
を指定した場合と異なり、第3のモデル(joinモデル)が介在しません(訳注: 後述するようにjoin用のテーブルは必要です)。たとえば、アプリケーションにさまざまな完成品(assemblies)と部品(parts)があり、完成品ごとに多数の部品が対応し、逆に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[7.1] 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 t.belongs_to :part end end end
belongs_to
とhas_one
のどちらを選ぶか2つのモデルの間に1対1のリレーションシップを設定したいのであれば、一方のモデルにbelongs_to
を追加し、他方のモデルにhas_one
を追加する必要があります。どちらの関連付けをどちらのモデルに置けばよいのでしょうか。
区別の決め手となるのは外部キー(foreign key)をどちらに置くかです(外部キーは、belongs_to
関連付けを追加したモデルのテーブルに追加されます)が、もう少しデータの実際の意味についても考えてみる必要があります。has_one
というリレーションシップは、主語となるものが目的語となるものを「所有する」ことを表します。「1人の供給者がアカウントを1つ所有する」と考える方が、「1つのアカウントが1人の供給者を所有する」と考えるよりも自然です。つまり、この場合の正しいリレーションシップは以下のようになります。
class Supplier < ApplicationRecord has_one :account end class Account < ApplicationRecord belongs_to :supplier end
上の関連付けに対応するマイグレーションは以下のような感じになります。
class CreateSuppliers < ActiveRecord::Migration[7.1] def change create_table :suppliers do |t| t.string :name t.timestamps end create_table :accounts do |t| t.bigint :supplier_id t.string :account_number t.timestamps end add_index :accounts, :supplier_id end end
マイグレーションでt.bigint :supplier_id
のように「小文字のモデル名_id」と書くと、外部キーを明示的に指定できます。現在のバージョンのRailsでは、同じことをt.references :supplier
という方法で記述できます。こちらの方が実装の詳細を抽象化して隠蔽できます。
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
です。こちらの場合は、joinモデルを経由する間接的な関連付けが使われます。
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
どちらを使うかを決める最もシンプルな経験則は次のとおりです。
リレーションシップモデル自体を独立したエンティティとして扱いたい場合は、中間にjoinモデルを使うhas_many :through
リレーションシップを設定します。
リレーションシップモデルで何か特別なことをする必要がまったくない場合は、joinモデルの不要なhas_and_belongs_to_many
リレーションシップを使う方がよりシンプルです(ただし、こちらの場合はjoinモデルが不要な代わりに、専用のjoinテーブルを別途データベースに作成しておく必要があることをお忘れなく)。
joinモデルでバリデーション、コールバック、追加の属性が必要な場合は、has_many :through
をお使いください。
has_and_belongs_to_many
リレーションシップは、id: false
を介して「主キーを含まない」joinテーブルを作成することを示唆していますが、has_many :through
リレーションシップでは、joinテーブルに複合主キーを含めることを検討してください。
たとえば上の例では create_table :manifests, primary_key: [:assembly_id, :part_id]
を使うことが推奨されます。
ポリモーフィック関連付け(polymorphic association)は、関連付けのやや高度な応用です。ポリモーフィック関連付けを使うと、ある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
モデルの1個のインスタンスから取得できます。
同様に、@product.pictures
とすれば、写真のコレクションをProduct
モデルの1個のインスタンスから取得できます。
Picture
モデルのインスタンスがあれば、@picture.imageable
とすることでその親を取得できます。これを可能にするには、ポリモーフィックなインターフェイスを宣言するモデルで、外部キーのカラムと型のカラムを両方とも宣言しておく必要があります。
class CreatePictures < ActiveRecord::Migration[7.1] def change create_table :pictures do |t| t.string :name t.bigint :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.2] def change create_table :pictures do |t| t.string :name t.references :imageable, polymorphic: true t.timestamps end end end
Railsは多くの場合、追加情報を必要とせずに、複合主キーをモデル間の関連付けで「主キー〜外部キー」情報を推論できます。以下の例をご覧ください。
class Order < ApplicationRecord self.primary_key = [:shop_id, :id] has_many :books end class Book < ApplicationRecord belongs_to :order end
ここでRailsは、1件のorder(注文)とそのbooks(本)の関連付けの主キーに:id
カラムが使われると仮定します。これは、通常のhas_many
関連付けやbelongs_to
関連付けと同様です。Railsは、books
テーブル上の外部キーカラムが:order_id
であると推測します。
ある本の注文に、以下のようにアクセスするとします。
order = Order.create!(id: [1, 2], status: "pending") book = order.books.create!(title: "A Cool Book") book.reload.order
この場合、以下のSQLを生成してorderにアクセスします。
SELECT * FROM orders WHERE id = 2
これが期待通りに動作するのは、このモデルの複合主キーに:id
カラムが含まれており、かつ:id
カラムがすべてのレコードで一意である場合だけです。関連付けで完全な複合主キーを使うには、その関連付けでquery_constraints
オプションを設定してください。このオプションは、関連付けられるレコードをクエリするときに複合外部キーを指定します。例:
class Author < ApplicationRecord self.primary_key = [:first_name, :last_name] has_many :books, query_constraints: [:first_name, :last_name] end class Book < ApplicationRecord belongs_to :author, query_constraints: [:author_first_name, :author_last_name] end
以下のように、ある本のauthor(著者)にアクセスするとします。
author = Author.create!(first_name: "Jane", last_name: "Doe") book = author.books.create!(title: "A Cool Book") book.reload.author
この場合、以下のようにSQLクエリで:first_name
と:last_name
が使われます。
SELECT * FROM authors WHERE first_name = 'Jane' AND last_name = 'Doe'
データモデルを設計していると、モデルを自分自身に関連付ける必要が生じることがあります。たとえば、全従業員(employees)を1つのデータベースモデルに格納しておきたいが、マネージャー(manager)と部下(subordinates)のリレーションシップも追えるようにしておきたい場合が考えられます。この状況は、以下のように自己結合(self-joining)関連付けでモデル化できます。
class Employee < ApplicationRecord has_many :subordinates, class_name: "Employee", foreign_key: "manager_id" belongs_to :manager, class_name: "Employee", optional: true end
上のように宣言しておくと、@employee.subordinates
と@employee.manager
を取り出せるようになります。
マイグレーションおよびスキーマでは、モデル自身にreferencesカラムを追加します。
class CreateEmployees < ActiveRecord::Migration[7.1] def change create_table :employees do |t| t.references :manager, foreign_key: { to_table: :employees } t.timestamps end end end
foreign_key
に渡されるto_table
オプションなどについて詳しくは、SchemaStatements#add_reference
を参照してください。
RailsアプリケーションでActive Recordの関連付けを効果的に使うためには、以下について知っておく必要があります。
関連付けのメソッドは、すべてキャッシュを中心に構築されています。最後に実行したクエリの結果はキャッシュに保持され、次回以降の操作で利用できます。このキャッシュは、以下のようにメソッド間でも共有される点にご注意ください。
# データベースからbooksを取得する author.books.load # booksのキャッシュコピーが使われる author.books.size # booksのキャッシュコピーが使われる author.books.empty?
データがアプリケーションの他の部分によって更新されている可能性があるのでキャッシュを再読み込みしたい場合は、どうしたらよいでしょうか。その場合は、以下のように関連付けのメソッド呼び出しでreload
を呼び出せば、キャッシュが破棄されてデータが再読み込みされます。
# データベースからbooksを取得する author.books.load # booksのキャッシュコピーが使われる author.books.size # booksのキャッシュコピーが破棄され、その後データベースから再度読み込まれる author.books.reload.empty?
関連付けにはどんな名前でも使えるとは限りません。関連付けを作成すると、モデルにその名前のメソッドが追加されます。従って、ActiveRecord::Base
のインスタンスで既に使われているような名前を関連付けに使うのは禁物です。そのような名前を関連付けに使うと、基底メソッドが上書きされて不具合が生じる可能性があります。attributes
やconnection
は関連付けに使ってはならない名前の例です。
関連付けはきわめて便利ですが、残念ながら魔法ではありません。関連付けを使うからには、関連付けの設定に合わせてデータベースのスキーマを常に更新しておく責任が生じます。作成した関連付けにもよりますが、具体的には次の2つの作業が必要になります。
belongs_to
関連付けを使う場合は、外部キーを作成する必要があります。has_and_belongs_to_many
関連付けを使う場合は、適切なjoinテーブルを作成する必要があります。belongs_to
関連付けに対応する外部キーを作成するbelongs_to
関連付けを宣言したら、対応する外部キーを作成する必要があります。以下のモデルを例にとります。
class Book < ApplicationRecord belongs_to :author end
上の宣言は、以下のbooksテーブル上で対応する外部キーカラムと整合している必要があります。作成した直後のテーブルの場合、マイグレーションは次のような感じになります。
class CreateBooks < ActiveRecord::Migration[7.1] def change create_table :books do |t| t.datetime :published_at t.string :book_number t.references :author end end end
一方、既存のテーブルの場合、マイグレーションは次のような感じになります。
class AddAuthorToBooks < ActiveRecord::Migration[7.1] def change add_reference :books, :author end end
データベースレベルでの参照整合性を強制するには、上の‘reference’カラム宣言にforeign_key: true
オプションを追加します。
has_and_belongs_to_many
関連付けに対応するjoinテーブルを作成するhas_and_belongs_to_many
関連付けを作成した場合は、それに対応するjoinテーブルを明示的に作成する必要があります。joinテーブルの名前が:join_table
オプションで明示的に指定されていない場合、Active Recordは2つのクラス名をABC順に結合して、joinテーブル名を作成します。たとえばAuthorモデルとBookモデルを結合する場合、'a'は辞書で'b'より先に出現するので "authors_books"というデフォルトのjoinテーブル名が使われます。
モデル名の優先順位はString
クラスの<=>
演算子を用いて算出されます。つまり、2つの文字列の長さが異なり、短い方が長い方の途中まで完全に一致している場合は、長い方の文字列は短い方よりも優先順位が高くなるということです。たとえば、"paper_boxes" テーブルと "papers" テーブルがある場合、"paper_boxes" の方が長いので、これらのjoinテーブル名は "papers_paper_boxes" になりそうな気がします。しかし実際に生成されるjoinテーブル名は "paper_boxes_papers" になります(これは多くのエンコーディングでアンダースコア '_' の方が 's' よりも並び順が前になるためです)。
生成された名前がどのようなものであれ、適切なマイグレーションを実行してjoinテーブルを生成する必要があります。以下の関連付けを例にとって考えてみましょう。
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[7.1] def change create_table :assemblies_parts, id: false do |t| t.bigint :assembly_id t.bigint :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[7.1] def change create_join_table :assemblies, :parts do |t| t.index :assembly_id t.index :part_id end end end
デフォルトでは、関連付けによって探索されるのは、現在のモジュールのスコープ内にあるオブジェクトだけです。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
通常は、関連付けを双方向に機能させるために、2つのモデルの両方に関連付けを定義する必要があります。
class Author < ApplicationRecord has_many :books end class Book < ApplicationRecord belongs_to :author end
Active Recordは、これらの関連付けの設定から、2つのモデルが双方向の関連を共有していることを自動的に認識しようとします。以下に示すように、Active RecordはAuthor
オブジェクトを1個だけ読み出すことで、アプリケーションの効率を高めるとともにデータの一貫性を維持します。
既に読み込まれたデータに対する不要なクエリを防ぐ
irb> author = Author.first irb> author.books.all? do |book| irb> book.author.equal?(author) # 追加クエリはここで実行されない irb> end => true
データの不整合を防ぐ(Author
オブジェクトのコピーは1個しか読み込まれない)
irb> author = Author.first irb> book = author.books.first irb> author.name == book.author.name => true irb> author.name = "Changed Name" irb> author.name == book.author.name => true
関連付けを自動保存するケースを増やす
irb> author = Author.new irb> book = author.books.new irb> book.save! irb> book.persisted? => true irb> author.persisted? => true
関連付けのpresence
やabsence
をバリデーションするケースを増やす
irb> book = Book.new irb> book.valid? => false irb> book.errors.full_messages => ["Author must exist"] irb> author = Author.new irb> book = author.books.new irb> book.valid? => true
Active Recordでは、ほとんどの標準的な名前同士の関連付けについて自動識別をサポートしています。ただしActive Recordは、:through
や:foreign_key
オプションを含む双方向関連付けを自動認識しません。
また、関連付け自身でカスタムスコープが使われている場合も、config.active_record.automatic_scope_inversing
をtrue
に設定しない限り自動識別しません(新しいアプリケーションではデフォルトでconfig.active_record.automatic_scope_inversing = true
が設定されます)。
たとえば、次のようなモデルを宣言したケースを考えてみましょう。
class Author < ApplicationRecord has_many :books end class Book < ApplicationRecord belongs_to :writer, class_name: 'Author', foreign_key: 'author_id' end
この場合、:foreign_key
オプションのため、Active Recordは双方向関連付けを自動的に認識しなくなります。これによってアプリケーションで以下が発生する可能性があります。
同じデータに対して不要なクエリを実行する(この例ではN+1クエリが発生する)
irb> author = Author.first irb> author.books.any? do |book| irb> book.author.equal?(author) # authorクエリがbook 1件ごとに発生する irb> end => false
データが一貫していないモデルの複数のコピーを参照する
irb> author = Author.first irb> book = author.books.first irb> author.name == book.author.name => true irb> author.name = "Changed Name" irb> author.name == book.author.name => false
関連付けの自動保存が失敗する
irb> author = Author.new irb> book = author.books.new irb> book.save! irb> book.persisted? => true irb> author.persisted? => false
presence
やabsence
バリデーションが失敗する
irb> author = Author.new irb> book = author.books.new irb> book.valid? => false irb> book.errors.full_messages => ["Author must exist"]
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が双方向関連付けを認識して、上述の最初の例のように動作するようになります。
本セクションでは、関連付けの種別ごとの詳細を解説します。関連付けの宣言によって追加されるメソッドやオプションについても説明します。
belongs_to
関連付けの詳細な参照belongs_to
関連付けは、データベースの観点では、このモデルのテーブルに別のテーブルへの参照を表すカラムが含まれていることを意味します。belongs_to
関連付けは、状況に応じて1対1または1対多のリレーションを設定するのに利用できます。相手側クラスのテーブルが1対1のリレーションで参照を含んでいる場合は、has_one
を使うべきです。
belongs_to
で追加されるメソッドbelongs_to
関連付けを宣言したクラスでは、以下の8つのメソッドが自動的に利用できるようになります。
association
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
reload_association
reset_association
association_changed?
association_previously_changed?
上のメソッド名のassociation
の部分はプレースホルダなので、belongs_to
の第1引数として渡されるものの名前で読み替えてください。
たとえば以下の宣言があるとします。
class Book < ApplicationRecord belongs_to :author end
このとき、Book
モデルのインスタンスで以下のメソッドが使えるようになります。
author
author=
build_author
create_author
create_author!
reload_author
reset_author
author_changed?
author_previously_changed?
新しく作成したhas_one
関連付けまたはbelongs_to
関連付けを初期化するには、association.build
メソッドではなくbuild_
で始まるメソッドを使う必要があります(association.build
はhas_many
関連付けやhas_and_belongs_to_many
関連付けで使われます)。関連付けを作成する場合も、create_
で始まるメソッドをお使いください。
association
association
メソッドは、関連付けられたオブジェクトを返します。関連付けられたオブジェクトがない場合はnil
を返します。
@author = @book.author
関連付けられたオブジェクトがデータベースから既に取得されている場合は、キャッシュされたものを返します。この振る舞いを上書きして、キャッシュを読み出さずにデータベースから強制的に読み込みたい場合は、親オブジェクトが持つ#reload_association
メソッドを呼び出します。
@author = @book.reload_author
関連付けされたオブジェクトのキャッシュバージョンをアンロードして、次回のアクセスでデータベース呼び出しからクエリするには、親オブジェクトの#reset_association
を呼び出します。
@book.reset_author
association=(associate)
association=
メソッドは、引数のオブジェクトをそのオブジェクトに関連付けます。その背後では、関連付けられたオブジェクトから主キーを取り出し、そのオブジェクトの外部キーにその同じ値を設定しています。
@book.author = @author
build_association(attributes = {})
build_association
メソッドは、関連付けられた型の新しいオブジェクトを返します。返されるオブジェクトは、渡された属性に基いてインスタンス化され、外部キーを経由するリンクが設定されます。関連付けられたオブジェクトはまだ保存されていないことにご注意ください。
@author = @book.build_author(author_number: 123, author_name: "John Doe")
create_association(attributes = {})
create_association
メソッドは、関連付けられた型の新しいオブジェクトを返します。このオブジェクトは、渡された属性を用いてインスタンス化され、そのオブジェクトの外部キーを介してリンクが設定されます。そして、関連付けられたモデルで指定されているバリデーションがすべてパスすると、この関連付けられたオブジェクトは保存されます。
@author = @book.create_author(author_number: 123, author_name: "John Doe")
create_association!(attributes = {})
上のcreate_association
と同じですが、レコードが無効な場合にActiveRecord::RecordInvalid
がraiseされる点が異なります。
association_changed?
association_changed?
メソッドは、新しい関連付けオブジェクトが代入された場合にtrue
を返します。外部キーは次の保存で更新されます。
@book.author # => #<Author author_number: 123, author_name: "John Doe"> @book.author_changed? # => false @book.author = Author.second # => #<Author author_number: 456, author_name: "Jane Smith"> @book.author_changed? # => true @book.save! @book.author_changed? # => false
association_previously_changed?
association_previously_changed?
メソッドは、関連付けが前回の保存で更新されて新しい関連付けオブジェクトを参照している場合にtrue
を返します。
@book.author # => #<Author author_number: 123, author_name: "John Doe"> @book.author_previously_changed? # => false @book.author = Author.second # => #<Author author_number: 456, author_name: "Jane Smith"> @book.save! @book.author_previously_changed? # => true
belongs_to
のオプションRailsのデフォルトのbelongs_to
関連付けは優秀なので、ほとんどの場合カスタマイズ不要ですが、関連付けの参照をカスタマイズしたい場合もあります。これは、作成するときに渡すオプションとスコープブロックで簡単にカスタマイズできます。たとえば、以下のようなオプションを関連付けに追加できます。
class Book < ApplicationRecord belongs_to :author, touch: :books_updated_at, counter_cache: true end
belongs_to
関連付けでは以下のオプションがサポートされています。
:autosave
:class_name
:counter_cache
:default
:dependent
:ensuring_owner_was
:foreign_key
:foreign_type
:primary_key
:inverse_of
:optional
:polymorphic
:required
:strict_loading
:touch
:validate
:autosave
:autosave
オプションをtrue
に設定すると、親オブジェクトが保存されるたびに、読み込まれているすべての関連付けメンバを保存し、destroyフラグが立っているメンバを破棄します。
:autosave
をfalse
に設定することと、:autosave
オプションを未設定のままにしておくことは同じではありません。:autosave
オプションを渡さない場合、関連付けられたオブジェクトのうち、新しいオブジェクトは保存されますが、更新されたオブジェクトは保存されません。
:class_name
関連付けの相手となるオブジェクト名を関連付け名から生成できない事情がある場合、:class_name
オプションを用いてモデル名を直接指定できます。たとえば、書籍(book)が著者(author)に従属しているが実際の著者のモデル名がPatron
である場合には、以下のように指定します。
class Book < ApplicationRecord belongs_to :author, class_name: "Patron" end
: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
宣言で指定しますが、実際に個数を数えたいカラムは「相手の」モデル(関連付け先のモデル)の側に追加する必要があります。上の場合は、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
によってオーナーモデルの読み出し専用属性リストに追加されます。
何らかの理由でオーナーモデルの主キーの値を変更し、カウントされたモデルの外部キーも更新しなかった場合、カウンタキャッシュのデータが古くなっている可能性があります(つまり、孤立したモデルも引き続きカウンタでカウントされます)。古くなったカウンタキャッシュを修正するには、reset_counters
をお使いください。
:default
true
に設定すると、その関連付けが存在することが保証されなくなります。
:dependent
:dependent
で指定するオプションの挙動は以下のとおりです。
:destroy
: オブジェクトが削除されるときに、関連付けられたオブジェクトのdestroy
メソッドが実行されます。:delete
: オブジェクトが削除されるときに、関連付けられたオブジェクトが直接データベースから削除されます。destroy
メソッドは実行されません。:destroy_async
: オブジェクトが削除されるときに、ActiveRecord::DestroyAssociationAsyncJob
ジョブがジョブキューに入り、関連付けられたオブジェクトでdestroy
メソッドを呼び出します。このジョブが動作するには、Active Jobをセットアップしておく必要があります。
関連付けの背後にデータベースの外部キー制約がある場合は、このオプションを使ってはいけません。外部キー制約の操作は、そのオーナーを削除するのと同じトランザクション内で発生します。このオプションは、他のクラスのhas_many
関連付けとつながりのあるbelongs_to
関連付けに対して使ってはいけません。孤立レコードがデータベースに残ってしまう可能性があります。
:ensuring_owner_was
オーナーによって呼び出されるインスタンスメソッドを指定します。関連付けられるレコードがバックグラウンドジョブで削除されるようにするには、このメソッドがtrue
を返さなければなりません。
:foreign_key
Railsの規約では、相手のモデルを指す外部キーを保持しているjoinテーブル上のカラム名については、そのモデル名にサフィックス_id
を追加した関連付け名が使われることを前提とします。:foreign_key
オプションを使えば、外部キーの名前を直接指定できます。
class Book < ApplicationRecord belongs_to :author, class_name: "Patron", foreign_key: "patron_id" end
Railsは外部キーのカラムを自動的に作成することはありません。外部キーを使うには、マイグレーションで明示的に定義する必要があります。
:foreign_type
ポリモーフィック関連付けが有効になっている場合、関連付けられるオブジェクトの型を保存するカラムを指定します。デフォルトでは、これは関連付け名の末尾に_type
サフィックスを追加した名前を推測します。したがって、belongs_to :taggable, polymorphic: true
という関連付けを定義するクラスでは、taggable_type
がデフォルトの:foreign_type
として使われます。
:primary_key
Railsの規約では、id
カラムはそのテーブルの主キーとして使われます。:primary_key
オプションを指定すると、指定された別のカラムを主キーとして設定できます
たとえば、users
テーブルにguid
という主キーがあるとします。そのguid
カラムに、別のtodos
テーブルの外部キーであるuser_id
カラムを使いたい場合は、次のように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
のguid
としてuser_id
を持つようになります。
:inverse_of
:inverse_of
オプションは、その関連付けの逆関連付けとなるhas_many
関連付けまたはhas_one
関連付けの名前を指定します。
詳しくは双方向関連付けを参照してください。
class Author < ApplicationRecord has_many :books, inverse_of: :author end class Book < ApplicationRecord belongs_to :author, inverse_of: :books end
:optional
:optional
オプションにtrue
を指定すると、関連付けられるオブジェクトが存在することが保証されなくなります。このオプションは、デフォルトではfalse
です。
:polymorphic
:polymorphic
オプションにtrue
を指定すると、ポリモーフィック関連付けを指定できます。ポリモーフィック関連付けについて詳しくは本ガイドのポリモーフィック関連付けを参照してください。
:required
true
を指定すると、その関連付けが存在することも保証されるようになります。これによってバリデーションされるのは関連付け自身であり、idではありません。:inverse_of
を使うと、バリデーション時に余分なクエリが発生することを回避できます。
このrequired
オプションはデフォルトでtrue
に設定されますが、これは非推奨化されています。関連付けの存在をバリデーションしたくない場合は、optional: true
をお使いください。
:strict_loading
true
を指定すると、関連付けられるレコードが、この関連付けを経由して読み込まれるたびにstrict loadingを強制するようになります。
:touch
:touch
オプションをtrue
に設定すると、そのオブジェクトがsave
またはdestroy
されたときに、関連付けられたオブジェクトのupdated_at
タイムスタンプやupdated_on
タイムスタンプが常に現在の時刻に設定されます。
class Book < ApplicationRecord belongs_to :author, touch: true end class Author < ApplicationRecord has_many :books end
上のBook
は、関連付けられているAuthor
のタイムスタンプをsave
またはdestroy
するときに更新します。更新時に特定のタイムスタンプ属性を指定することも可能です。
class Book < ApplicationRecord belongs_to :author, touch: :books_updated_at end
:validate
:validate
オプションをtrue
に設定すると、新たに関連付けられたオブジェクトを保存するときに必ずバリデーションされます。デフォルトはfalse
であり、この場合新たに関連付けられたオブジェクトは保存時にバリデーションされません。
belongs_to
のスコープbelongs_to
で使われるクエリをカスタマイズしたい場合があります。スコープブロックを用いてこのようなカスタマイズを行えます。以下に例を示します。
class Book < ApplicationRecord belongs_to :author, -> { where active: true } end
スコープブロック内では標準のクエリメソッドをすべて利用できます。ここでは以下について説明します。
where
includes
readonly
select
where
where
メソッドは、関連付けられるオブジェクトが満たすべき条件を指定します。
class Book < ApplicationRecord belongs_to :author, -> { where active: true } end
includes
includes
メソッドを使うと、その関連付けが使われるときにeager loadingすべき第2関連付けを指定できます。以下のモデルを例に考えてみましょう。
class Chapter < ApplicationRecord belongs_to :book end class Book < ApplicationRecord belongs_to :author has_many :chapters end class Author < ApplicationRecord has_many :books end
章(chapter)から本の著者名(author)を@chapter.book.author
のように直接取り出す頻度が高い場合は、以下のようにChapter
からBook
への関連付けでAuthor
をあらかじめincludes
しておくと、クエリが減って効率が高まります。
class Chapter < ApplicationRecord belongs_to :book, -> { includes :author } end class Book < ApplicationRecord belongs_to :author has_many :chapters end class Author < ApplicationRecord has_many :books end
直接の関連付けではincludes
を使う必要はありません。Book belongs_to :author
のような直接の関連付けでは必要に応じて自動的にeager loadingされます。
readonly
readonly
を指定すると、関連付けられたオブジェクトを読み出し専用で取り出します。
select
select
メソッドを使うと、関連付けられたオブジェクトのデータ取り出しに使われるSQLのSELECT
句をオーバーライドできます。Railsはデフォルトですべてのカラムを取り出します。
select
をbelongs_to
関連付けで使う場合は、正しい結果を得るために:foreign_key
オプションも設定してください。
association.nil?
メソッドを用いて、関連付けられたオブジェクトが存在するかどうかを確認できます。
if @book.author.nil? @msg = "この本の著者が見つかりません" end
オブジェクトをbelongs_to
関連付けに割り当てても、オブジェクトが自動的に保存されるわけではありません。関連付けられたオブジェクトも保存されません。
has_one
関連付けの詳細な参照has_one
関連付けは相手のモデルと1対1対応します。データベースの観点では、この関連付けは相手のクラスが外部キーを持つことを意味します。相手ではなく自分のクラスが外部キーを持つのであれば、belongs_to
を使うべきです。
has_one
で追加されるメソッドhas_one
関連付けを宣言したクラスでは、以下の6つのメソッドが自動的に利用できるようになります。
association
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
reload_association
reset_association
上のメソッド名のassociation
の部分はプレースホルダなので、has_one
の第1引数として渡されるものの名前で読み替えてください。
class Supplier < ApplicationRecord has_one :account end
これにより、Supplier
モデルのインスタンスで以下のメソッドが使えるようになります。
account
account=
build_account
create_account
create_account!
reload_account
reset_account
新しく作成したhas_one
関連付けまたはbelongs_to
関連付けを初期化するには、association.build
メソッドではなくbuild_
で始まるメソッドを使う必要があります(association.build
はhas_many
関連付けやhas_and_belongs_to_many
関連付けで使われます)。関連付けを作成する場合も、create_
で始まるメソッドをお使いください。
association
association
メソッドは、関連付けられたオブジェクトを返します。関連付けられたオブジェクトがない場合はnil
を返します。
@account = @supplier.account
関連付けられたオブジェクトがデータベースから既に取得されている場合は、キャッシュされたものを返します。キャッシュを読み出さずにデータベースから直接読み込みたい場合は、親オブジェクトが持つ#reload_association
メソッドを呼び出します。
@account = @supplier.reload_account
関連付けされたオブジェクトのキャッシュバージョンをアンロードして、次回のアクセスでデータベース呼び出しからクエリするには、親オブジェクトの#reset_association
を呼び出します。
@supplier.reset_account
association=(associate)
association=
メソッドは、引数のオブジェクトをそのオブジェクトに関連付けます。その背後では、関連付けられたオブジェクトから主キーを取り出し、そのオブジェクトの外部キーにその同じ値を設定しています。
@supplier.account = @account
build_association(attributes = {})
build_association
メソッドは、関連付けられた型の新しいオブジェクトを返します。返されるオブジェクトは、渡された属性に基いてインスタンス化され、外部キーを経由するリンクが設定されます。関連付けられたオブジェクトはまだ保存されていないことにご注意ください。
@account = @supplier.build_account(terms: "Net 30")
create_association(attributes = {})
create_association
メソッドは、関連付けられた型の新しいオブジェクトを返します。このオブジェクトは、渡された属性を用いてインスタンス化され、そのオブジェクトの外部キーを介してリンクが設定されます。そして、関連付けられたモデルで指定されているバリデーションがすべてパスすると、この関連付けられたオブジェクトは保存されます。
@account = @supplier.create_account(terms: "Net 30")
create_association!(attributes = {})
上のcreate_association
と同じですが、レコードが無効な場合にActiveRecord::RecordInvalid
がraiseされる点が異なります。
has_one
のオプションRailsのデフォルトのhas_one
関連付けは優秀なので、ほとんどの場合カスタマイズ不要ですが、関連付けの参照をカスタマイズしたい場合もあります。これは、作成するときに渡すオプションで簡単にカスタマイズできます。たとえば、以下のようなオプションを関連付けに追加できます。
class Supplier < ApplicationRecord has_one :account, class_name: "Billing", dependent: :nullify end
has_one
関連付けでは以下のオプションがサポートされます。
:as
:autosave
:class_name
:dependent
:disable_joins
:ensuring_owner_was
:foreign_key
:inverse_of
:primary_key
:query_constraints
:required
:source
:source_type
:strict_loading
:through
:touch
:validate
:as
:as
オプションを設定すると、ポリモーフィック関連付けを指定できます。ポリモーフィック関連付けについて詳しくは本ガイドのポリモーフィック関連付けを参照してください。
:autosave
:autosave
オプションをtrue
に設定すると、親オブジェクトが保存されるたびに、読み込まれているすべてのメンバを保存し、destroyフラグが立っているメンバを破棄します。
:autosave
をfalse
に設定することと、:autosave
オプションを未設定のままにしておくことは同じではありません。:autosave
オプションを渡さない場合、関連付けられたオブジェクトのうち、新しいオブジェクトは保存されますが、更新されたオブジェクトは保存されません。
:class_name
関連付けの相手となるオブジェクト名を関連付け名から生成できない事情がある場合、:class_name
オプションを用いてモデル名を直接指定できます。たとえば、Supplier(供給者)がAccount(アカウント)を1つ持ち、アカウントを含むモデルの実際の名前がAccount
ではなくBilling
になっている場合、以下のようにモデル名を指定できます。
class Supplier < ApplicationRecord has_one :account, class_name: "Billing" end
:dependent
オブジェクトのオーナーがdestroyされたときの、それに関連付けられたオブジェクトの扱いを制御します。
:destroy
: 関連付けられたオブジェクトも同時にdestroyされます。:delete
: 関連付けられたオブジェクトはデータベースから直接削除されます(コールバックは実行されません)。:destroy_async
: オブジェクトが削除されるときに、ActiveRecord::DestroyAssociationAsyncJob
ジョブがジョブキューに入り、関連付けられたオブジェクトでdestroy
メソッドを呼び出します。このジョブが動作するには、Active Jobをセットアップしておく必要があります。
関連付けの背後にデータベースの外部キー制約がある場合は、このオプションを使ってはいけません。外部キー制約の操作は、そのオーナーを削除するのと同じトランザクション内で発生します。:nullify
: 外部キーがNULL
に設定されます。ポリモーフィックなtypeカラムもポリモーフィック関連付けでNULL
に設定されます。コールバックは実行されません。:restrict_with_exception
: 関連付けられたレコードがある場合にActiveRecord::DeleteRestrictionError
例外が発生します。:restrict_with_error
: 関連付けられたオブジェクトがある場合にエラーがオーナーに追加されます。NOT NULL
データベース制約のある関連付けでは、:nullify
オプションを与えないようにする必要があります。そのような関連付けをdestroyするときにdependent
を設定しなかった場合、関連付けられたオブジェクトを変更できなくなってしまいます。これは、最初に関連付けられたオブジェクトの外部キーがNULL
値になってしまい、この値は許されていないためです。
:disable_joins
関連付けでJOINをスキップするかどうかを指定します。
true
を指定した場合、クエリが2つ以上生成されるようになります。ただし場合によっては、ORDERやLIMITが適用されるとデータベースの制限によりインメモリで処理されることがあります。
このオプションは、has_one :through
関連付けにのみ適用されます(has_one
単体ではJOINは実行されません)。
:foreign_key
Railsの規約では、相手のモデル上の外部キーを保持しているカラム名については、そのモデル名にサフィックス_id
を追加した関連付け名が使われることを前提とします。:foreign_key
オプションを使うと外部キーの名前を直接指定できます。
class Supplier < ApplicationRecord has_one :account, foreign_key: "supp_id" end
Railsは外部キーのカラムを自動的に作成することはありません。外部キーを使うには、マイグレーションで明示的に定義する必要があります。
:inverse_of
:inverse_of
オプションは、その関連付けの逆関連付けとなるbelongs_to
関連付けの名前を指定します。
詳しくは双方向関連付けを参照してください。
class Supplier < ApplicationRecord has_one :account, inverse_of: :supplier end class Account < ApplicationRecord belongs_to :supplier, inverse_of: :account end
:primary_key
Railsの規約では、モデルの主キーはid
カラムに保存されていることを前提とします。:primary_key
オプションで主キーを明示的に指定することでこれを上書きできます。
:query_constraints
複合外部キーとして機能するようになります。関連付けられるオブジェクトをクエリするために使うカラムのリストを定義します。この機能はオプションです(デフォルトでは、値の自動導出を試みます)。値が設定されている場合、Array
のサイズは、関連付けられるモデルの主キーのサイズまたはquery_constraints
のサイズと一致する必要があります。
:required
true
を指定すると、その関連付けが存在することも保証されるようになります。これによってバリデーションされるのは関連付け自身であり、idではありません。:inverse_of
を使うと、バリデーション時に余分なクエリが発生することを回避できます。
:source
:source
オプションは、has_one :through
関連付けで用いる関連付け元の名前を指定します。
:source_type
:source_type
オプションは、ポリモーフィック関連付けを介するhas_one :through
関連付けで、関連付け元の型を指定します。
class Author < ApplicationRecord has_one :book has_one :hardback, through: :book, source: :format, source_type: "Hardback" has_one :dust_jacket, through: :hardback end class Book < ApplicationRecord belongs_to :format, polymorphic: true end class Paperback < ApplicationRecord; end class Hardback < ApplicationRecord has_one :dust_jacket end class DustJacket < ApplicationRecord; end
:strict_loading
true
を指定すると、関連付けられるレコードが、この関連付けを経由して読み込まれるたびにstrict loadingを強制するようになります。
:through
:through
オプションは、このガイドで既に説明したhas_one :through
関連付けのクエリを実行する際に経由するjoinモデルを指定します。
:touch
:touch
オプションをtrue
に設定すると、そのオブジェクトがsave
またはdestroy
されたときに、関連付けられたオブジェクトのupdated_at
タイムスタンプやupdated_on
タイムスタンプが常に現在の時刻に設定されます。
class Supplier < ApplicationRecord has_one :account, touch: true end class Account < ApplicationRecord belongs_to :supplier end
上のSupplier
は、関連付けられているAccount
のタイムスタンプをsave
またはdestroy
するときに更新します。更新時に特定のタイムスタンプ属性を指定することも可能です。
class Supplier < ApplicationRecord has_one :account, touch: :suppliers_updated_at end
:validate
:validate
オプションをtrue
に設定すると、新たに関連付けられたオブジェクトが保存時に必ずバリデーションされます。デフォルトはfalse
であり、この場合新たに関連付けられたオブジェクトは保存時に バリデーション されません。
has_one
のスコープhas_one
で使われるクエリをカスタマイズしたい場合があります。スコープブロックを用いてこのようなカスタマイズを行えます。以下に例を示します。
class Supplier < ApplicationRecord has_one :account, -> { where active: true } end
スコープブロック内では標準のクエリメソッドをすべて利用できます。ここでは以下について説明します。
where
includes
readonly
select
where
where
は、関連付けられるオブジェクトが満たすべき条件を指定します。
class Supplier < ApplicationRecord has_one :account, -> { where "confirmed = 1" } end
includes
includes
メソッドを使うと、その関連付けが使われるときにeager loadingすべき第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
をあらかじめincludes
しておくと、クエリが減って効率が高まります。
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
readonly
readonly
を指定すると、関連付けられたオブジェクトを読み出し専用で取り出します。
select
select
メソッドを使うと、関連付けられたオブジェクトのデータ取り出しに使われるSQLのSELECT
句をオーバーライドできます。Railsはデフォルトではすべてのカラムを取り出します。
association.nil?
メソッドを用いて、関連付けられたオブジェクトが存在するかどうかを確認できます。
if @supplier.account.nil? @msg = "この供給者のアカウントがありません" end
has_one
関連付けにオブジェクトを割り当てると、外部キーを更新するためにそのオブジェクトは自動的に保存されます。さらに、オブジェクトを置き換えると外部キーも変更されるので、置き換えられたオブジェクトはすべて自動的に保存されます。
関連付けられているオブジェクト同士のいずれか一方がバリデーションによって保存に失敗すると、割り当ての状態がfalse
になり、割り当てはキャンセルされます。
親オブジェクト(has_one
関連付けを宣言している側のオブジェクト)が保存されていない場合(つまりnew_record?
がtrue
を返す場合)、子オブジェクトは保存されません。親オブジェクトが保存されると、子オブジェクトは自動的に保存されます。
has_one
関連付けにオブジェクトを割り当てて、しかもそのオブジェクトを保存したくない場合は、build_association
メソッドをお使いください。
has_many
関連付けの詳細な参照has_many
関連付けは、他のモデルとの間に「1対多」のつながりを作成します。データベースの観点では、この関連付けは、他のクラスがこのクラスのインスタンスを参照する外部キーを持っていることを意味します。
has_many
で追加されるメソッドhas_many
関連付けを宣言したクラスでは、以下の17のメソッドが自動的に利用できるようになります。
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
collection
collection
メソッドは、関連付けられたすべてのオブジェクトのリレーションを返します。関連付けられたオブジェクトがない場合は、空のリレーションを1つ返します。
@books = @author.books
collection<<(object, ...)
collection<<
メソッドは、1つ以上のオブジェクトをコレクションに追加します。このとき、追加されるオブジェクトの外部キーは、呼び出し側モデルの主キーに設定されます。
@author.books << @book1
collection.delete(object, ...)
collection.delete
メソッドは、外部キーをNULL
に設定することで、コレクションから1個以上のオブジェクトを削除します。
@author.books.delete(@book1)
削除の方法はこれだけではありません。オブジェクト同士がdependent: :destroy
で関連付けられている場合はdestroyされますが、オブジェクト同士がdependent: :delete_all
で関連付けられている場合はdeleteされるのでご注意ください。
collection.destroy(object, ...)
collection.destroy
は、コレクションに関連付けられているオブジェクトに対してdestroy
を実行することで、コレクションから1つまたは複数のオブジェクトを削除します。
@author.books.destroy(@book1)
この場合オブジェクトは無条件にデータベースから削除されます。このとき:dependent
オプションはすべて無視されます。
collection=(objects)
collection=
メソッドは、削除や追加を適宜実行することで、コレクションに渡されたオブジェクトだけが含まれるようにします。変更の結果はデータベースで永続化されます。
collection_singular_ids
collection_singular_ids
メソッドは、そのコレクションに含まれるオブジェクトのidを配列にしたものを返します。
@book_ids = @author.book_ids
collection_singular_ids=(ids)
collection_singular_ids=
メソッドは、削除や追加を適宜実行することで、指定された主キーのidを持つオブジェクトだけが含まれるようにします。変更の結果はデータベースで永続化されます。
collection.clear
collection.clear
メソッドは、dependent
オプションで指定された戦略に沿って、コレクションからすべてのオブジェクトを削除します。オプションが渡されなかった場合は、デフォルトの戦略に従います。デフォルトの戦略は、has_many :through
の関連付けの場合はdelete_all
が指定され、has_many
の関連付けの場合は外部キーがNULL
に設定されます。
@author.books.clear
dependent: :delete_all
の場合と同様に、オブジェクトがdependent: :destroy
またはdependent: :destroy_async
で関連付けされていた場合、それらのオブジェクトは削除されます。
collection.empty?
collection.empty?
メソッドは、関連付けられたオブジェクトがコレクションに存在しない場合にtrue
を返します。
<% if @author.books.empty? %> No Books Found <% end %>
collection.size
collection.size
メソッドは、コレクションに含まれるオブジェクトの個数を返します。
@book_count = @author.books.size
collection.find(...)
collection.find
メソッドは、コレクションに含まれるオブジェクトを検索します。
@available_book = @author.books.find(1)
collection.where(...)
collection.where
メソッドは、コレクションに含まれているオブジェクトを指定された条件に基いて検索します。このメソッドではオブジェクトは遅延読み込み(lazy load)されるので、オブジェクトに実際にアクセスするときだけデータベースへのクエリが発生します。
@available_books = @author.books.where(available: true) # クエリはまだ発生しない @available_book = @available_books.first # ここでクエリが発生する
collection.exists?(...)
collection.exists?
メソッドは、指定された条件に合うオブジェクトがコレクションの中に存在するかどうかをチェックします。
collection.build(attributes = {}, ...)
collection.build
メソッドは、関連付けされた型のオブジェクトまたはオブジェクトの配列を返します。返されるオブジェクトは、渡された属性に基いてインスタンス化され、外部キーを経由するリンクが作成されます。関連付けられたオブジェクトはまだ保存されていないことにご注意ください。
@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" } ])
collection.create(attributes = {})
collection.create
メソッドは、関連付けされた型の新しいオブジェクトまたはオブジェクトの配列を返します。このオブジェクトは、渡された属性を用いてインスタンス化され、そのオブジェクトの外部キーを介してリンクが作成されます。そして、関連付けられたモデルで指定されているバリデーションがすべてパスすると、この関連付けられたオブジェクトは保存されます。
@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" } ...
collection.create!(attributes = {})
上のcollection.create
と同じですが、レコードが無効な場合にActiveRecord::RecordInvalid
がraiseされる点が異なります。
collection.reload
collection.reload
メソッドは、関連付けられたすべてのオブジェクトのリレーションを1つ返し、データベースを強制的に読み出します。関連付けられたオブジェクトがない場合は、空のリレーションを1つ返します。
@books = @author.books.reload
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
:disable_joins
:ensuring_owner_was
:extend
:foreign_key
:foreign_type
:inverse_of
:primary_key
:query_constraints
:source
:source_type
:strict_loading
:through
:validate
:as
:as
オプションを設定すると、ポリモーフィック関連付けであることが指定されます。ポリモーフィック関連付けについて詳しくは本ガイドのポリモーフィック関連付けを参照してください。
:autosave
:autosave
オプションをtrue
に設定すると、親オブジェクトが保存されるたびに、読み込まれているすべてのメンバを保存し、destroyフラグが立っているメンバを破棄します。
:autosave
をfalse
に設定することと、:autosave
オプションを未設定のままにしておくことは同じではありません。:autosave
オプションを渡さない場合、関連付けられたオブジェクトのうち、新しいオブジェクトは保存されますが、更新されたオブジェクトは保存されません。
:class_name
関連付けの相手となるオブジェクト名を関連付け名から生成できない事情がある場合、:class_name
オプションを用いてモデル名を直接指定できます。たとえば、1人の著者(author)が複数の書籍(books)を持っているが、実際の書籍モデル名がTransaction
である場合には以下のように指定します。
class Author < ApplicationRecord has_many :books, class_name: "Transaction" end
:counter_cache
このオプションは、:counter_cache
オプションを任意の名前に変更したい場合に使います。このオプションは、belongs_toの関連付けで:counter_cache
の名前を変更したときにのみ必要になります。
:dependent
オーナーオブジェクトがdestroyされたときに、オーナーに関連付けられたオブジェクトの扱いを制御します。
:destroy
: 関連付けられたオブジェクトもすべて同時にdestroyされます。:delete_all
: 関連付けられたオブジェクトはすべてデータベースから直接削除されます(コールバックは実行されません)。:destroy_async
: オブジェクトが削除されるときに、ActiveRecord::DestroyAssociationAsyncJob
ジョブがジョブキューに入り、関連付けられたオブジェクトでdestroy
メソッドを呼び出します。このジョブが動作するには、Active Jobをセットアップしておく必要があります。:nullify
: 外部キーはNULL
に設定されます。ポリモーフィックなtypeカラムもポリモーフィック関連付けでNULL
に設定されます。コールバックは実行されません。:restrict_with_exception
: 関連付けられたレコードがある場合にActiveRecord::DeleteRestrictionError
例外が発生します。:restrict_with_error
: 関連付けられたオブジェクトがある場合にエラーがオーナーに追加されます。:destroy
オプションや:delete_all
オプションは、collection.delete
メソッドやcollection=
メソッドのセマンティクス(意味)にも影響します(コレクションから削除されると、関連付けられたオブジェクトもdestroyされます)。
:disable_joins
関連付けでJOINをスキップするかどうかを指定します。
true
を指定した場合、クエリが2つ以上生成されるようになります。ただし場合によっては、ORDERやLIMITが適用されるとデータベースの制限によりインメモリで処理されることがあります。
このオプションは、has_many :through associations
関連付けにのみ適用されます(has_many
単体ではJOINは実行されません)。
:ensuring_owner_was
オーナーによって呼び出されるインスタンスメソッドを指定します。関連付けられるレコードがバックグラウンドジョブで削除されるようにするには、このメソッドがtrue
を返さなければなりません。
:extend
返される関連付けオブジェクトを拡張するのに使うモジュール(またはモジュールの配列)を指定します。メソッドを複数の関連付けで定義する場合、特に複数の関連付けオブジェクト間で共有する必要がある場合に便利です。
:foreign_key
Railsの規約では、相手のモデル上の外部キーを保持しているカラム名については、そのモデル名にサフィックス _id
を追加した関連付け名が使われることを前提とします。:foreign_key
オプションを使うと外部キーの名前を直接指定できます。
class Author < ApplicationRecord has_many :books, foreign_key: "cust_id" end
Railsは外部キーのカラムを自動的に作成することはありません。外部キーを使うには、マイグレーションで明示的に定義する必要があります。
:foreign_type
ポリモーフィック関連付けが有効になっている場合、関連付けられるオブジェクトの型を保存するカラムを指定します。デフォルトでは、as
オプションで指定したポリモーフィック関連付け名に_type
サフィックスを追加した名前を推測します。したがって、has_many :tags, as: :taggable
という関連付けを定義するクラスでは、taggable_type
がデフォルトの:foreign_type
として使われます。
:inverse_of
:inverse_of
オプションは、その関連付けの逆関連付けとなるbelongs_to
関連付けの名前を指定します。
class Author < ApplicationRecord has_many :books, inverse_of: :author end class Book < ApplicationRecord belongs_to :author, inverse_of: :books end
: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
になります。
:query_constraints
複合外部キーとして機能するようになります。関連付けられるオブジェクトをクエリするために使うカラムのリストを定義します。この機能はオプションです(デフォルトでは、値の自動導出を試みます)。値が設定されている場合、Array
のサイズは、関連付けられるモデルの主キーのサイズまたはquery_constraints
のサイズと一致する必要があります。
:source
:source
オプションは、has_many :through
関連付けで用いる関連付け元の名前を指定します。このオプションが必要になるのは、関連付け名から関連付け元の名前を自動的に推論できない場合のみです。
:source_type
:source_type
オプションは、ポリモーフィック関連付けを介するhas_many :through
関連付けで、関連付け元の型を指定します。
class Author < ApplicationRecord has_many :books has_many :paperbacks, through: :books, source: :format, source_type: "Paperback" end class Book < ApplicationRecord belongs_to :format, polymorphic: true end class Hardback < ApplicationRecord; end class Paperback < ApplicationRecord; end
:strict_loading
true
を指定すると、関連付けられるレコードが、この関連付けを経由して読み込まれるたびにstrict loadingを強制するようになります。
:through
:through
オプションは、本ガイドで既に説明したhas_one :through
関連付けのクエリを実行する際に経由するjoinモデルを指定します。
:validate
:validate
オプションをfalse
に設定すると、新たに関連付けられたオブジェクトは保存時にバリデーションされません。デフォルトはtrue
であり、この場合新たに関連付けられたオブジェクトは保存時にバリデーションされます。
has_many
のスコープhas_many
で使われるクエリをカスタマイズしたい場合があります。スコープブロックを用いてこのようなカスタマイズを行えます。以下に例を示します。
class Author < ApplicationRecord has_many :books, -> { where processed: true } end
スコープブロック内では標準のクエリメソッドをすべて利用できます。ここでは以下について説明します。
where
extending
group
includes
limit
offset
order
readonly
select
distinct
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)が常に作成されます。
extending
extending
メソッドは、関連付けプロキシを拡張する名前付きモジュールを指定します。関連付けの拡張については後述します。
group
group
メソッドは、結果をグループ化する属性名を1つ指定します。内部的にはSQLのGROUP BY
句が使われます。
class Author < ApplicationRecord has_many :chapters, -> { group 'books.id' }, through: :books end
includes
includes
メソッドを使うと、その関連付けが使われるときにeager loadingすべき第2関連付けを指定できます。以下のモデルを例に考えてみましょう。
class Author < ApplicationRecord has_many :books end class Book < ApplicationRecord belongs_to :author has_many :chapters end class Chapter < ApplicationRecord belongs_to :book end
著者名(author)から本の章(chapter)を@author.books.chapters
のように直接取り出す頻度が高い場合は、Author
からBook
への関連付けを行なう時にChapters
をあらかじめincludes
しておくと、クエリが減って効率が高まります。
class Author < ApplicationRecord has_many :books, -> { includes :chapters } end class Book < ApplicationRecord belongs_to :author has_many :chapters end class Chapter < ApplicationRecord belongs_to :book end
limit
limit
メソッドは、関連付けを用いて取得できるオブジェクトの総数の上限を指定するのに使います。
class Author < ApplicationRecord has_many :recent_books, -> { order('published_at desc').limit(100) }, class_name: "Book" end
offset
offset
メソッドは、関連付けを用いてオブジェクトを取得する際の開始オフセットを指定します。たとえば、-> { offset(11) }
と指定すると、最初の11レコードはスキップされ、12レコード目以降が返されるようになります。
order
order
メソッドは、関連付けられたオブジェクトの並び順を指定します。内部的にはSQLのORDER BY
句が使われます。
class Author < ApplicationRecord has_many :books, -> { order "date_confirmed DESC" } end
readonly
readonly
を指定すると、関連付けられたオブジェクトを読み出し専用で取り出します。
select
select
メソッドを使うと、関連付けられたオブジェクトのデータ取り出しに使われるSQLのSELECT
句をオーバーライドできます。Railsはデフォルトではすべてのカラムを取り出します。
独自のselect
メソッドを使う場合には、関連付けられているモデルの主キーカラムと外部キーカラムを必ず含めておいてください。これを行わなかった場合、Railsでエラーが発生します。
distinct
distinct
メソッドは、コレクション内で重複が発生しないようにします。
このメソッドは:through
オプションと併用するときに特に便利です。
class Person < ApplicationRecord has_many :readings has_many :articles, through: :readings end
irb> person = Person.create(name: 'John') irb> article = Article.create(name: 'a1') irb> person.articles << article irb> person.articles << article irb> person.articles.to_a => [#<Article id: 5, name: "a1">, #<Article id: 5, name: "a1">] irb> Reading.all.to_a => [#<Reading id: 12, person_id: 5, article_id: 5>, #<Reading id: 13, person_id: 5, article_id: 5>]
上の例の場合、2つのreadingがどちらも同じ記事を指しており、person.articles
を実行すると同じ記事が2件取り出されてしまいます。
今度はdistinct
を設定してみましょう。
class Person has_many :readings has_many :articles, -> { distinct }, through: :readings end
irb> person = Person.create(name: 'Honda') irb> article = Article.create(name: 'a1') irb> person.articles << article irb> person.articles << article irb> person.articles.to_a => [#<Article id: 7, name: "a1">] irb> Reading.all.to_a => [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>]
上の例でも2つのreadingは重複していますが、person.articles
を実行すると1件の記事だけを表示します。これはコレクションが一意のレコードのみを読み出しているからです。
挿入時にも同様に、永続化済みのレコードをすべて一意にする(関連付けを検査したときに重複レコードが決して発生しないようにする)には、テーブル自体にuniqueインデックスを追加する必要があります。たとえばreadings
というテーブルがあるとすると、1人のpersonに記事を1回しか追加できないようにするには、マイグレーションに以下を追加します。
add_index :readings, [:person_id, :article_id], unique: true
インデックスが一意になると、同じ記事をpersonに2回追加しようとすると
ActiveRecord::RecordNotUnique
エラーが発生するようになります
irb> person = Person.create(name: 'Honda') irb> article = Article.create(name: 'a1') irb> person.articles << article irb> person.articles << article ActiveRecord::RecordNotUnique
なお、include?
などのRubyメソッドで一意性をチェックすると競合が発生しやすいので注意が必要です。関連付けで強制的に一意にする目的でinclude?
を使わないでください。たとえば上のarticleの例では、以下のコードで競合が発生しやすくなります。これは、複数のユーザーが同時にこのコードを実行する可能性があるためです。
person.articles << article unless person.articles.include?(article)
has_many
関連付けにオブジェクトを割り当てると、外部キーを更新するためにそのオブジェクトは自動的に保存されます。1つの文で複数のオブジェクトを割り当てると、それらはすべて保存されます。
関連付けられているオブジェクトのどれかがバリデーションエラーで保存に失敗すると、割り当ての状態がfalse
になり、割り当てはキャンセルされます。
親オブジェクト(has_many
関連付けを宣言している側のオブジェクト)が保存されない場合(つまりnew_record?
がtrue
を返す場合)、子オブジェクトは追加時に保存されません。親オブジェクトが保存されると、関連付けられていたオブジェクトのうち保存されていなかったメンバーはすべて保存されます。
has_many
関連付けにオブジェクトを割り当てて、しかもそのオブジェクトを保存したくない場合、collection.build
メソッドをお使いください。
has_and_belongs_to_many
関連付けの参照has_and_belongs_to_many
関連付けは、他のモデルとの間に「多対多」リレーションシップを作成します。データベースの観点では、2つのクラスは中間でjoinテーブルを介して関連付けられます。このjoinテーブルには、両方のクラスを参照する外部キーがそれぞれ含まれます。
has_and_belongs_to_many
で追加されるメソッドhas_and_belongs_to_many
関連付けを宣言したクラスでは、以下の17のメソッドが自動的に利用できるようになります。
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
has_and_belongs_to_many
関連付けで利用している中間のjoinテーブルが、2つの外部キー以外のカラムを含んでいる場合、これらのカラムは関連付けを介して取り出されるレコードに属性として追加されます。属性が追加されたレコードは常に読み出し専用になります。このようにして読み出された属性に対する変更は保存できないためです。
has_and_belongs_to_many
関連付けで使うjoinテーブルにこのような余分なカラムを追加することは非推奨化されています。2つのモデルを多対多リレーションシップで結合するjoinテーブルでこのような複雑な振る舞いが必要な場合は、has_and_belongs_to_many
ではなくhas_many :through
をお使いください。
collection
collection
メソッドは、関連付けられたすべてのオブジェクトのリレーションを1つ返します。関連付けられたオブジェクトがない場合は、空のリレーションを1つ返します。
@assemblies = @part.assemblies
collection<<(object, ...)
collection<<
メソッドは、joinテーブル上でレコードを作成し、それによって1個以上のオブジェクトをコレクションに追加します。
@part.assemblies << @assembly1
このメソッドはcollection.concat
およびcollection.push
のエイリアスです。
collection.delete(object, ...)
collection.delete
メソッドは、joinテーブル上のレコードを削除し、それによって1個以上のオブジェクトをコレクションから削除します。オブジェクトはdestroyされません。
@part.assemblies.delete(@assembly1)
collection.destroy(object, ...)
collection.destroy
メソッドは、joinテーブル上のレコードを削除することで、1個以上のオブジェクトをコレクションから削除します。オブジェクトはdestroyされません。
@part.assemblies.destroy(@assembly1)
collection=(objects)
collection=
メソッドは、指定したオブジェクトでそのコレクションの内容を置き換えます。元からあったオブジェクトは削除されます。この変更はデータベースで永続化されます。
collection_singular_ids
collection_singular_ids
メソッドは、そのコレクションに含まれるオブジェクトのidを配列にしたものを返します。
@assembly_ids = @part.assembly_ids
collection_singular_ids=(ids)
collection_singular_ids=
メソッドは、指定された主キーidを持つオブジェクトでコレクションの内容を置き換えます。元からあったオブジェクトは削除されます。この変更はデータベースで永続化されます。
collection.clear
collection.clear
メソッドは、joinテーブル上のレコードを削除し、それによってすべてのオブジェクトをコレクションから削除します。このメソッドを実行しても、関連付けられたオブジェクトはdestroyされません。
collection.empty?
collection.empty?
メソッドは、関連付けられたオブジェクトがコレクションに含まれていない場合にtrue
を返します。
<% if @part.assemblies.empty? %> ※この部品はどの完成品でも使われていません。 <% end %>
collection.size
collection.size
メソッドは、コレクションに含まれるオブジェクトの個数を返します。
@assembly_count = @part.assemblies.size
collection.find(...)
collection.find
メソッドは、コレクションに含まれるオブジェクトを検索します。
@assembly = @part.assemblies.find(1)
collection.where(...)
collection.where
メソッドは、コレクションに含まれているオブジェクトを指定された条件に基いて検索します。このメソッドではオブジェクトは遅延読み込み(lazy load)されるので、オブジェクトに実際にアクセスするときだけデータベースへのクエリが発生します。
@new_assemblies = @part.assemblies.where("created_at > ?", 2.days.ago)
collection.exists?(...)
collection.exists?
メソッドは、指定された条件に合うオブジェクトがコレクションの中に存在するかどうかをチェックします。
collection.build(attributes = {})
collection.build
メソッドは、関連付けられた型の新しいオブジェクトを1つ返します。このオブジェクトは、渡された属性でインスタンス化され、そのjoinテーブルを介してリンクが作成されます。ただし、関連付けられたオブジェクトはこの時点では保存されていないことにご注意ください。
@assembly = @part.assemblies.build({ assembly_name: "Transmission housing" })
collection.create(attributes = {})
collection.create
メソッドは、関連付けられた型の新しいオブジェクトを1つ返します。このオブジェクトは、渡された属性を用いてインスタンス化され、joinテーブルを介してリンクが作成されます。そして、関連付けられたモデルで指定されているバリデーションがすべてパスすると、この関連付けられたオブジェクトは保存されます。
@assembly = @part.assemblies.create({ assembly_name: "Transmission housing" })
collection.create!(attributes = {})
上のcollection.create
と同じですが、レコードが無効な場合にActiveRecord::RecordInvalid
がraiseされる点が異なります。
collection.reload
collection.reload
メソッドは、関連付けられたすべてのオブジェクトのリレーションを1つ返し、データベースを強制的に読み出します。関連付けられたオブジェクトがない場合は、空のリレーションを1つ返します。
@assemblies = @part.assemblies.reload
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
:strict_loading
:validate
:association_foreign_key
Railsの慣例では、相手のモデルを指す外部キーを保持しているjoinテーブル上のカラム名については、そのモデル名にサフィックス _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
:autosave
:autosave
オプションをtrue
に設定すると、親オブジェクトが保存されるたびに、読み込まれているすべての関連付けられたメンバを保存し、destroyフラグが立っているメンバを破棄します。
:autosave
をfalse
に設定することと、:autosave
オプションを未設定のままにしておくことは同じではありません。:autosave
オプションを渡さない場合、関連付けられたオブジェクトのうち、新しいオブジェクトは保存されますが、更新されたオブジェクトは保存されません。
:class_name
関連付けの相手となるオブジェクト名を関連付け名から生成できない事情がある場合、:class_name
オプションを用いてモデル名を直接指定できます。たとえば、1つの部品(Part)が複数の組み立て(Assembly)で使われ、組み立てを含む実際のモデル名がGadget
である場合、次のように設定します。
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, class_name: "Gadget" end
:foreign_key
Railsの規約では、そのモデルを指す外部キーを保持しているjoinテーブル上のカラム名については、そのモデル名にサフィックス _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
:join_table
辞書順に基いて生成されたjoinテーブルのデフォルト名が気に入らない場合、:join_table
オプションを用いてデフォルトのテーブル名を上書きできます。
:strict_loading
true
を指定すると、関連付けられるレコードが、この関連付けを経由して読み込まれるたびにstrict loadingを強制するようになります。
:validate
:validate
オプションをfalse
に設定すると、新たに関連付けられたオブジェクトは保存時にバリデーションされません。デフォルトはtrue
であり、この場合新たに関連付けられたオブジェクトは保存時にバリデーションされます。
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
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
を持つassembliesが作成されます。
extending
extending
メソッドは、関連付けプロキシを拡張する名前付きモジュールを指定します。関連付けの拡張については後述します。
group
group
メソッドは、結果をグループ化する属性名を1つ指定します。内部的にはSQLのGROUP BY
句が使われます。
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { group "factory" } end
includes
includes
メソッドを使うと、その関連付けが使われるときにeager loadingすべき第2関連付けを指定できます。
limit
limit
メソッドは、関連付けを用いて取得できるオブジェクトの総数の上限を指定するのに使います。
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { order("created_at DESC").limit(50) } end
offset
offset
メソッドは、関連付けを用いてオブジェクトを取得する際の開始オフセットを指定します。たとえばoffset(11)と指定すると、最初の11レコードはスキップされ、12レコード以降が返されるようになります。
order
order
メソッドは、関連付けられたオブジェクトの並び順を指定します。内部的にはSQLのORDER BY
句が使われます。
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { order "assembly_name ASC" } end
readonly
readonly
を指定すると、関連付けられたオブジェクトを読み出し専用で取り出します。
select
select
メソッドを使うと、関連付けられたオブジェクトのデータ取り出しに使われるSQLのSELECT
句をオーバーライドできます。Railsはデフォルトではすべてのカラムを取り出します。
distinct
distinct
メソッドは、コレクション内の重複を削除します。
has_and_belongs_to_many
関連付けにオブジェクトを割り当てると、joinテーブルを更新するためにそのオブジェクトは自動的に保存されます。1つの文で複数のオブジェクトを割り当てると、それらはすべて保存されます。
関連付けられているオブジェクト同士のどれかがバリデーションエラーで保存に失敗すると、割り当ての状態がfalse
になり、割り当てはキャンセルされます。
親オブジェクト(has_and_belongs_to_many
関連付けを宣言している側のオブジェクト)が保存されない場合(つまりnew_record?
がtrue
を返す場合)、子オブジェクトは追加時に保存されません。親オブジェクトが保存されると、関連付けられていたオブジェクトのうち保存されていなかったメンバはすべて保存されます。
has_and_belongs_to_many
関連付けにオブジェクトを割り当てて、しかもそのオブジェクトを保存したくない場合は、collection.build
メソッドをお使いください。
通常のコールバックは、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
関連付けのコールバックについて詳しくは、Active Recordコールバックガイドを参照してください。
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
関連付けオブジェクトのコレクションを返します。関連付けのスコープをさらに制御する必要がある状況では、関連付けのオーナーをスコープブロックに引数として渡すことが可能です。ただし、関連付けのプリロードができなくなる点にご注意ください。
class Supplier < ApplicationRecord has_one :account, ->(supplier) { where active: supplier.active? } end
異なるモデル間でフィールドや振る舞いを共有したい場合があります。
たとえば、Car
モデル、Motorcycle
モデル、Bicycle
モデルがあり、color
やprice
などのフィールドや、いくつかの関連メソッドを共有したいが、モデルごとに振る舞いやコントローラーが異なっているとしましょう。
まず、各モデルのベースとなるVehicle
モデルを生成します。
$ bin/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=親モデル
オプションを使って特定の親モデルを継承している点にご注目ください。このオプションを使うと、同様のマイグレーションファイルを生成せずにモデルを生成できます(該当するテーブルが既に存在しているため)。
たとえばCar
モデルの場合は以下のようになります。
$ bin/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')
単一テーブル継承(STI)は、サブクラスとその属性にほとんど違いがない場合に最適ですが、単一テーブルを作るために必要なすべてのサブクラスのすべての属性を含めることになります。
この方法の欠点は、テーブルが肥大化することです。他のクラスでは使われないサブクラス固有の属性まで含まれてしまうからです。
以下の例では、同じ"Entry"クラスを継承する2つのActive Recordモデルがあり、subject
属性が含まれています。
# Schema: entries[ id, type, subject, created_at, updated_at] class Entry < ApplicationRecord end class Comment < Entry end class Message < Entry end
delegated typesでは、delegated_type
を用いてこの問題を解決します。
delegated typesを使うためには、特定の方法でデータをモデル化する必要があります。要件は以下の通りです。
これにより、すべてのサブクラスで意図せず共有される属性を1個のテーブルで定義する必要がなくなります。
これを上記の例に適用するためには、モデルを再度生成する必要があります。
まず、スーパークラスとして機能するベースEntry
モデルを生成してみましょう:
$ bin/rails generate model entry entryable_type:string entryable_id:integer
次に、委譲で使う新しいMessage
モデルとComment
モデルを生成します。
$ bin/rails generate model message subject:string body:string $ bin/rails generate model comment content:string
ジェネレータを実行すると、次のようなモデルができるはずです。
# Schema: entries[ id, entryable_type, entryable_id, created_at, updated_at ] class Entry < ApplicationRecord end # Schema: messages[ id, subject, body, created_at, updated_at ] class Message < ApplicationRecord end # Schema: comments[ id, content, created_at, updated_at ] class Comment < ApplicationRecord end
delegated_type
を宣言するまず、Entry
スーパークラスでdelegated_type
を宣言します。
class Entry < ApplicationRecord delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy end
entryable
パラメータは、委譲で使うフィールドを指定し、委譲クラスとしてMessage
型とComment
型を含みます。
このEntry
クラスはentryable_type
フィールドとentryable_id
フィールドを持ちます。これは、delegated_type
の定義内で、entryable
という名前に_type
と_id
というサフィックスを付加したフィールドです。
entryable_type
には委譲サブクラス名が保存され、entryable_id
には委譲サブクラスのレコードIDが保存されます。
次に、それらのdelegated typesを実装するモジュールを定義する必要があります。このモジュールは、as: :entryable
パラメータをhas_one
関連付けに宣言することで定義します。
module Entryable extend ActiveSupport::Concern included do has_one :entry, as: :entryable, touch: true end end
続いて、作成したモジュールをサブクラスにinclude
します。
class Message < ApplicationRecord include Entryable end class Comment < ApplicationRecord include Entryable end
定義が完了し、Entry
デリゲーターは以下のメソッドを提供するようになりました。
メソッド | 戻り値 |
---|---|
Entry#entryable_class |
Message または Comment |
Entry#entryable_name |
"message" または "comment" |
Entry.messages |
Entry.where(entryable_type: "Message") |
Entry#message? |
entryable_type == "Message" の場合trueを返す |
Entry#message |
entryable_type == "Message" の場合はメッセージレコードを返し、それ以外の場合は nil を返す |
Entry#message_id |
entryable_type == "Message" の場合はentryable_id を返し、それ以外の場合はnil を返す |
Entry.comments |
Entry.where(entryable_type: "Comment") |
Entry#comment? |
entryable_type == "Comment" の場合はtrueを返す |
Entry#comment |
entryable_type == "Comment" の場合はコメント・メッセージを返し、それ以外の場合はnil を返す |
Entry#comment_id |
entryable_type == "Comment" の場合はentryable_id を返し、それ以外の場合はnil を返す |
新しいEntry
オブジェクトを作成する際に、entryable
サブクラスを同時に指定できます。
Entry.create! entryable: Message.new(subject: "hello!")
Entry
デリゲータを拡張し、delegates
を定義してサブクラスに対してポリモーフィズムを使用することで、さらに拡張できます。
たとえば、Entry
のtitle
メソッドをそのサブクラスに委譲するには以下のようにします。
class Entry < ApplicationRecord delegated_type :entryable, types: %w[ Message Comment ] delegate :title, to: :entryable end class Message < ApplicationRecord include Entryable def title subject end end class Comment < ApplicationRecord include Entryable def title content.truncate(20) end end
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。