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



我有这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.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)

有没有办法做我想做的事,让监护人和学生属于同一所学校,而不必求助于将已经创建的监护人和学生传递给工厂(这不是它的目的(?

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

以下是最新版本的Factory Girl语法的更新,以防其他人偶然发现它:

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) 传递到工厂时被替换。

我认为这应该有效:

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
有更

干净的方法来编写此关联。答案来自这个 github 问题。

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

在 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

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

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

最新更新