How to user factory girl to create associated lists with a has_many with a validation that requires it on create
Asked Answered
P

3

18

In a Rails application, 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.

Pow answered 2/5, 2012 at 20:29 Comment(0)
P
22

I reposted this over on the Factory Girl github page as an issue and worked my way around to the answer:

before_create do |article|
  article.reviewers << FactoryGirl.build(:reviewer, article: article)
end

The key was doing it in a before_create, so the validations haven't fired yet, and making sure to push the newly created reviewer into the list of reviews on the instance being created. Thanks to Unixmonkey for responding and keeping me trying new things :)

https://github.com/thoughtbot/factory_girl/issues/369#issuecomment-5490908

Pow answered 3/5, 2012 at 16:32 Comment(0)
S
3
factory :article do
  reviewers {|a| [a.association(:reviewer)] }
end

or

factory :article do
  before_create do |a|
    FactoryGirl.create(:reviewer, article: a)
  end
end
Shilashilha answered 2/5, 2012 at 20:44 Comment(3)
When I try this, I get: SystemStackError: stack level too deep. It seems like when the reviewer factory gets fired off, it doesn't know about the article, so then it tries to create yet another article.Pow
@Pow You could remove the article creation from the reviewer factory or use before_create. I've edited my answer to reflect that.Shilashilha
I gave your second example a shot as well after you added it and got the validation error: ActiveRecord::RecordInvalid: Validation failed: article must have at least one reviewer againPow
P
1

The new syntax is:

before(:create) do |article|
  article.reviewers << FactoryGirl.build(:reviewer, article: article)
end
Pandolfi answered 25/8, 2016 at 15:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.