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

时间:2012-01-11 13:31:31

标签: ruby-on-rails factory-bot

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

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)

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

6 个答案:

答案 0 :(得分: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子句阻止studentFactoryGirl.create(:relationship, student: foo)传递到工厂时被替换。

答案 1 :(得分:8)

我认为这应该有效:

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

答案 2 :(得分: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

答案 3 :(得分: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

答案 4 :(得分:1)

延伸到nitsas&#39;解决方案,您可以滥用@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

答案 5 :(得分:0)

我使用transient&amp;在这种情况下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