让工厂内的两个关联共享另一个关联

发布于 2024-12-26 02:24:36 字数 1314 浏览 0 评论 0原文

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

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

I've got these 5 models: Guardian, Student, Relationship, RelationshipType and School. Between them, I've got these associations

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

I want to write a FactoryGirl which defines a relationship. Every relationship must have a guardian and a student. These two must belong to the same school. The guardian factory has an association with school, and so does the student factory. I've been unable to get them to be built in the same school. I've got the following code:

FactoryGirl.define do

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

end

This results in the following error when I try to build a relationship using this factory:

undefined method `school' for #<FactoryGirl::Declaration::Implicit:0x0000010098af98> (NoMethodError)

Is there any way to do what I want, to make the guardian and the student belong to the same school without having to resort to passing already created guardians and students to the factory (which is not its purpose)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

毁虫ゝ 2025-01-02 02:24:36

这个答案是 Google 上关于“工厂女孩共享关联”的第一个结果,来自 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) 传递到工厂时被替换。

This answer is the first result on Google for 'factory girl shared association' and the answer from santuxus really helped me out :)

Here's an update with the syntax from the latest version of Factory Girl in case anyone else stumbles across it:

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

The unless clause prevents student from being replaced if it's been passed into the factory with FactoryGirl.create(:relationship, student: foo).

苏佲洛 2025-01-02 02:24:36

我认为这应该有效:

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

I think this should work:

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
蓝戈者 2025-01-02 02:24:36

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

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

There is cleaner way to write this association. Answer got from this github issue.

FactoryGirl.define do
  factory :relationship do 
    association :guardian
    student { build(:student, school: relationship.guardian.school) }
    relationship_type RelationshipType.first
  end
end
失眠症患者 2025-01-02 02:24:36

扩展 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

Extending on nitsas' solution, you can abuse @overrides to check whether the guardian or student association have been overridden, and use the school association from the guardian/student. This allows you to override not only the school, but also just the guardian or just the student.

Unfortunately, this relies on instance variables, not public API. Future updates could very well break your factories.

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
旧瑾黎汐 2025-01-02 02:24:36

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

提出问题如果用户改变学校怎么办?StudentGuardian 上的学校都需要更新,否则模型会得到不同步。

我提出学生、监护人、学校三者是一体的。如果学生更换学校,则会为新学校创建新的关系。作为一个很好的副作用,这使得学生所受教育的历史记录得以存在。

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

This isn't really an answer you are seeking, but it seems that the difficulty in creating this association is suggesting that the table design may need to be adjusted.

Asking the question What if the user changes school?, the school on both the Student and Guardian needs to be updated, otherwise, the models get out of sync.

I put forward that a student, a guardian, and a school, all have a relationship together. If a student changes school, a new Relationship is created for the new school. As a nice side effect this enables a history to exist of where the student has been schooled.

The belongs_to associations would be removed from Student and Guardian, and moved to Relationship instead.

The factory can then be changed to look like this:

factory :relationship do
  school
  student
  guardian
  relationship_type
end

This can then be used in the following ways:

# 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
夏至、离别 2025-01-02 02:24:36

我会使用 transient &在这种情况下,依赖属性:

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

I'd use transient & dependent attributes in this case:

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

Usage:

relationship = FactoryGirl.create(:relationship)

relationship.guardian.school == relationship.student.school
# => true

And you can even override the school if you want:

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文