ポリモーフィック関連って何?
複数の親を持つ子テーブルを実現するための関連の仕組み
どんな場合に使うの?
例えば、 Shop model と、User model でそれぞれ、Profile を持たせたいが
持たせるProfileの内容が全く同じな場合とか
- 同じ内容の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 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
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
コード
感想
- オブジェクト指向っぽい
- 全く同じmodelを使うことが確定しているならば便利そう
- その裏返しで、特定の親の場合だけ、あるカラムが必要になったとかになると大変そう
- 検索時に、id と文字列で検索しているので、1回で大量の子要素にアクセスする場合、処理時間を気にしたほうがよいかも
- 大量に短時間に更新する必要があるなら、1つのテーブルに入れたほうが速度はでそう