Deprecating static attributes in factory_bot 4.11

Daniel Colson

factory_bot has traditionally allowed you to define attributes dynamically, with a block:

factory :robot do
  name { "Ralph" }
end

or statically:

factory :robot do
  name "Ralph"
end

With the dynamic version, every time you build a new robot you will get a new “Ralph” string for the name, whereas in the static version you will always get the same string (i.e. the same object in memory).

Static attributes have been a source of much confusion over the years. A large number of our GitHub issues and Stack Overflow questions have been related to this confusion, and we have been thinking about deprecating static attributes since 2013.

Getting the same string for every robot name is not great, since mutating it will affect other robots:

factory :robot do
  name "Ralph"
end

robot1 = build(:robot)
robot2 = build(:robot)

robot2.name
#=> "Ralph"

robot1.name << "y"

robot2.name
#=> "Ralphy"

This can lead to confusing, order-dependent bugs in your test suite.

Using methods like Time.now with static attributes can also be confusing:

factory :post do
  published_at Time.now
end

first_post = build(:post)

Timecop.travel(2.days.from_now)

second_post = build(:post)

You might expect those two posts to have different published_at times, but instead every post you build will have the same time, based on when the factory definition was loaded.

Using persisted records with static attributes can be confusing as well:

factory :robot do
  friend Person.create!(name: "Daniel")
end

With this factory, every robot you build will be friends with the same Daniel. That may be good for Daniel, but it probably doesn’t make sense for your test suite.

This example gets worse if you use factory_bot_rails. factory_bot_rails is normally included in the test and development groups of your Gemfile (it needs to be in the development group if you use the generators, and you may also use it to create records when messing around in the console). Since factory_bot_rails automatically loads factory definitions as your application loads, writing a definition like this would cause another Daniel to get added to your database every time you start the server or open a console. I like Daniels and all, but there is a limit.

With all this potential for confusion, it doesn’t make sense for us to keep static attributes around. We will deprecate them in factory_bot 4.11, and we will remove them entirely in factory_bot 5.

While I think there was something visually satisfying about defining attributes without a block, I like even better that I no longer need to think about whether or not the block is necessary.

Deprecation warnings aren’t exactly fun. To make life easier for factory_bot users, we added a rubocop-rspec Cop to help. Hopefully upgrading will be as simple as adding rubocop-rspec to your Gemfile and running:

rubocop \
  --require rubocop-rspec \
  --only FactoryBot/AttributeDefinedStatically \
  --auto-correct

I may write more about my adventures with RuboCop another day. In the meantime, I wish you a painless upgrade!

Visit our Open Source page to learn more about our team’s contributions.