Get two associations within a Factory to share another association

点点圈 提交于 2019-12-03 09:57:17

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

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).

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

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

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

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!