FactoryGirl association model trouble: "SystemStackError: stack level too deep"
Asked Answered
D

2

14

I am using Ruby on Rails 3.0.9, RSpec-rails 2 and FactoryGirl. I am trying to state a Factory association model but I am in trouble.

I have a factories/user.rb file like the following:

FactoryGirl.define do
  factory :user, :class => User do
    attribute_1
    attribute_2
    ...

    association :account, :factory => :users_account, :method => :build, :email => '[email protected]'
  end
end

and a factories/users/account.rb file like the following:

FactoryGirl.define do
  factory :users_account, :class => Users::Account do
    sequence(:email) {|n| "foo#{n}@bar.com" }
    ...
  end
end

The above example works as expected in my spec files, but if in the factory :users_account statement I add the association :user code so to have

FactoryGirl.define do
  factory :users_account, :class => Users::Account do
    sequence(:email) {|n| "foo#{n}@bar.com" }
    ...
    association      :user
  end
end

I get the following error:

Failure/Error: Unable to find matching line from backtrace
SystemStackError:
  stack level too deep

How can I solve that problem so to access associated models from both sides\factories (that is, in my spec files I would like to use RoR association model methods like user.account and account.user)?

P.S.: I read the Factory Girl and has_one question and my case is very close to the case explained in the linked question. That is, I have an has_one association too (between User and Users::Account classes).

Depravity answered 20/9, 2011 at 1:3 Comment(2)
Just curious, you have a User and then you have a Users::Account, is that supposed to be plural or should it be User::Account or typo?Dace
@kwon - It is not a typo. I have a Users::Account class.Depravity
Q
26

According to the docs, you can't just put both sides of the associations into the factories. You'll need to use their after callback to set an object(s) to return.

For instance, in the factories/users/account.rb file, you put something like

after(:build) do |user_account, evaluator|
    user_account.user = FactoryGirl.build(:user, :account=>user_account)
end

For has_many associations, you'll need to use their *_list functions.

after(:build) do |user_account, evaluator|
    user_account.users = FactoryGirl.build_list(:user, 5, :account=>user_account)
end

Note: I believe the example in the docs is a bit misleading it doesn't assign anything to the object. I believe it should be something like (note the assignment).

# the after(:create) yields two values; the user instance itself and the
# evaluator, which stores all values from the factory, including ignored
# attributes; `create_list`'s second argument is the number of records
# to create and we make sure the user is associated properly to the post
after(:create) do |user, evaluator|
  user.posts = FactoryGirl.create_list(:post, evaluator.posts_count, user: user)
end
Quagga answered 27/12, 2012 at 19:51 Comment(0)
R
0

Spyle's excellent answer (still working with Rails 5.2 and RSpec 3.8) will work for most associations. I had a use case where a factory needed to use 2 different factories (or different traits) for a single has_many association (ie. for a scope type method).

What I ended up coming up with was:

# To build user with posts of category == 'Special' and category == 'Regular'
after(:create) do |user, evaluator|
  array = []
  array.push(FactoryBot.create_list(:post, 1, category: 'Regular')
  array.push(FactoryBot.create_list(:post, 1, category: 'Special')
  user.posts = array.flatten
end

This allowed the user to have 1 post of category 'Regular' and 1 post of category 'Special.'

Rightwards answered 17/2, 2019 at 16:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.