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,inverse_of: :projects end
用户可以作为成员或经理参与许多项目.连接模型称为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>]>
到目前为止一直很好 – AR自己创建了ProjectParticipation,我可以使用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,role: nil>]> u.projects => #<ActiveRecord::Associations::CollectionProxy []>
为什么这些项目是空的?我不能像以前一样通过u.projects访问项目.
但如果我直接参与,项目会显示:
u.project_participations.map(&:project) => [#<Project id: nil>]
它不应该直接像第一个例子那样工作:u.projects返回我所有的项目,不依赖于我是否自己创建了连接对象?或者我怎样才能让AR意识到这一点?
解决方法
答案很长:
在开始之前,我们应该知道如何在ActiveRecord :: Base中处理has_many:through.因此,让我们从has_many(name,scope = nil,options = {},&extension)
方法开始,该方法调用其关联builder here,在方法结束时返回reflection,然后将反射添加到hash作为缓存with key-value pair here.
现在的问题是,这些关联如何被激活?!?!
这是因为association(name)
方法.其中调用association_class
方法,实际调用并返回此常量:Associations::HasManyThroughAssociation
,使this line自动加载active_record / associations / has_many_through_association.rb和instantiate its instance here.这是在创建关联时保存owner and reflection的位置,并且在下一个重置方法中被调用的子类在ActiveRecord :: Associations :: CollectionAssociation here中被调用.
为什么这个重置呼叫很重要?因为,它将@target设置为数组.这个@target是一个数组,在您进行查询时存储所有关联对象,然后在代码中重复使用它而不是创建新查询时将其用作缓存.这就是为什么调用user.projects(其中用户和项目在db中持续存在,即调用:user = User.find(1)然后是user.projects)将进行数据库查询并再次调用它不会.
因此,当您在关联时进行reader调用时,例如:user.projects,它是invokes the collectionProxy,然后从load_target
填充@target.
这几乎没有触及表面.但是,您了解如何使用构建器(根据条件创建different reflection)构建关联,并创建用于读取目标变量中数据的代理.
TL;博士
第一个和第二个示例之间的区别在于它们的关联构建器被调用以创建关联的reflection(based on macro),代理和目标实例变量.
第一个例子:
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,updated_at: nil>] u.association(:project_participations) #=> ActiveRecord::Associations::HasManyAssociation object #=> @proxy = #<ActiveRecord::Associations::CollectionProxy [#<ProjectParticipation id: nil,role: nil,updated_at: nil>]> #=> @target = [#<ProjectParticipation id: nil,updated_at: nil>] u.project_participations.first.association(:project) #=> ActiveRecord::Associations::BelongsToAssociation object #=> @target = #<Project id: 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,updated_at: nil> #=> @target = [#<ProjectParticipation id: nil,updated_at: nil>
BelongsToAssociation没有代理,它只有target and owner.
但是,如果您真的倾向于让您的第二个示例工作,您只需要这样做:
u.association(:projects).instance_variable_set('@target',[p])
现在:
u.projects #=> #<ActiveRecord::Associations::CollectionProxy [#<Project id: nil,updated_at: nil>]>
在我看来,这是创建/保存关联的一种非常糟糕的方式.所以,坚持第一个例子本身.