Get two associations within a Factory to share another association

拈花ヽ惹草 提交于 2019-12-04 16:55:29

问题


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


回答1:


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



回答2:


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




回答3:


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



回答4:


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



回答5:


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



回答6:


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


来源:https://stackoverflow.com/questions/8820114/get-two-associations-within-a-factory-to-share-another-association

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