0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Ruby on Rails】モデルを関連づける方法(Active Recordの関連付け)。:referencesやbelongs_toなど

Posted at

個人メモです。

RailsにおけるモデルであるActive Recordには関連付け(association)という機能がある。

この関連付けとは何なのかについて。

Rails公式 Active Recordの関連付けとは

目次

  1. モデルの関連付けとは?
  2. 関連付けの種類
  3. 関連付けの注意点
  4. belongs_toとhas_oneどちらを選ぶべきか?
  5. has_many :throughとhas_and_belongs_to_manyのどちらを選ぶかべきか?
  6. 効率的な関連付けのために知っておくべきこと
    1. キャッシュ制御
    2. 名前衝突の回避
    3. スキーマの更新
    4. 関連付けのスコープ制御
    5. 双方向関連付け

モデルの関連付けとは?

モデルはデフォルトでは個々に独立しており関連性がない。関連付けとは複数のモデル同士に関連性を持たせつこと。(そのまま、、)

関連付けとは同期のようなもので、あるモデルの内容を変更すると、それと連動して、関連するモデルの内容も変更できるようになる

いちいち同じ処理を繰り返さなくていい便利機能。


## 関連付けの種類 6種類の関連付けがサポートされている。1つのモデルに複数使うことができる。
関連付け 意味 内容
belongs_to 従属。1対1 一つのインスタンス同士が繋がる
has_one まるごと含む(所有) すべてのインスタンスが関連する
has_many 1対多 複数のインスタンスが繋がる。(関連付けされている側にはbelong_toが使われることが多い)
has_many: through 多対多。モデルが3つ以上 中継するインスタンスを持つモデルが存在する。(モデル1 ⇄ モデル2 ⇄ モデル3)
has_one: through 1対1。モデル3つ以上 あるモデルを経由してインスタンスを取得できる。(モデル1 → モデル2 → モデル3)
has_and_belongs_to_many 多対多。モデルは2つ belongs_toでモデルを関連づけるが、インスタンスの間に複数のデータが存在する場合。(モデルは2つだが、接続用のテーブルが必要)

## 関連付けの注意点

・モデル名は単数形(命名ルールに従う)
Railsがクラス名を推測するときに命名ルールに従うため。複数形の名前がついていると推測できなくなる。

・has_manyでつなげるテーブル名は複数形になる
対象のモデルの複数のインスタンスが繋がるため。(対象がBookモデルなら、has_many :books

・関連付けは通常双方向で設定する
通常、2つのモデル両方に関連を定義する必要がある。双方向の関連付けが認識されると、一方のデータの取得のみでもう一方を操作できるようになる(通信回数が減り効率化に繋がる)


## belongs_toとhas_oneどちらを選ぶべきか? 結論的にはど**ちらも必要。主体となるモデルにhas_oneを記述する**。

2つのモデルの間に1対1の関係を作りたい場合は、一方のモデルにbelongs_toを追加し、もう一方にhas_oneを追加する。

メインとなるモデルに所有を意味するhas_oneを、従属するモデルにbelongs_toを設置する。

SupplierとAccountという2つのモデルがあった場合、SupplierがAccountを持っているのが普通なので、Supplierにhas_one: account, Accountにbelongs_to: supplierを記述する。

# モデル1
class Supplier < ApplicationRecord
  has_one :account
end

# モデル2
class Account < ApplicationRecord
  belongs_to :supplier
end

## `has_many :through`と`has_and_belongs_to_many`のどちらを選ぶかべきか?

どちらも多対多の宣言をする時に使う。違いは、介在するモデルを、独立したエンティティとして扱いたいかどうか。

簡単なのは、has_and_belongs_to_many。介在するモデルを作らず、接続用のテーブルだけ作成して関連づける。

中継するモデルをエンティティとして独立して扱いたい場合は、has_many :throughを使う。

また、中継のモデルで検証(validation)、コールバック、追加の属性が必要な場合は、has_many :throughを使う。

has_and_belongs_to_many
# モデル1
class Assembly < ApplicationRecord
  has_and_belongs_to_many :parts
end

# モデル2
class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end

has_many_through
# モデル1
class Assembly < ApplicationRecord
  has_many :manifests
  has_many :parts, through: :manifests
end

# モデル2
class Manifest < ApplicationRecord
  belongs_to :assembly
  belongs_to :part
end

# モデル3
class Part < ApplicationRecord
  has_many :manifests
  has_many :assemblies, through: :manifests
end

## 効率的な関連付けのために知っておくべきこと
  1. キャッシュ制御
  2. 名前衝突の回避
  3. スキーマの更新
  4. 関連付けのスコープ制御
  5. 双方向関連付け

1. キャッシュ制御

関連付けのメソッドはすべて、効率化のためキャッシュを中心に構築されている。(毎回通信してデータを取得することはしない)

  • 最後に実行したクエリの結果はキャッシュに保持され、次回以降の操作で利用できる。
  • キャッシュはメソッド間でも共有される。
キャッシュを使った処理の例
author.books       # データベースからbooksを取得
author.books.size  # booksのキャッシュコピーが使われる
author.books.empty? # booksのキャッシュコピーが使われる


# キャッシュ破棄(.reload)
author.books.reload.empty?  # booksのキャッシュコピーが破棄される
                            # 次の処理ではDBから再度読み込み

### 2. 使ってはいけないモデル名(名前衝突の回避) attributesやconnectionは関連付けに使ってはいけない。

生成したモデルが継承しているApplicationRecordが継承しているActiveRecord::Baseのインスタンスには既に使われているメソッドがある。

モデルの関連付けをすると、命名ルールに従ってメソッドが追加されるが、これによりベースのメソッドを上書きしてしまうリスクがある。


## 3. スキーマの更新 定義した関連付けに合わせて、スキーマを更新する必要がある。

belongs_toを使う場合は、外部キーを作成する必要がある

has_and_belongs_to_manyを使う場合は、適切なjoinテーブルを作成する必要がある。joinテーブルの名前はアルファベット順になる(詳細は後述)。

3-1. 外部キーの作成(belongs_to)

例えば、Bookモデルをbelongs_toでAuthorモデルと関連づける場合は以下のようになる。

Bookモデル
class Book < ApplicationRecord
  belongs_to :author
end

▼新しいテーブルの場合
t.references :テーブル名(単数形)

マイグレーションファイル
class CreateBooks < ActiveRecord::Migration[5.0]
  def change
    create_table :books do |t|
      t.datetime :published_at
      t.string   :book_number
      t.references :author
    end
  end
end

**▼既存のテーブルの場合** `add_reference :関連づけるモデル1, :関連づけるモデル2`
マイグレーションファイル
class AddAuthorToBooks < ActiveRecord::Migration[5.0]
  def change
    add_reference :books, :author
  end
end

### 3-2. 関連付けに対応するjoinテーブルの作成(has_and_belongs_to_many)

AuthorモデルとBookモデルは命名ルールより、authorsとbooksというテーブルと対応している。

この2つのモデルをhas_and_belongs_to_manyで関連付けた場合は、中間のjoinテーブルとして、

モデル1のテーブル名_モデル2のテーブル名というテーブルを生成する必要がある。

モデルのテーブル名の順番はアルファベット順になる。(AuthorとBookなら、authors_booksというテーブル名になる)

モデル
# モデル1
class Assembly < ApplicationRecord
  has_and_belongs_to_many :parts
end

# モデル2
class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end

関連付けに対応するassemblies_partsテーブルをマイグレーションで作成ておく。(このテーブルには主キーを設定しないこと)

▼create_tableを使う場合

マイグレーション
class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0]
  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

id: false
joinテーブルはモデルを表さないので、create_tableにid: falseを渡す。これがないと関連付けは正常に動作しない。


**create_join_tableを使う場合** create_tableではなく、create_join_tablを使うと簡単に記述できる。
マイグレーション
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

## 4. 関連付けのスコープ制御 関連付けによって探索されるオブジェクトは、現在のモジュールのスコープ内のものだけ。

モジュール外のモデルを同士を紐づけるためには、完全な名前空間を指定する必要がある。

4-1. 同じモジュール内の場合

モジュール
module MyApplication
  module Business
    class Supplier < ApplicationRecord
      has_one :account
    end

    class Account < ApplicationRecord
      belongs_to :supplier
    end
  end
end

4-2. 異なるモジュール内の場合

class_name:で完全な名前空間を指定する。

モジュール
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

### 4-3. NG事例
モジュール
module MyApplication
  module Business
    class Supplier < ApplicationRecord
      has_one :account
    end
  end

  module Billing
    class Account < ApplicationRecord
      belongs_to :supplier
    end
  end
end

## 5. 双方向関連付け 通常、2つのモデル両方に関連を定義する必要がある。

これを行うと、モデルが双方向の関連を共有していることを自動的に認識し、オブジェクトのコピーを1つだけ読み出し、アプリケーションをより効率的かつ一貫性を持って操作できるようになる。

5-1. has_manyやbelongs_toの場合

モデルの関連付け
class Author < ApplicationRecord
  has_many :books
end

class Book < ApplicationRecord
  belongs_to :author
end

双方向関連性の確認
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

Authorモデルのインスタンス、a.first_nameを変更すると、Bookモデルのインスタンスb.author.first_nameも自動で変更されている。


## 5-2. throughやforeign_keyの場合 :throughや:foreign_keyオプションを使う場合は、`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
双方向関連性の確認
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

Authorモデルのインスタンス、a.first_nameを変更すると、Bookモデルのインスタンスb.author.first_nameも自動で変更されている。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?