ポリモーフィック関連覚え書き

# ポリモーフィック関連って何?

複数の親を持つ子テーブルを実現するための関連の仕組み

# どんな場合に使うの?

例えば、 Shop model と、User model でそれぞれ、Profile を持たせたいが
持たせるProfileの内容が全く同じな場合とか

![Untitled_Untitled_-_Cacoo_-_Vimperator.png](https://qiita-image-store.s3.amazonaws.com/0/5329/bb938b7d-1213-94e1-53f6-781b3ac6c70b.png)

- 同じ内容のmodelを2個作っても良いけど、
    - 変更時に2個変更させなければならなくてめんどい
    - ShopかUserか関係なく、とにかくProfileの一覧を出したいときにめんどい

そんな時は、ポリモーフィック関連を使って、Shop modelとUser model両方を親とするProfile modelを作成する

# どんな風に実現するの?

## User model を作成

```
$ rails g scaffold name:string
```

## Profile modelを作成

`#{関連名}:references{polymorphic}` でポリモーフィック関連のためのカラムを生成

- 関連名については、`#{自model名}able` にする場合が多いらしい
    - 参考 : [Rails ポリモーフィック関連の関連名の命名 - 130単位](http://d.hatena.ne.jp/deeeki/touch/20130903/polymorphic_associations_naming)

```
$ rails g model profile profilable:references{polymorphic} disription:string
```

## migrationとmodel の実装

こんなかんじ

```db/migrate/20160621160304_create_profiles.rb
class CreateProfiles < ActiveRecord::Migration
  def change
    create_table :profiles do |t|
      t.references :profilable, polymorphic: true, index: true
      t.string :disription

      t.timestamps null: false
    end
  end
end
```

```app/models/profile.rb
class Profile < ActiveRecord::Base
  belongs_to :profilable, polymorphic: true
end
```

```app/models/user.rb
class User < ActiveRecord::Base
  has_one :profile, :as => :profilable
end
```

## データを入れてみる

```.rb
u = User.create(name: 'aaa')
u.profile = Profile.create(disription: 'test')
```

- `profilable_type` : 親modelのclass名
- `profilable_id` : 親modelのid

```sql
sqlite> select * from users;
id          name        created_at                  updated_at
----------  ----------  --------------------------  --------------------------
1           aaa         2016-06-21 16:07:03.154460  2016-06-21 16:07:03.154460

sqlite> select * from profiles;
id          profilable_id  profilable_type  disription  created_at                  updated_at
----------  -------------  ---------------  ----------  --------------------------  --------------------------
1           1              User             test        2016-06-21 16:07:45.587894  2016-06-21 16:07:45.593233
```

## Shop modelも生成

```
$ rails g model shop name:string
```

```app/model/shop.rb
class Shop < ActiveRecord::Base
  has_one :profile, :as => :profilable
end
```


## データを入れてみる

```.rb
s = Shop.create(name: 'aaa')
s.profile = Profile.create(disription: 'abc')

s = Shop.create(name: 'bbb')
s.profile = Profile.create(disription: 'abcd')
```

```.sql
sqlite> select * from shops;
id          name        created_at                  updated_at
----------  ----------  --------------------------  --------------------------
1           aaa         2016-06-21 16:13:06.268592  2016-06-21 16:13:06.268592
2           bbb         2016-06-21 16:13:39.186536  2016-06-21 16:13:39.186536

sqlite> select * from profiles;
id          profilable_id  profilable_type  disription  created_at                  updated_at
----------  -------------  ---------------  ----------  --------------------------  --------------------------
1           1              User             test        2016-06-21 16:07:45.587894  2016-06-21 16:07:45.593233
2           1              Shop             abc         2016-06-21 16:13:06.295915  2016-06-21 16:13:06.331801
3           2              Shop             abcd        2016-06-21 16:13:44.673048  2016-06-21 16:13:44.680561
```

## ポリモーフィック関連を検索するときにどんなクエリが発行されているか?

```.rb
Shop.find_by(name: 'bbb').profile.disription
```

`profilable_type` と、 `profilable_id` で SELECT している

```.sql
  Shop Load (0.3ms)  SELECT  "shops".* FROM "shops" WHERE "shops"."name" = ? LIMIT 1  [["name", "bbb"]]
  Profile Load (0.2ms)  SELECT  "profiles".* FROM "profiles" WHERE "profiles"."profilable_id" = ? AND "profiles"."profilable_type" = ? LIMIT 1  [["profilable_id", 2], ["profilable_type", "Shop"]]
=> "abcd"
```

## 子から親modelにアクセスする方法

`profilable` を使う

```
Profile.find_by(disription: 'abc').profilable.class.to_s # => Shop
```

# コード

https://github.com/kasei-san/polymorphic_association

# 感想

- オブジェクト指向っぽい
- 全く同じmodelを使うことが確定しているならば便利そう
    - その裏返しで、特定の親の場合だけ、あるカラムが必要になったとかになると大変そう
- 検索時に、id と文字列で検索しているので、1回で大量の子要素にアクセスする場合、処理時間を気にしたほうがよいかも
    - 大量に短時間に更新する必要があるなら、1つのテーブルに入れたほうが速度はでそう

# 参考

- [Rails ポリモーフィック関連の関連名の命名 - 130単位](http://d.hatena.ne.jp/deeeki/touch/20130903/polymorphic_associations_naming)
- [Rails.GrAFR: ポリモーフィック関連のDB構造](http://rails-gf.blogspot.jp/2012/12/db.html?m=1)