FactoryGirl has_many association with validation
Asked Answered
E

3

19

I have a standard has_many relationship (Booking has many Orders) with validation that a Booking does not get saved without at least one Order. I'm trying to replicate this with my FactoryGirl factories but the validation is preventing me from doing so.

class Booking < ActiveRecord::Base
  has_many :orders
  validates :orders, presence: true
end

class Order < ActiveRecord::Base
  belongs_to :booking
end

Here are my FactoyGirl factory specifications for each model as followed from FactoryGirl's GitHub wiki page.

FactoryGirl.define do                                                    

  factory :booking do                                                                                                                   
    factory :booking_with_orders do

      ignore do                                                                                                                         
        orders_count 1                                                                                                                  
      end                                                                                                                               

      before(:create) do |booking, evaluator|                                                                                           
        FactoryGirl.create_list(:order, evaluator.orders_count, booking: booking)                                                       
      end                                                                                                                               
    end                                                                                                                                 
  end 

  factory :order do
    booking
  end

end 

When I try to run FactoryGirl.create(:booking_with_orders) from my spec, I get:

Failure/Error: @booking = FactoryGirl.create(:booking_with_orders)
ActiveRecord::RecordInvalid:
  Validation failed: Orders can't be blank

It seems like the check for the validation is running even before before(:create) [...] which would theoretically create the Orders for the Booking.

This post recommends not adding has_many relationships to your factories but I would like to solve this anyways if there is a good way to do it.

Thanks in advance.

Erased answered 13/12, 2012 at 1:6 Comment(0)
C
38

Wat? Impossible? Not at all.

Just change your code to something like this:

after :build do |booking, evaluator|
  booking.orders << FactoryGirl.build_list(:order, evaluator.orders_count, booking: nil)
end
Children answered 7/5, 2013 at 1:19 Comment(3)
You mean booking.orders << but otherwise, this was the answer for me. Thanks!Microcurie
Thanks, this answer pointed me in the right direction to solve my own issue.Freshen
I owe you the internet equivalent of a beerGigantopithecus
M
2

Taking off from @jassa's answer, if you just need to add a single (required) associated record with a specific attribute, this pattern worked for me:

factory :booking do
  ignore do
    order_name "name"
  end

  after :build do |factory, evaluator|
    factory.orders << FactoryGirl.build(:order, name: evaluator.order_name, booking: nil)
  end
end
Moonseed answered 11/12, 2013 at 14:5 Comment(0)
M
0

This seems like an overly simplistic observation but what you're trying to do is in effect make sure that the Order exists before the Booking, which is impossible, as the Order cannot exist without its booking_id (meaning that the Booking needs to be created first).

There's nothing wrong with a has_many relationship in your factories, it's your validation that is a problem. Does this currently work in your application? How do you save your records in that case? What is the flow for creating Orders and Bookings?

Even the infamous accepts_nested_attributes_for won't help you here.

My advice is to rethink your record saving and validation strategy so that it is a little more sane.

Mender answered 13/12, 2012 at 18:35 Comment(2)
Thanks, this is a good consideration but everything is working in my application with the validation. Here is my flow: @booking = Booking.new(params[:booking]) @booking.orders << Order.new(params[:order]) @booking.save.Erased
@ErikNomitch Have you tried the after(:build) callback instead of before(:create)? How exactly is your validation on Booking set up? I guess the real crux of the issue is that FactoryGirl probably wants to create these objects in isolation, which makes sense from a testing point of view, but when you create them as you just noted in your comment they are created together. This kind of coupling usually leads to headaches.Mender

© 2022 - 2024 — McMap. All rights reserved.