What is the purpose of a `transient do` block in FactoryBot factories?
transient
attributes allow you to pass in data that isn’t an attribute on the model.
Say you have a model called car
with the following attributes:
- name
- purchase_price
- model
You want to capitalize the name of the car when you create the car model in the factory. What we can do is:
factory :car do
transient do
# capitalize is not an attribute of the car
capitalize false
end
name { "Jacky" }
purchase_price { 1000 }
model { "Honda" }
after(:create) do |car, evaluator|
car.name.upcase! if evaluator.capitalize
end
end
Hence, whenever you create the car factory and you want to capitalize the name. You can do
car = FactoryGirl.create(:car, capitalize: true)
car.name
# => "JACKY"
What are factory_girl transient attributes? Why would I use one?
factory_bot's transient 'attributes' aren't attributes at all; they're just parameters to the factory method call that can be used by your code inside the factory. So, in your example, no, upcased
isn't a model attribute.
The transient
block lists 'attribute' names (that is, keys in the hash passed to the factory method) that are not attributes. factory_bot ignores them when setting attributes on the newly created model instance unless you write code in the factory definition to tell factory_bot to do something with them.
evaluator
is an object passed to factory_bot callbacks. It's always the second block parameter; the model object is always the first parameter. It's conceptually like Ruby's binding
. You can ask it for the value of any key in the argument hash, regardless of whether it's an actual attribute or a transient 'attribute'.
Traits and transient attributes don't affect each other as far as arguments to factory methods are concerned, since traits are scalar and transient attributes are part of the argument hash. Any number of real attributes and transient 'attributes' can be in the argument hash.
Here's the factory_bot documentation for the record: https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md
FactoryBot: how to use transient in skip_create factory to specify properties in an internal factory?
It is because you missed the curly braces {}
FactoryBot.define do
factory :experiment do
transient do
order { nil }
end
....
end
end
Factory Girl - Manipulate Transient Attribute
I was able to find a suitable solution based on the idea presented by Ben. I used a local array variable to store the types of windows that needed to be created. The traits for each window add a type of window to the array in the after(:build)
block. Then, in an after(:create)
block I actually create the window records.
The very first after(:build)
block (the one not in a trait
) is important because that resets the window_types
array between object creation.
FactoryGirl.define do
window_types = []
factory :house do
floors 3
exterior 'Brick'
after(:build) do
window_types = []
end
after(:create) do |house|
window_types.each do |window_type|
FactoryGirl.create(:window, window_type, house: house)
end
end
trait :with_picture_window do
after(:build) do
window_types << :picture
end
end
trait :with_double_hung_window do
after(:build) do
window_types << :double_hung
end
end
trait :with_bay_window do
after(:build) do
window_types << :bay
end
end
factory :house_with_bay_and_picture_window, traits: [:bay, :picture]
end
end
I'm very curious to hear any thoughts on this approach. For now, this suits my needs and is very flexible.
Using a transient attribute in before(:create) callback
Defining same_group
without a default value causes Factory Girl to treat it as an association rather than an attribute (and attempt to look up a factory of the same name). Changing this line to e.g. same_group nil
would solve that problem. It's an admittedly confusing aspect of Factory Girl that the same method can be treated as either an attribute or an association depending on the number of arguments or presence of a block.
Defining factories with chained associations
Pass the Membership to the Comment factory in a transient attribute. In a before(:create)
callback, create a Decision from the Membership and add the Decision to the Comment:
factory :comment do
transient do
membership
end
before(:create) do |comment, evaluator|
decision = create(:decision, group: evaluator.membership.group)
comment.decision = decision
end
end
Expected behavior FactoryBot
Rails caches the model instances to avoid repetitive database queries. You'll need to call
user.reload
before your assertions to update the object with the latest values from the database.
Related Topics
Does Rails Come with a "Not Authorized" Exception
How to Pluck Email from Array of Users
How to Catch All Http Traffic (Local Proxy)
How to Disable a Form Submit Button "A Là Ruby on Rails Way"
Using Will_Paginate Without :Total_Entries to Improve a Lengthy Query
Cheat Sheet for All Design Patterns Implemented in Ruby
How to Integrate Hoptoad with Delayedjob and Daemonspawn
Activerecord Association Select Counts for Included Records
Rvm: How to Use Gems from a Different Ruby
How to Sort a Ruby Hash Alphabetically by Keys
Ruby Object Prints Out as Pointer
Sequel Accessing Many_To_Many Join Table When Adding Association
Mechanize How to Get Current Url
Automatic Logging of Datamapper Queries