Search code examples
activerecordrspecfactory-bot

FactoryGirl association Parent can't be blank


I am trying to use FactoryGirl to create a test database with an association that is Parent has_many Entries. At this point, it is throwing the ActiveRecord validation error that Parent can't be blank. I'm having a tough time with this one and have tried many, many methods of creating this test database with this association. I think I am close, but I may not be even close and may have basic errors, so any and all advice is much appreciated.

My guess is that the hash { parent_id: :id } is not being passed to the Entry factory. That would fail the validation. But, I don't know that that is actually the case and, even if it is, I don't know how to fix it. Thanks in advance for your help...

The error is:

ActiveRecord::RecordInvalid: Validation failed: Parent can't be blank

The RSpec call is:

before(:all) do
  rand(11..25).times { FactoryGirl.create(:parent) }
  visit "/parents?direction=asc&sort=parent_blog"  
end
after(:all)  do
  Parent.delete_all
end

The Parent model is:

class Parent < ActiveRecord::Base
  has_many :entries, dependent: :destroy
  accepts_nested_attributes_for :entries, :allow_destroy => :true
  validates :parent_blog, presence: true,
              uniqueness: true
end

The Entry model is:

class Entry < ActiveRecord::Base
  belongs_to :parent
  validates :entry_blog,     presence:true,
            uniqueness: true
  validates :parent_id,       presence: true
end

The Parent factory is:

FactoryGirl.define do
  factory :parent do
    sequence(:parent_blog) { |n| "http://www.parent%.3d.com/ss" % n }
    entries { rand(5..15).times { FactoryGirl.create(:entry,  parent_id: :id) } }
  end
end

The Entry factory is:

FactoryGirl.define do
  factory :entry do
    sequence(:entry_blog)      { |n| "http://www.parent%.3d.com/ss/entry%.3d" % [n, n] }
    parent_id                   { :parent_id }
  end
end

Solution

  • The following modification to your factory definitions should work. I'll be back later to offer some explanation.

    FactoryGirl.define do
    
      factory :parent do
        sequence(:parent_blog) { |n| "http://www.parent%.3d.com/ss" % n }
        after(:create) {|parent| rand(5..15).times {FactoryGirl.create(:entry, parent: parent)}}
      end
    
      factory :entry do
        sequence(:entry_blog)   { |n| "http://www.parent%.3d.com/ss/entry%.3d" % [n, n] }
        parent
      end
    
    end
    

    The two changes, introducing the use of after in the :parent factory and using parent instead of parent_id, are both examples of RSpec's support of associations, as discussed in https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#associations.

    As for why your code didn't work, I can only provide a partial answer at this point. The reason you can't include entries as part of the initial parent creation is that you don't have the parent id until the parent record is created, yet you need the parent id to create the entries because of Entry validating the presence of parent_id. Put another way, parent_id hadn't been set when the entries block was evaluated in your parent factory.

    What I'm not sure of is why you can't replace parent with parent_id in the entry factory and correspondingly replace parent: parent with parent_id: parent.id in the FactoryGirl.create call in the parent factory. I tried that variant before submitting the above and it failed with:

      1) test
         Failure/Error: rand(11..25).times { FactoryGirl.create(:parent) }
         ArgumentError:
           Trait not registered: parent_id
         # ./spec/factories.rb:4:in `block (4 levels) in <top (required)>'
         # ./spec/factories.rb:4:in `times'
         # ./spec/factories.rb:4:in `block (3 levels) in <top (required)>'
         # ./tmp/factory_spec.rb:5:in `block (3 levels) in <top (required)>'
         # ./tmp/factory_spec.rb:5:in `times'
         # ./tmp/factory_spec.rb:5:in `block (2 levels) in <top (required)>'
    

    I'll update this answer again if/when I figure that out.