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