after(:create) in FactoryGirl trait - ruby-on-rails

I am using traits to modify the behavior of my factory.
When the :with_answers trait is used, I want to create a quiz_answer with the created submission as parameter.
FactoryGirl.define do
factory :quiz_submission do
quiz_id 1
[...]
trait :with_answers do
after(:create) do |submission|
FactoryGirl.create(:quiz_answer, quiz_submission: submission.id)
[...]
end
end
end
end
The block that is passed to after(:create) is never entered, though.
Can anyone tell me why?
EDIT:
I call the factory with FactoryGirl.create :quiz_submission, :with_answers

The proper solution is to have a transient attribute and a global after(:create) handler that does the work based on that attribute.
Something like this:
FactoryGirl.define do
factory :quiz_submission do
transient do
submissions_count { 0 }
end
quiz_id 1
[...]
trait :with_answers do
submissions_count { 1 }
end
after(:create) do |submission, evaluator|
create_list(:quiz_answer, evaluator.submissions_count, quiz_submission: submission.id)
[...]
end
end
end

Related

In Rails 4 and FactoryGirl, how do I override the base factory's "after(:build)" method?

I’m using Rails 4.2.3 with FactoryGirl. I have this factory for my users
FactoryGirl.define do
…
factory :user do
after(:build) do |user, vars|
print "in main user after build.\n"
def user.publish
# and here you can stub method response if you need
end
end
…
trait :with_callbacks do
after(:build) do |user, vars|
print "after build my user\n"
end
…
end
I wanted to override my base “after(:build)” method, so I created the trait “with_callbacks.” But when I call my factory with my traits
create(:user, :my_user)
It seems like both “after(:build)” methods are getting called based on the output …
after build my user
in main user after build.
Is there a way to rig things so that I can override the base factory’s “after(:build)” method?
Not sure what you're trying to do, but another option would be to use a transient variable (called ignore in FactoryBot versions < 5.0) to control the behavior, which can be overwritten in a trait.
Something like:
transient do # use `ignore` in factory bot < 5.0
with_callback_behavior { false }
end
after(:build) do |user, vars|
if vars.with_callback_behavior
puts "behavior for trait after build."
else
puts "behavior for main main after build."
end
end
trait :with_callbacks do
with_callback_behavior { true }
end
Of course, this means that
build :user, :with_callbacks
#=> "behavior for trait after build."
is the same as
build :user, with_callback_behavior: true
#=> "behavior for trait after build."

Factory Girl create_list with trait and dynamic variable

I'm trying to create some extra records from a Factorygirl callback. It has to use one of the traits in that factory and it has to pass it a dynamic variable. I thought I could do something like this:
create_list(:some_factory, 5, :some_trait, dynamic_variable: 1)
But I get an undefined method `dynamic_variable=' on the ActiveRecord object
You need to define the transient in factory. Here is an example, I copied from wiki
factory :user do
transient do
rockstar true
upcased false
end
name { "John Doe#{" - Rockstar" if rockstar}" }
email { "#{name.downcase}#example.com" }
after(:create) do |user, evaluator|
user.name.upcase! if evaluator.upcased
end
end
create_list(:user, 10, upcased: true).name

Factory Girl - Manipulate Transient Attribute

I have a use case where I would like to manipulate a transient attribute in a factory based on the traits that are included. Is there a way to do this, or is there a better way to do what I'm trying to accomplish?
Let's say I'm building a House object. The house can have many windows. I want to create some sub-factories that will automatically create some windows, but I want to be able to add a trait for a specific type of window. The window_types transient attribute is actually a list of traits for the window factories.
factory :house do
floors 3
exterior 'Brick'
transient do
window_types { [:bay, :double_hung] }
end
trait :with_picture_window do
window_types.push(:picture)
end
factory :ranch_house do
floors 1
after(:create) do |house, evaluator|
evaluator.window_types.each do |window_type|
FactoryGirl.create :window, window_type
end
end
end
factory :mountain_house do
floors 2
exterior 'Log'
after(:create) do |house, evaluator|
evaluator.window_types.each do |window_type|
FactoryGirl.create :window, window_type
end
end
end
end
factory :window do
material 'Glass'
trait :bay do
# bay window attributes
end
trait :double_hung do
# double hung window attributes
end
trait :picture do
# picture window attributes
end
end
This throws a NoMethodError: undefined method 'push' for #<FactoryGirl::Declaration::Implicit> error from within the :with_picture_window trait.
For the sake of argument, assume that I do need different factories for these houses. Is there a way that I can modify the window_types transient attribute from within a trait, and then have that transient attribute be reflected in my sub-factories?
I would like to be able to do:
FactoryGirl.create :ranch_house
# creates a ranch house with only bay and double_hung windows
FactoryGirl.create :ranch_house, :with_picture_window
# creates a ranch house with bay, double_hung, AND picture windows
If not, is there a better way that I can accomplish this?
Many thanks in advance.
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.
I am not sure this will answer your question but commenting would be useless with the formatting limitations. Perhaps the followign pattern could be useful in solving this?
trait :with_window do
after(:build) do |house|
# Create a house window
house.windows << FactoryGirl.build(:window) #Presume you could also pass a window trait here.
end
after(:create) do |room|
# Clear the windows attached in after(:build) and create one without saving the house again
house.windows.each do |window|
window.house_id = house.id
window.save
end
house.reload
end
end
Hope this provides help in some way!

Is it possible to define a method in a Factory Girl factory that is not in the associated Active::Record model?1

So I have a factory:
factory :person do
password_string { Faker::Lorem.words(3).join }
after(:create) do |object|
object.password = object.password_hash(object.password_string)
object.save!
end
end
And I get an error:
NoMethodError:
undefined method `password_string=' for #<Person:0xe1cdf00>
Which is expected, but I want to define the password_string for my test environment (mainly so I can mock a signed in user). Is there a way to get around the NoMethodError by defining attributes that are unique to the factory?
Thanks,
Yes, you can! Usually you'd put these on a trait, not certain if you can put them on a plain factory or not.
trait :with_password_string do
ignore do
password_string nil
end
after(:build) do |content, evaluator|
if evaluator.password_string
content.password = content.password_hash(evaluator.password_string)
end
end
end

error in FactoryGirl when trying to create objects before another object

I am trying to create a model named global_list. It will take a list which is associated with a user and a global_id which is associated with an item. I need to create a user (a list is created in a callback for this object) and an item (where a global_identification is created as a callback to item creation). Neither of the following solutions work in trying to create these associated objects before. Should they? Is there a better way to handle this? I have tried creating accessors for liked_item_list_id but that also didn't work.
FactoryGirl.define do
factory :global_list do
before(:create) {
user=FactoryGirl.create(:user)
mi=FactoryGirl.create(:item)
}
list_id user.liked_list_id
global_id mi.global_id
end
end
Will a block help?
FactoryGirl.define do
factory :global_list do
before(:create) {
user=FactoryGirl.create(:user)
mi=FactoryGirl.create(:item)
}
list_id { List.first.id }
global_id { 3 } # 3 will be created at this point { mil.global_id } doesn't work
end
end
I'm starting to think this is not possible with FactoryGirl. Any help would be appreciated?
Try this.
FactoryGirl.define do
factory :global_list do
before(:create) { |list|
user=FactoryGirl.create(:user)
mi=FactoryGirl.create(:item)
list.list_id = user.liked_list_id
list.global_id = mi.global_id
}
end
end
Its completely untested
This is totally possible in FactoryGirl, and actually often used.
It's simple to define associated object in a factory like below. No fancy "before" needed.
FactoryGirl.define do
factory :global_list do
user
mi
foo_attribute
bar_attribute
end
factory :user do
# blah
end
factory :mi do
# blah
end
end
With above code, FactoryGirl will create required associated object at first and use their id to create this object automatically.
this did it:
FactoryGirl.define do
factory :global_list do
list_id { FactoryGirl.create(:user).liked_list_id }
global_id { FactoryGirl.create(:item).global_id }
end
end
Ruby's blocks in this context are confusing. So I asked: what's the practical difference between these two FactoryGirl declarations

Resources