Get two associations within a Factory to share another association

后端 未结 6 1645
情书的邮戳
情书的邮戳 2021-02-12 14:57

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

class Guardian < ActiveReco         


        
相关标签:
6条回答
  • 2021-02-12 15:24

    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
    
    0 讨论(0)
  • 2021-02-12 15:31

    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
    
    0 讨论(0)
  • 2021-02-12 15:34

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

    0 讨论(0)
  • 2021-02-12 15:34

    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
    
    0 讨论(0)
  • 2021-02-12 15:44

    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
    
    0 讨论(0)
  • 2021-02-12 15:44

    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
    
    0 讨论(0)
提交回复
热议问题