FactoryGirl: Populate a has many relation preserving build strategy

試著忘記壹切 提交于 2019-12-03 00:03:10

Here's a slightly cleaner version of Flipstone's answer:

factory :offering do
  trait :stay do
    ...
    photos do
      association :hotel_photo, :strategy => @build_strategy.class
    end
  end
end

You can use the various FactoryGirl callbacks:

factory :offering do
  association :partner, factory: :named_partner
  association :destination, factory: :geolocated_destination

  trait :stay do
    title "Hotel Gran Vía"
    description "Great hotel in a great zone with great views"
    offering_type 'stay'
    price 65
    rooms 70
    stars 4
    event_spaces 3
    after(:stub) do |offering|
      offering.photos = [build_stubbed(:hotel_photo)]
    end
    after(:build) do |offering|
      offering.photos = [build(:hotel_photo)]
    end
    after(:create) do |offering|
      offering.photos = [create(:hotel_photo)]
    end
  end
end

You can also invoke the FactoryRunner class directly and pass it the build strategy to use.

factory :offering do
  trait :stay do
    ...
    photos do
      FactoryGirl::FactoryRunner.new(:hotel_photo, @build_strategy.class, []).run
    end
  end
end

Other answers have a flaw, the inverse association is not being properly initialized, e.g. offering.photos.first.offering == offering is false. Even worse that being incorrect, the offering is a new Offering for each of the photos.

Also, explicitly specifying a strategy is redundant.

To overcome the flow and to simplify things:

factory :offering do
  trait :stay do
    ...
    photos do
      association :hotel_photo, offering: @instance
    end
  end
end

@instance is an instance of the Offering being created by the factory at the moment. For the curious, context is FactoryGirl::Evaluator.

If you don't like the @instance like I do, you may look in evaluator.rb and find the following:

def method_missing(method_name, *args, &block)
  if @instance.respond_to?(method_name)
    @instance.send(method_name, *args, &block)

I really like how itself looks:

factory :offering do
  trait :stay do
    ...
    photos do
      association :hotel_photo, offering: itself
    end
  end
end

Do be able to use itself, undefine it on the Evaluator:

FactoryGirl::Evaluator.class_eval { undef_method :itself }

It will be passed to the @instance and will return the @instance itself.

For the sake of providing a full example with several photos:

factory :offering do
  trait :stay do
    ...
    photos do
      3.times.map do
        association :hotel_photo, offering: itself
      end
    end
  end
end

Usage:

offering = FactoryGirl.build_stubbed :offering, :stay
offering.photos.length # => 3
offering.photos.all? { |photo| photo.offering == offering } # => true

Be careful, some things might not work as expected:

  • offering.photos.first.offering_id will surprisingly be nil;
  • offering.photos.count will hit the database with a SELECT COUNT(*) FROM hotel_photos ... (and will return 0 in most cases), please use length or size in assertions.

This kind of thing works for me:

factory :offering do
  trait :stay do
    ...
    photos { |o| [o.association(:hotel_photo)] }
  end
end
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!