In a Rails 3.0 application using factory_girl_rails 3.2.0 (and factory_girl 3.2.0 as well), given three models User, Article and Reviewer with the following relationships and validations:
class User < ActiveRecord::Base
has_many :articles
has_many :reviewers
end
class Reviewer < ActiveRecord::Base
belongs_to :user
belongs_to :article
end
class Article < ActiveRecord::Base
belongs_to :user
has_many :reviewers
validate :has_reviewers?
def has_reviewers?
errors.add(:base, "article must have at least one reviewer.") if self.reviewers.blank?
end
end
And the following factories using the newer DSL:
FactoryGirl.define do
factory :user do
name { (8...20).map{ ('a'..'z').to_a[rand(26)] }.join }
age { Kernel.rand(100) }
end
factory :article do
body "This is the article content"
title "This is the title"
user
after_create do |article|
article.reviewers = create_list(:user, 2)
end
end
factory :reviewer do
user
article
state { ["published","draft","rejected","archived"][Kernel.rand(4)] }
end
end
The factory to create the article doesn't work because the validation fails before the reviewers are created:
> FactoryGirl.create(:article)
ActiveRecord::RecordInvalid: Validation failed: article must have at least one reviewer.
I have made more attempts than I would like to admit trying to overcome this hurdle, but I am stuck! One idea I had was to create the reviewers like this:
factory :article do
body "This is the article content"
title "This is the title"
user
reviewers {|a| [FactoryGirl.create(:reviewer, article: a)] }
end
but in this context, the "a" is not the instance. So that doesn't work either, like it used to.
After posting about this issue on stack overflow, two suggestions were made:
factory :article do
reviewers {|a| [a.association(:reviewer)] }
end
This results in a stack level too deep error, as the reviewer creates a new article which creates a new reviewer, etc. The other suggestion was this:
factory :article do
before_create do |a|
FactoryGirl.create(:reviewer, article: a)
end
end
This still fails because of the validation error as in the original attempt.
At this point it feels like maybe this is more of a bug that I can't find a way to do this. Would love some suggestions!
Yeah, this is a pretty interesting model structure. The after_build (or before_create) callbacks are definitely your best bet. You may want to try:
factory :article do
before_create do |article|
FactoryGirl.build(:reviewer, article: article)
end
end
I think that ActiveRecord's behavior here would save the article and reviewer appropriately so you don't see a validation error.
When thinking through problems like this, I find it easiest to write the order of steps out _without_ FactoryGirl at all - that is, use ActiveRecord and write out, line by line, exactly what you want to do. Once you have it working and the order is correct with ActiveRecord, it's typically trivial to port it to what you'd need to do with FactoryGirl.
I'd start with building the reviewer in the callback first. If that doesn't work, get the correct order of operations without FG and let me know what you come up with (assuming you don't know how to convert that order of operations to FG).
Thank you for the suggestion, but it still didn't work.
> FactoryGirl.create(:article)
ActiveRecord::RecordInvalid: Validation failed: article must have at least one reviewer.
Could it be that in this situation, article.reviewers is empty?
After my parting comment in the last post, I thought to try this:
before_create do |article|
article.reviewers << FactoryGirl.build(:reviewer, article: article)
end
which worked!
Ah, awesome! Our example for a has_many association in the GETTING_STARTED doc outlines a similar method as you and I mentioned, but the validations mess things up; glad to hear you figured it out!
This is two years old now, but I just wanted to say thanks because this solved my issue :purple_heart:
:+1: helped me too, thanks.
:+1: this is an oldie but a goodie!
:+1:
Helped a lot. Thx!
IMO there can be done more clearly in up-to-date FactoryGirl using initialize_with:
factory :article do
reviewer
initialize_with { new(reviewer: reviewer) }
end
I couldn't get the issue resolved using blizzo's method but ojab's worked a treat. Thanks
Guys you just saved my life. Spent 2 days on solving this issue. With the help of this thread it took 5 minutes. Maybe it's just me but it was hard to find this page. I think it would be nice to make this solution easily available for newcomers.
Can also be resolved for has_many associations by writing as below
factory :article do
reviewers { create_list(:review, 2) }
end
where reviewers is an has_many association over article.
@ojab worked perfect for me in a similar situation and so clean!
@ojab solution worked for me with one small gotcha. I had to add [] around 'reviewer' for a has_many relationship. So:
initialize_with { new(reviewer: [reviewer]) }
so helpful!
Not a single one of these methods is working for me in 2018 for the same type of association and validation. Any ideas?
@ImOnMars true. Always getting must exist error in my case
@ImOnMars @anuraagbitcot super late (and probably wrong), but I got it to work on a has_many association by doing:
factory :review do
article
end
factory :article do
reviewers { [create(:reviewer, article: nil)] }
end
@ggsp I guess this might fail if you have validation check on reviewers model side.
after(:create) do |article|
create(:review, article: article)
article.reload
end
I added this in my factory for article, and it worked.
after(:build) do |article|
article.reviewers << create(:reviewer, article: article)
end
after(:build) is before validations, after(:create) first pass the validations and than create the object,
so needed after(:build) for pass the validations
Rails 5. This is still a thing. Had to make the child association belongs_to macro optional
belongs_to :customer, optional: true
In order to factory to pass validation in the way @ggsp suggested
FactoryBot.define do
factory :customer do
addresses { [FactoryBot.create(:address, customer: nil)] }
end
end
One of the N-N sides has a presence validation:
class Video < ApplicationRecord
has_many :video_instructors, -> { order(:id) }
has_many :instructors, through: :video_instructors
validates :instructors, presence: true
end
class Instructor < ApplicationRecord
has_many :video_instructors, -> { order(:id) }
has_many :videos, through: :video_instructors
end
Existing join table needed to be converted to a 1st class model:
class VideoInstructor < ApplicationRecord
belongs_to :video, touch: true, inverse_of: :video_instructors
belongs_to :instructor, touch: true, inverse_of: :video_instructors
end
The only way I could find to define the factory (and not change specs everywhere):
factory :video do
before(:create) do |video|
video.save(validate: false)
create(:video_instructor, video: video, instructor: create(:instructor))
end
after(:build) do |article| article.reviewers << create(:reviewer, article: article) endafter(:build) is before validations, after(:create) first pass the validations and than create the object,
so needed after(:build) for pass the validations
In my case, besides using after(:build), I need to use the build method when for the reviewer like below:
after(:build) do |article|
article.reviewers << build(reviewer, article: article)
end
Reason is that reviewer has a belongs_to :article that requires the presence of the article to be created (and I don't want to make it optional with optional: true)
Most helpful comment
After my parting comment in the last post, I thought to try this:
which worked!