Подтвердить что ты не робот

Получите две ассоциации внутри Factory, чтобы поделиться другой ассоциацией

У меня есть эти 5 моделей: Guardian, Student, Relationship, RelationshipType и School. Между ними у меня есть эти ассоциации

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, который определяет отношения. У каждого отношения должен быть опекун и студент. Эти двое должны принадлежать одной школе. У опекуна factory есть связь со школой, и ученик factory. Я не смог заставить их быть построенными в одной школе. У меня есть следующий код:

FactoryGirl.define do

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

end

Это приводит к следующей ошибке, когда я пытаюсь построить отношения, используя этот factory:

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

Есть ли способ сделать то, что я хочу, чтобы страж и ученик принадлежали к одной и той же школе, не прибегая к передаче уже созданных опекунов и учеников к factory (что не является его целью)?

4b9b3361

Ответ 1

Я думаю, что это должно сработать:

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

Этот ответ является первым результатом в Google для 'factory girl shared association', и ответ от santuxus действительно помог мне:)

Здесь обновление с синтаксисом из последней версии Factory Girl в случае, если кто-то еще наткнулся на него:

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, если оно было передано в Factory с FactoryGirl.create(:relationship, student: foo).

Ответ 3

Существует более чистый способ написать эту ассоциацию. Ответ получен из этой проблемы github.

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

Ответ 4

На самом деле это не тот ответ, который вы ищете, но кажется, что трудность в создании этой ассоциации предполагает, что дизайн таблицы может потребоваться отрегулировать.

Задавая вопрос What if the user changes school?, необходимо обновить школу как для Student, так и для Guardian, иначе модели не синхронизируются.

Я предложил, чтобы студент, опекун и школа, все вместе имеют отношения. Если ученик меняет школу, для новой школы создается новый Relationship. Как хороший побочный эффект, это позволяет истории существования того, где ученик был обучен.

Ассоциации belongs_to будут удалены из Student и Guardian и вместо этого перейдут на Relationship.

factory можно изменить таким образом:

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

Ответ 5

Я бы использовал transient и dependent в этом случае:

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

Ответ 6

Продолжая решение 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