FactoryBot sidecar associations
Nick Bell
Posted on April 9, 2021
The problem
Often, we create "sidecar" models alongside "primary" models in Rails apps. For instance, if you're developing a blogging platform, you may want to create the user's blog model when the user signs up. An example set of models:
class User
has_one :blog
end
class Blog
belongs_to :user
end
class UserCreationService
def self.call(user_attrs:)
user = User.create(user_attrs)
Blog.create(user: user)
end
end
This works great in production, since we always run that service when users are created. However, when testing we might not want to use the service in every spec that requires a user. This is where a fixture/factory setup comes in.
Now, when testing your app in RSpec, I've found the ease of building models with FactoryBot to be a much better development experience than using Rails' standard fixtures. To build a factory for these, you might do something like this to start:
FactoryBot.define do
factory :user do
blog
end
factory :blog do
user
end
end
Then, in your model specs:
describe User do
subject { build(:user) }
it { is_expected.to be_valid } # i.e. have a valid factory
end
describe Blog do
subject { build(:blog) }
it { is_expected.to be_valid }
end
However, running these specs will give you an error like this:
SystemStackError:
stack level too deep
This happens because FactoryBot doesn't know that, when calling build(:user)
, (which calls build(:blog)
), that the user is already present to be attached. Therefore build(:blog)
will just call build(:user)
, and so on, creating the infinite loop.
The Solution
To fix this, we need to specifically assign the sidecar model to the primary, and vice versa, in the factories:
FactoryBot.define do
factory :user do
blog { association :blog, user: instance }
end
factory :blog do
user { association :user, blog: instance }
end
end
This will properly associate the 1-1 models whether you call build(:user)
or build(:blog)
. You can even still use an override attribute to create a model with override attributes.
2 examples, 0 failures
Hopefully this helps. This should be one of many tiny tidbits.
Posted on April 9, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.