简短回答:不,第二个示例不会像第一个示例那样工作。您必须使用第一个示例的方式直接创建与用户和项目对象的中间关联。
长答案:
在开始之前,我们应该知道如何has_many :through
正在处理中ActiveRecord::Base
。那么,让我们从调用其关联的方法建设者在这里 https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/associations/builder/association.rb#L28-L42,在方法结束时返回反射 https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/reflection.rb#L623然后添加反射hash https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/reflection.rb#L11作为缓存这里有键值对 https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/reflection.rb#L33-L35.
现在的问题是,这些关联如何被激活?!?!
这是因为association(name) https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/associations.rb#L150-L160方法。哪个调用association_class https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/reflection.rb#L440-L445方法,它实际上调用并返回这个常量:Associations::HasManyThroughAssociation https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/reflection.rb#L442,这使得这条线 https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/associations.rb#L118自动加载active_record/associations/has_many_through_association.rb and 实例化它的实例 https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/associations/has_many_through_association.rb#L7-L12 here https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/associations.rb#L155。这是哪里所有者和反思 https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/associations/association.rb#L27在创建关联以及调用下一个在子类中调用的重置方法时保存ActiveRecord::Associations::CollectionAssociation
here https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/associations/collection_association.rb#L64-L67.
为什么这个重置调用很重要?因为,它设定了@target
作为一个数组。这@target
是在进行查询时存储所有关联对象的数组,然后在代码中重用它而不是进行新查询时将其用作缓存。这就是为什么打电话user.projects
(其中用户和项目保留在数据库中,即调用:user = User.find(1)
进而user.projects
)将进行数据库查询并再次调用它不会。
所以,当你制作一个reader https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/associations/collection_association.rb#L29-L37呼吁协会,例如:user.projects
, it 调用集合代理 https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/associations/collection_association.rb#L36,在填充之前@target
from load_target https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/associations/collection_association.rb#L373-L380.
这还只是触及表面。但是,您知道如何使用构建器构建关联(这会创建不同的反映 https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/reflection.rb#L15-L31基于条件)并创建用于读取目标变量中的数据的代理。
tl;dr
第一个和第二个示例之间的区别在于调用它们的关联构建器来创建关联反射的方式(基于宏) https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/reflection.rb#L15-L31、代理和目标实例变量。
第一个例子:
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>
第二个例子:
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>
没有代理BelongsToAssociation
,它刚刚目标和所有者 https://github.com/rails/rails/blob/02d3a253610eaf9c80587913b366e3fa0f56b71f/activerecord/lib/active_record/associations/association.rb#L27.
但是,如果您确实想让第二个示例发挥作用,则只需执行以下操作:
u.association(:projects).instance_variable_set('@target', [p])
And now:
u.projects
#=> #<ActiveRecord::Associations::CollectionProxy [#<Project id: nil, name: nil, created_at: nil, updated_at: nil>]>
在我看来,这是创建/保存关联的一种非常糟糕的方式。所以,坚持第一个例子本身。