The Practical Usage of factory_bot Transient Attributes
One interesting feature of factory_bot to me is this Transient Attributes. It's quite powerful - not only DRY your factory setup, but also make it intention-revealing.
How Transient Attributes works
Let's check out the example provided in the GETTING_STARTED official doc. (Breaking it into 2 parts to better demonstrate how it works.)
factory :user do
transient do
rockstar { true }
end
name { "John Doe#{" - Rockstar" if rockstar}" }
end
create(:user).name
#=> "John Doe - ROCKSTAR"
create(:user, rockstar: false).name
#=> "John Doe"
rockstar
is not an attribute on the User model. You can think of it as a virtual attribute that only exists during the test. You can use the passed in value in the factory definition to your need.
Note that you do need to provide a default value for the Transient Attributes, and nil
is also a valid value.
The next example is combining it with the callback:
factory :user do
transient do
upcased { false }
end
name { "John Doe" }
after(:create) do |user, evaluator|
user.name.upcase! if evaluator.upcased
end
end
create(:user, upcased: true).name
#=> "JOHN DOE"
The attributes defined in the transient
block can be accessed in the value block or via evaluator
parameter.
That's the functionality. Let's look at some practical usage.
Set the attributes of the associated records
Here we assume a User always has one Authenticaiton record, which has a uuid
attribute. With Transient Attributes, we can set the authentication.uuid
through the user factory directly.
factory :user do
transient do
uuid { SecureRandom.uuid }
end
before(:create) do |user, evaluator|
user.authentications.build(uuid: evaluator.uuid)
end
end
user = create(:user, uuid: "uuid-1234-xxxx")
user.authentications.first.uuid
# => "uuid-1234-xxxx"
user = create(:user)
user.authentications.first.uuid
#=> The one generated by SecureRandom.uuid
Works great with Trait Attributes
It's convenient to use Transient inside the Trait Attributes.
FactoryBot.define do
factory :user do
trait :with_post do
transient do
post_title "user post title"
end
after(:create) do |user, evaluator|
create(:post, user: user, title: evaluator.post_title)
end
end
end
end
user = create(:user, :with_post, post_title: "My Post")
user.posts.first.title
# => "My Post"
That's it. 🙂
References
Clap to support the author, help others find it, and make your opinion count.