Mar*_*kus 8 activerecord ruby-on-rails has-many-through
让我们使用这些类:
class User < ActiveRecord::Base
has_many :project_participations
has_many :projects, through: :project_participations, inverse_of: :users
end
class ProjectParticipation < ActiveRecord::Base
belongs_to :user
belongs_to :project
enum role: { member: 0, manager: 1 }
end
class Project < ActiveRecord::Base
has_many :project_participations
has_many :users, through: :project_participations, inverse_of: :projects
end
Run Code Online (Sandbox Code Playgroud)
A user
可以参与许多projects
角色扮演a member
或a manager
.调用连接模型ProjectParticipation
.
我现在在使用未保存对象上的关联时遇到问题.以下命令的工作方式与我认为应该有效相同:
# first example
u = User.new
p = Project.new
u.projects << p
u.projects
=> #<ActiveRecord::Associations::CollectionProxy [#<Project id: nil>]>
u.project_participations
=> #<ActiveRecord::Associations::CollectionProxy [#<ProjectParticipation id: nil, user_id: nil, project_id: nil, role: nil>]>
Run Code Online (Sandbox Code Playgroud)
到目前为止好- AR创建的ProjectParticipation
本身,我可以访问projects
的user
使用u.projects
.
但是,如果我ProjectParticipation
自己创建它,它就不起作用:
# second example
u = User.new
pp = ProjectParticipation.new
p = Project.new
pp.project = p # assign project to project_participation
u.project_participations << pp # assign project_participation to user
u.project_participations
=> #<ActiveRecord::Associations::CollectionProxy [#<ProjectParticipation id: nil, user_id: nil, project_id: nil, role: nil>]>
u.projects
=> #<ActiveRecord::Associations::CollectionProxy []>
Run Code Online (Sandbox Code Playgroud)
为什么项目是空的?我不能u.projects
像以前那样访问项目.
但如果我直接参与,项目会显示:
u.project_participations.map(&:project)
=> [#<Project id: nil>]
Run Code Online (Sandbox Code Playgroud)
它不应该像第一个例子一样直接工作:u.projects
不依赖于我是否自己创建连接对象而返回所有项目?或者我怎样才能让AR意识到这一点?
Sur*_*rya 10
简答:不,第二个例子不会像第一个例子那样有效.您必须使用第一个示例直接与用户和项目对象创建中间关联的方法.
答案很长:
在我们开始之前,我们应该知道如何has_many :through
处理ActiveRecord::Base
.所以,让我们从这里has_many(name, scope = nil, options = {}, &extension)
调用其关联构建器的方法开始,在方法结束时返回反射,然后将反射添加到散列中作为具有键值对的缓存.
现在的问题是,这些关联如何被激活?!?!
这是因为association(name)
方法.哪个调用association_class
方法实际调用并返回此常量:Associations::HasManyThroughAssociation
这使得该行自动加载active_record/associations/has_many_through_association.rb并在此实例化其实例 .这是业主和反思在创建关联时被保存,并在接下来的复位方法被调用它得到在子类中调用这里.ActiveRecord::Associations::CollectionAssociation
为什么这个重置呼叫很重要?因为,它设置@target
为数组.这@target
是一个数组,其中所有关联对象在您进行查询时存储,然后在代码中重复使用而不是创建新查询时用作缓存.这就是为什么调用user.projects
(用户和项目在db中持续存在,即调用:user = User.find(1)
然后user.projects
)将进行数据库查询并再次调用它的原因.
因此,当您在一个关联上调用读者时,例如:user.projects
,它会在填充from 之前调用collectionProxy.@target
load_target
这几乎没有抓到表面.但是,您了解如何使用构建器构建关联(根据条件创建不同的反射)并创建用于读取目标变量中的数据的代理.
第一个和第二个示例之间的区别在于它们的关联构建器被调用以创建关联的反射(基于宏),代理和目标实例变量.
第一个例子:
u = User.new
p = Project.new
u.projects << p
u.association(:projects)
#=> ActiveRecord::Associations::HasManyThroughAssociation object
#=> @proxy = #<ActiveRecord::Associations::CollectionProxy [#<Project id: nil, name: nil, created_at: nil, updated_at: nil>]>
#=> @target = [#<Project id: nil, name: nil, created_at: nil, updated_at: nil>]
u.association(:project_participations)
#=> ActiveRecord::Associations::HasManyAssociation object
#=> @proxy = #<ActiveRecord::Associations::CollectionProxy [#<ProjectParticipation id: nil, user_id: nil, project_id: nil, role: nil, created_at: nil, updated_at: nil>]>
#=> @target = [#<ProjectParticipation id: nil, user_id: nil, project_id: nil, role: nil, created_at: nil, updated_at: nil>]
u.project_participations.first.association(:project)
#=> ActiveRecord::Associations::BelongsToAssociation object
#=> @target = #<Project id: nil, name: nil, created_at: nil, updated_at: nil>
Run Code Online (Sandbox Code Playgroud)
第二个例子:
u = User.new
pp = ProjectParticipation.new
p = Project.new
pp.project = p # assign project to project_participation
u.project_participations << pp # assign project_participation to user
u.association(:projects)
#=> ActiveRecord::Associations::HasManyThroughAssociation object
#=> @proxy = nil
#=> @target = []
u.association(:project_participations)
#=> ActiveRecord::Associations::HasManyAssociation object
#=> @proxy = #<ActiveRecord::Associations::CollectionProxy [#<ProjectParticipation id: nil, user_id: nil, project_id: nil, role: nil, created_at: nil, updated_at: nil>
#=> @target = [#<ProjectParticipation id: nil, user_id: nil, project_id: nil, role: nil, created_at: nil, updated_at: nil>]
u.project_participations.first.association(:project)
#=> ActiveRecord::Associations::BelongsToAssociation object
#=> @target = #<Project id: nil, name: nil, created_at: nil, updated_at: nil>
Run Code Online (Sandbox Code Playgroud)
没有代理BelongsToAssociation
,它只是目标和所有者.
但是,如果您真的倾向于让您的第二个示例工作,您只需要这样做:
u.association(:projects).instance_variable_set('@target', [p])
Run Code Online (Sandbox Code Playgroud)
现在:
u.projects
#=> #<ActiveRecord::Associations::CollectionProxy [#<Project id: nil, name: nil, created_at: nil, updated_at: nil>]>
Run Code Online (Sandbox Code Playgroud)
在我看来,这是创建/保存关联的一种非常糟糕的方式.所以,坚持第一个例子本身.