2013年6月14日金曜日

[Rails3] has_many through で polymorphic な関連で相互に参照する

Rails3で has_many through でしかも polymorphic な関連を実装していて、polymorphicなモデル側からhas_manyを探せるけど、逆のやり方に苦戦したのでメモ

User モデルは複数の Clubに属すことができて、Clubもまた複数の Userを受け入れられるという感じ。

最初にやってた方法

class Club < ActiveRecord::Base
attr_accessible :name
has_many :memberships,
as: :membable
has_many :members,
source: :user
end
view raw club.rb hosted with ❤ by GitHub
class Membership < ActiveRecord::Base
attr_accessible :membable_id, :membable_type, :user_id
belongs_to :club, polymorphic: true
belongs_to :user
end
view raw memberships.rb hosted with ❤ by GitHub
class User < ActiveRecord::Base
attr_accessible :name
has_many :memberships,
dependent: :destroy
has_many :membered_clubs,
through: :memberships,
source: :membable,
source_type: 'Club'
end
view raw user.rb hosted with ❤ by GitHub

Membership はpolymorphicなモデルにしていて、例えばClub以外にもFamilyとか他のグループでも同じモデルを使いまわしたかった。

データを作ってみる

rails consoleでデータを作成

user = User.create({ name: 'kozo' })
=> #<User id: 1, name: "kozo", created_at: "2013-06-14 03:14:59", updated_at: "2013-06-14 03:14:59">
club = Club.create({ name: 'football' })
=> #<Club id: 1, name: "football", created_at: "2013-06-14 03:14:50", updated_at: "2013-06-14 03:14:50">

club.memberships.create({ user_id: user.id })
=> #<Membership id: 1, user_id: 1, membable_id: 1, membable_type: "Club", created_at: "2013-06-14 03:15:19", updated_at: "2013-06-14 03:15:19">

Club -> Userの参照はうまくいく

こんな感じにClub側からUserを参照するのは簡単にできちゃう

club = Club.find(1)
club.members
# =>[#<User id: 1, name: "kozo", created_at: "2013-06-14 03:14:59", updated_at: "2013-06-14 03:14:59">]

User -> Clubの参照はできなかった・・・

user = User.find(1)
user.membered_clubs
# => ActiveRecord::HasManyThroughSourceAssociationNotFoundError: 
 Could not find the source association(s) :membable in model Membership.
 Try 'has_many :membered_clubs, :through => :memberships, :source => <name>'. Is it one of :user or :club?

ドキュメントにはsourceとsource_typeを使えばOKって書いてあるっぽい

ruby/rails/RailsGuidesをゆっくり和訳してみたよ/Active Record Associations - 株式会社ウサギィwiki

4.3.2.18 :source

:source オプションは has_many :through アソシエーションの元になるアソシエーション名を指定します。 元のアソシエーションの名前が自動的にアソシエーション名から推測できない場合にのみ、このオプションを使用する必要があります。

4.3.2.19 :source_type

:source_type オプションは polymorphic アソシエーションを通じて進める has_many :through アソシエーションの元になる型を指定します。

でもググったらすぐ出てきた

has_many :through - The other side of polymorphic :through associations

MembershipとUserに細かく書いてあげるといいっぽい

class Membership < ActiveRecord::Base
attr_accessible :membable_id, :membable_type, :user_id
belongs_to :user
belongs_to :membable,
polymorphic: true
belongs_to :club,
class_name: 'Club',
foreign_key: 'membable_id'
end
view raw membership.rb hosted with ❤ by GitHub
class User < ActiveRecord::Base
attr_accessible :name
has_many :memberships,
dependent: :destroy
has_many :membered_clubs,
through: :memberships,
source: :club,
conditions: "memberships.membable_type = 'Club'"
end
view raw user.rb hosted with ❤ by GitHub
club = Club.find(1)
=> #<Club id: 1, name: "football", created_at: "2013-06-14 03:14:50", updated_at: "2013-06-14 03:14:50">
club.members
=> [#<User id: 1, name: "kozo", created_at: "2013-06-14 03:14:59", updated_at: "2013-06-14 03:14:59">]

user = User.find(1)
=> #<User id: 1, name: "kozo", created_at: "2013-06-14 03:14:59", updated_at: "2013-06-14 03:14:59">
user.membered_clubs
=> [#<Club id: 1, name: "football", created_at: "2013-06-14 03:14:50", updated_at: "2013-06-14 03:14:50">]

できた!

0 コメント:

コメントを投稿