I\'ve got these 5 models: Guardian, Student, Relationship, RelationshipType and School. Between them, I\'ve got these associations
class Guardian < ActiveReco
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
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)
.
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
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
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