Rails4 アソシエーション


[Active Recordの関連付け(アソシエーション)](http://railsguides.jp/association_basics.html)

## 注意点

* テーブル名は必ず複数形
* クラス名は必ず単数系
* 関連づけから自動的にモデルクラス名を推測するので、記載するときは、気をつける
* rails generateに記載するのは、クラス名なので、単数系（migrationの作成は自動で、複数形のテーブルを生成してくれる）

## 関連づけの種類

モデルに記載する

### belongs_to (1:1の従属 A→B)

orderは、customerに **従属** している

order:customerの場合

```ruby
class Order < ActiveRecord::Base
  belongs_to :customer
end
```

orderのmodel classに、belongs_to :customer を記載する

belongs_toは、 **必ず単数系**


### has_one (1:1の所有 A←B)

supplierは、accountを **所有** している

```ruby
class Supplier < ActiveRecord::Base
  has_one :account
end
```

### has_many (1:多の所有 A←B)

「0個以上の」インスタンスを 所有

customerは、orderを0個以上 **所有** している

```ruby
class Customer < ActiveRecord::Base
  has_many :orders
end
```

has_manyは、 **必ず複数系**


## has_many :through (多:多 中間の所有 A→B←C)

2つのモデルの間に「第3のモデル」(結合モデル)が介在する
相手モデルの「0個以上」のインスタンスとマッチ

例：患者(patient)が医師(physician)との診察予約(appointment)

```ruby
class Physician < ActiveRecord::Base
  has_many :appointments
  has_many :patients, through: :appointments
end
 
class Appointment < ActiveRecord::Base
  belongs_to :physician
  belongs_to :patient
end
 
class Patient < ActiveRecord::Base
  has_many :appointments
  has_many :physicians, through: :appointments
end
```

```ruby:migration
class CreateAppointments < ActiveRecord::Migration
  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
```

1つのドキュメントに多くの節(section)があり、1つの節の下に多くの段落(paragraph)がある状態で、節をスキップしてドキュメントの下のすべての段落の単純なコレクションが欲しいとします

ツリーの様な構造

```ruby
class Document < ActiveRecord::Base
  has_many :sections
  has_many :paragraphs, through: :sections
end
 
class Section < ActiveRecord::Base
  belongs_to :document
  has_many :paragraphs
end
 
class Paragraph < ActiveRecord::Base
  belongs_to :section
end
```

***!! throughは、そこを通るイメージ !!***


### has_one :through(1:1 中間の所有 A→B→C)

1人の提供者(supplier)が1つのアカウントに関連付けられ、さらに1つのアカウントが1つのアカウント履歴に関連付けられる場合

A → B → C の用な関係(1:1)

```ruby
class Supplier < ActiveRecord::Base
  has_one :account
  has_one :account_history, through: :account
end
 
class Account < ActiveRecord::Base
  belongs_to :supplier
  has_one :account_history
end
 
class AccountHistory < ActiveRecord::Base
  belongs_to :account
end
```

### has_and_belongs_to_many(多:多 互いに従属&所有)

「多対多」のつながりを作成しますが、through:を指定した場合と異なり、第3のモデル(結合モデル)が介在しません
**※結合用のテーブルは必要(モデルは不要)**

```ruby
class Assembly < ActiveRecord::Base
  has_and_belongs_to_many :parts
end
 
class Part < ActiveRecord::Base
  has_and_belongs_to_many :assemblies
end
```

```ruby:migration
class CreateAssembliesAndParts < ActiveRecord::Migration
  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
```

## tips

### belongs_toとhas_oneどちらを選ぶか

外部キーをどちらに置くか
外部キー = belongs_to

どちらが主語(目的)か。

* 供給者がアカウントを持っている ○
* アカウントが供給者を持っている ×

```ruby:○
class Supplier < ActiveRecord::Base
  has_one :account
end
 
class Account < ActiveRecord::Base
  belongs_to :supplier
end
```
```ruby
class CreateSuppliers < ActiveRecord::Migration
  def change
    create_table :suppliers do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :accounts do |t|
      t.integer :supplier_id #外部キーの指定 (3or4系 t.references :supplier と書く)
      t.string  :account_number
      t.timestamps
    end
  end
end
```

### has_many :throughとhas_and_belongs_to_manyのどちらを選ぶか

#### has_many :through

リレーションシップのモデルそれ自体を独立したエンティティとして扱いたい
***中間モデルを使いたい場合***

#### has_and_belogns_to_many

リレーションシップのモデルで何か特別なことをする必要がまったくないのであれば、結合モデルの不要なhas_and_belongs_to_manyリレーションシップを使用するのがシンプル
***中間モデルは不要な場合***


#### まとめ

結合モデルで検証(validation)、コールバック、追加の属性が必要なのであれば、has_many :throughを使用した方がよい

### ポリモーフィック関連付け

ある1つのモデルが他の複数のモデルに属していることを、1つの関連付けだけで表現する
**※エイリアスを設定して、対応するイメージ**

写真を、社員と商品の両方に従属させるようにしたい


```ruby
class Picture < ActiveRecord::Base
  belongs_to :imageable, polymorphic: true
end
 
class Employee < ActiveRecord::Base
  has_many :pictures, as: :imageable
end
 
class Product < ActiveRecord::Base
  has_many :pictures, as: :imageable
end
```

```ruby:migration
class CreatePictures < ActiveRecord::Migration
  def change
    create_table :pictures do |t|
      t.string  :name
      t.references :imageable, polymorphic: true
      t.timestamps
    end
  end
end
```

### 自己結合

1つのデータベースモデルに全従業員を格納しておきたいが、マネージャーと部下(subordinate)の関係も追えるようにしておきたい場合

```ruby
class Employee < ActiveRecord::Base
  has_many :subordinates, class_name: "Employee",
                          foreign_key: "manager_id"
 
  belongs_to :manager, class_name: "Employee"
end
```
```ruby:migration
class CreateEmployees < ActiveRecord::Migration
  def change
    create_table :employees do |t|
      t.references :manager #自信に外部キーを設定する
      t.timestamps
    end
  end
end
```

## 注意事項

### キャッシュ制御

関連付けのメソッドは、すべてキャッシュを中心に構築される
別の場所から関連テーブルが更新された場合、キャッシュが残り、悪影響を及ぼす可能性がある

関連付けのメソッド呼び出しでtrueを指定する

	customer.orders(true).empty? 

### 名前衝突の回避

関連付けを作成すると、モデルにその名前のメソッドが追加される
ActiveRecord::Baseのインスタンスで既に使用されているような名前を関連付けに使用するのは禁物

### スキーマの更新

#### belongs_toは、対象テーブルに外部キーが設定されている必要がある

```ruby
class CreateOrders < ActiveRecord::Migration
  def change
    create_table :orders do |t|
      t.datetime :order_date
      t.string   :order_number
      t.integer  :customer_id # 外部キーは、t.references or t.モデル名_id
    end
  end
end
```

#### has_and_belongs_to_manyは、それに対応する結合(join)テーブルを明示的に作成しておく

自動：辞書順に結合したもの
手動：:join_tableオプションを指定してする

```ruby
class CreateAssembliesPartsJoinTable < ActiveRecord::Migration
  def change
	# 主キーは設定しないこと
    create_table :assemblies_parts, id: false do |t|
      t.integer :assembly_id
      t.integer :part_id
    end
  end
end
```

### 関連付けのスコープ制御

関連付けによって探索されるオブジェクトは、 **現在のモジュールのスコープ内**のみ

異なる名前空間にあるモデルを関連付けるには、関連付けの宣言で **完全なクラス名を指定する**

```ruby
module MyApplication
  module Business
    class Supplier < ActiveRecord::Base
       has_one :account,
        class_name: "MyApplication::Billing::Account"
    end
  end
 
  module Billing
    class Account < ActiveRecord::Base
       belongs_to :supplier,
        class_name: "MyApplication::Business::Supplier"
    end
  end
end
```

### 双方向関連づけ

```ruby
c = Customer.first
o = c.orders.first
c.first_name == o.customer.first_name # => true
c.first_name = 'Manny'
c.first_name == o.customer.first_name # => false
```

途中で共通の値を更新した場合、双方向連携は正常に動作しない

inverse_ofオプションをつけることで回避できるが、下記制限がある

* :through関連付けと併用することはできません。
* :polymorphic関連付けと併用することはできません。
* :as関連付けと併用することはできません。
* belongs_to関連付けの場合、has_manyの逆関連付けは無視されます。

標準な名前であれば、通常でサポートされるが、
下記の場合サポートされないため、明示する必要がある
しかし、polymorphicなどは、上記制限のため、使えない？

* :conditions
* :through
* :polymorphic
* :foreign_key