LoginSignup
37
31

More than 5 years have passed since last update.

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

Posted at

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

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

どんな場合に使うの?

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

Untitled_Untitled_-_Cacoo_-_Vimperator.png

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

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

どんな風に実現するの?

User model を作成

$ rails g scaffold name:string

Profile modelを作成

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

$ 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

データを入れてみる

u = User.create(name: 'aaa')
u.profile = Profile.create(disription: 'test')
  • profilable_type : 親modelのclass名
  • profilable_id : 親modelのid
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

データを入れてみる

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

s = Shop.create(name: 'bbb')
s.profile = Profile.create(disription: 'abcd')
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

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

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

profilable_type と、 profilable_id で SELECT している

  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

コード

感想

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

参考

37
31
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
37
31