2012-01-11 37 views
18

我有以下5个模型:监护人,学生,关系,关系类型和学校。在他们之间,我有这些协会在工厂内部获得两个关联以共享另一个关联

class Guardian < ActiveRecord::Base 
    belongs_to :school 
    has_many :relationships, :dependent => :destroy 
    has_many :students, :through => :relationships 
end 

class Student < ActiveRecord::Base 
    belongs_to :school 
    has_many :relationships, :dependent => :destroy 
    has_many :guardians, :through => :relationships 
end 

class Relationship < ActiveRecord::Base 
    belongs_to :student 
    belongs_to :guardian 
    belongs_to :relationship_type 
end 

class School < ActiveRecord::Base 
    has_many :guardians, :dependent => :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 #<FactoryGirl::Declaration::Implicit:0x0000010098af98> (NoMethodError) 

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

+0

我不确定这是否与错误有关,但School类是作为第二个关系类声明(编辑之前)编写的。 – PinnyM 2012-01-11 23:02:34

回答

7

我认为这应该工作:

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 
+3

真的应该有这样做的更好的方法... – Ajedi32 2012-08-15 14:06:19

9

这个答案是对谷歌的“工​​厂女孩共享协会”,并从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)传入工厂,则不会被替换。

2

有更清晰的方式来写这个协会。从this github issue得到答案。

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

基于这个问题,它看起来应该是'student {build(:student,school:guardian.school)}''而不是'(关系) – 2017-06-10 15:12:46

1

这不是真的你正在寻找一个答案,但似乎在创造这个协会的难度表明该表的设计可能需要调整。

问题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 
0

我想在这种情况下使用transient & dependent属性:

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 
0

上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