获取工厂内的两个关联以共享另一个关联

我有这五个模型:卫报,学生,关系,关系类型和学校。 在他们之间,我有这些联想

class Guardian  :destroy has_many :students, :through => :relationships end class Student  :destroy has_many :guardians, :through => :relationships end class Relationship < ActiveRecord::Base belongs_to :student belongs_to :guardian belongs_to :relationship_type end class School  :destroy has_many :students, :dependent => :destroy end class RelationshipType < ActiveRecord::Base has_many :relationships end 

我想写一个定义关系的FactoryGirl。 每个关系都必须有一个监护人和一个学生。 这两个必须属于同一所学校。 监护人工厂与学校有联系,学生工厂也是如此。 我一直无法让他们在同一所学校建造。 我有以下代码:

 FactoryGirl.define do factory :relationship do association :guardian association :student, :school => self.guardian.school relationship_type RelationshipType.first end end 

当我尝试使用此工厂构建关系时,这会导致以下错误:

 undefined method `school' for # (NoMethodError) 

有没有办法做我想做的事,让监护人和学生属于同一所学校,而不必诉诸已经创建的监护人和学生到工厂(这不是它的目的)?

我认为这应该有效:

 FactoryGirl.define do factory :relationship do association :guardian relationship_type RelationshipType.first after_build do |relationship| relationship.student = Factory(:student, :school => relationship.guardian.school) end end end 

这个答案是谷歌“工厂女孩共享协会”的第一个结果,santuxus的回答真的帮助了我:)

以下是有关其他人偶然发现的最新版工厂女孩的语法更新:

 FactoryGirl.define do factory :relationship do guardian relationship_type RelationshipType.first after(:build) do |relationship| relationship.student = FactoryGirl.create(:student, school: relationship.guardian.school) unless relationship.student.present? end end end 

unless student已经通过FactoryGirl.create(:relationship, student: foo)传递到工厂,则unless子句会阻止student被替换。

有更清晰的方式来编写这种关联。 答案来自这个github问题 。

 FactoryGirl.define do factory :relationship do association :guardian student { build(:student, school: relationship.guardian.school) } relationship_type RelationshipType.first end end 

这不是您正在寻求的答案,但似乎创建此关联的困难表明可能需要调整表格设计。

提出问题What if the user changes school?StudentGuardian的学校需要更新,否则,模型会不同步。

我提出一个学生,一个监护人和一个学校,都有一个关系。 如果学生改变学校,则为新学校创建新的Relationship 。 作为一个很好的副作用,这使得学生在学习的地方存在历史。

belongs_to关联将从StudentGuardian删除,并转移到Relationship

然后可以将工厂更改为如下所示:

 factory :relationship do school student guardian relationship_type end 

然后可以通过以下方式使用它:

 # use the default relationship which creates the default associations relationship = Factory.create :relationship school = relationship.school student = relationship.student guardian = relationship.guardian # create a relationship with a guardian that has two charges at the same school school = Factory.create :school, name: 'Custom school' guardian = Factory.create :guardian relation1 = Factory.create :relationship, school: school, guardian: guardian relation2 = Factory.create :relationship, school: school, guardian: guardian student1 = relation1.student student2 = relation2.student 

在这种情况下,我会使用瞬态和依赖属性:

 FactoryGirl.define do factory :relationship do transient do school { create(:school) } # now you can even override the school if you want! end guardian { create(:guardian, school: school) } student { create(:student, school: school) } relationship_type RelationshipType.first end end 

用法:

 relationship = FactoryGirl.create(:relationship) relationship.guardian.school == relationship.student.school # => true 

如果你愿意,你甚至可以覆盖学校:

 awesome_school = FactoryGirl.create(:school) awesome_relationship = FactoryGirl.create(:relationship, school: awesome_school) awesome_relationship.guardian.school == awesome_school # => true awesome_relationship.student.school == awesome_school # => true 

根据nitsas的解决方案,您可以滥用@overrides来检查监护人或学生协会是否已被覆盖,并使用监护人/学生的学校协会。 这使您不仅可以覆盖学校,还可以覆盖监护人或学生。

不幸的是,这依赖于实例变量,而不是公共API。 未来的更新很可能打破你的工厂。

 factory :relationship do guardian { create(:guardian, school: school) } student { create(:student, school: school) } transient do school do if @overrides.key?(:guardian) guardian.school elsif @overrides.key?(:student) student.school else create(:school) end end end end