I have 2 factories. Beta_user and Beta_invite. Basically before a Beta_user can validly save I have to create an entry of Beta_invite. Unfortunately these models don't have clean associations, but they do share an email field.
Factory.sequence :email do |n|
"email#{n}#factory.com"
end
#BetaInvite
Factory.define :beta_invite do |f|
f.email {Factory.next(:email)}
f.approved false
f.source "web"
end
#User
Factory.define :user do |f|
f.email {Factory.next(:email)}
f.password "password"
end
#User => BetaUser
Factory.define :beta_user, :parent => :user do |f|
f.after_build do |user|
if BetaInvite.find_by_email(user.email).nil?
Factory(:beta_invite, :email => user.email)
end
end
end
So in the beta beta_user factory I am trying to use the after_build call back to create the beta_invite factory.
However it seems to be acting async or something. Possibly doing the find_by_email fetch?
If I try this:
Factory(:beta_user)
Factory(:beta_user)
Factory(:beta_user)
I get a failure stating that there is no record of a beta_invite with that users email.
If instead I try:
Factory.build(:beta_user).save
Factory.build(:beta_user).save
Factory.build(:beta_user).save
I get better results. As if calling the .build method and waiting to save allows time for the beta_invite factory to be created. Instead of calling Factory.create directly. The docs say that in the case of calling Factory.create both the after_build and after_create callbacks get called.
Any help is much appreciated.
UPDATE:
So the User model I am using does a before_validation call to the method that checks if there is a beta invite. If I move this method call to before_save instead. It works correctly. Is there something i'm over looking. When does factory_girl run the after_build and after_create callbacks in relation to active-record's before_validation and before_save?
To me it seems like it just should be able to work, but I have had problems with associations in Factory-girl as well. An approach I like to use in a case like this, if the relations are less evident, is to define a special method, inside your factory as follows:
def Factory.create_beta_user
beta_invite = Factory(:beta_invite)
beta_user = Factory(:user, :email => beta_invite.email)
beta_user
end
and to use that in your tests, just write
Factory.create_beta_user
Hope this helps.
Not sure if this would help you but this is the code I used:
# Create factories with Factory Girl
FactoryGirl.define do
# Create a sequence of unique factory users
sequence(:email) { |n| "factoryusername+#{n}#example.com"}
factory :user do
email
password "factorypassword"
# Add factory user email to beta invite
after(:build) {|user| BetaInvite.create({:email => "#{user.email}"})}
end
end
I found this comment gave a really good example:
term = create(:term)
period = create(:period, term: term)
candidate = create(:candidate, term: term)
I applied it to my situation and can confirm it works.
Related
How i can create object with association
I have Post model and PostsPhoto
PostsPhoto => belongs_to :post
Post => has_many :post_photos, dependent: :destroy,class_name: 'PostsPhoto'
I have tried
FactoryGirl.define do
factory :post do
article Faker::Lorem.paragraph(4)
video 'http://youtube.com'
author Faker::Name.name
category 'article'
title Faker::Name.title
post_photos
end
end
and have got
NoMethodError: undefined method `each' for #<PostsPhoto:0x007ff68e3bd698>
i cant use after(:create) {},because i validate it on create
def check_slider_photo
errors.add(:post_photos, 'Add post photos') if self.post_photos.size <= 0
end
I want to create(:post)=> returns me post object with post_photos
There are a couple ways around this. It depends on what you're testing exactly, but if you don't need to alter the objects or use them in queries, try using build_stubbed instead of create.
That is, if you'd typically say, FactoryGirl.create(:post), try instead saying FactoryGirl.build_stubbed(:post).
Another option would be not to run your validation in test and test that validation separately. To do this you can say
post = FactoryGirl.build(:post)
post.save(validate: false)
Then create the other objects and pass them post.id Happy testing.
Try this:
FactoryGirl.define do
factory :post do
article Faker::Lorem.paragraph(4)
video 'http://youtube.com'
author Faker::Name.name
category 'article'
title Faker::Name.title
factory :post_with_photos do
ignore do
photos_count 3
end
after(:build) do |post, evaluator|
post.post_photos << build_list(:post_photo, evaluator.photos_count)
end
end
end
end
Factory.define(:player) do |u|
u.association(:owner), :factory => :user
u.association(:updater), :factory => user
end
Can i rewrite the above definition such that , I can initialize the values of the owner and updater to be the same, without passing them in explicitly when i call create
Factory.define(:player) do |uu|
uu.association(:owner), :factory => :user
uu.association(:updater), { |player| player.owner }
end
When defining associations, I often find it easier to use one of the after_create or after_build hooks:
Factory.define(:player) do |u|
after_build do |player|
user = FactoryGirl.create :user
player.owner = user
player.creator = user
end
end
I also usually try to set up my factories so they'll work whether I'm building (instantiating) or creating (instantiating and saving), but ActiveRecord is a bit finicky about how you set up the associations when you're just building, so I used create in this example.
FactoryGirl won't set my protected attribute user.confirmed. What's the best practice here?
Factory.define :user do |f|
f.name "Tim" # attr_accessible -- this works
f.confirmed true # attr_protected -- doesn't work
end
I can do a #user.confirmed = true after using my factory, but that's a lot of repetition across a lot of tests.
Using an after_create hook works:
Factory.define :user do |f|
f.name "Tim"
f.after_create do |user|
user.confirmed = true
user.save
end
end
You would have to pass it into the hash when you create the user since FactoryGirl is protecting it from mass-assignment.
user ||= Factory(:user, :confirmed => true)
Another approach is to use Rails' built in roles like this:
#user.rb
attr_accessor :confirmed, :as => :factory_girl
When mass-assigning FactoryGirl broadcasts this role, making this pattern possible.
Pros: Keeps factories fast, simple, and clean (less code in callbacks)
Cons: You are changing your model code for your tests :(
Some untested suggestions to address the Con:
You could re-open the class just above your factory.
You could re-open the class in a [test|spec]_helper
I am using Ruby on Rails 3.0.9, RSpec-rails 2 and FactoryGirl. I am trying to state a Factory association model but I am in trouble.
I have a factories/user.rb file like the following:
FactoryGirl.define do
factory :user, :class => User do
attribute_1
attribute_2
...
association :account, :factory => :users_account, :method => :build, :email => 'foo#bar.com'
end
end
and a factories/users/account.rb file like the following:
FactoryGirl.define do
factory :users_account, :class => Users::Account do
sequence(:email) {|n| "foo#{n}#bar.com" }
...
end
end
The above example works as expected in my spec files, but if in the factory :users_account statement I add the association :user code so to have
FactoryGirl.define do
factory :users_account, :class => Users::Account do
sequence(:email) {|n| "foo#{n}#bar.com" }
...
association :user
end
end
I get the following error:
Failure/Error: Unable to find matching line from backtrace
SystemStackError:
stack level too deep
How can I solve that problem so to access associated models from both sides\factories (that is, in my spec files I would like to use RoR association model methods like user.account and account.user)?
P.S.: I read the Factory Girl and has_one question and my case is very close to the case explained in the linked question. That is, I have an has_one association too (between User and Users::Account classes).
According to the docs, you can't just put both sides of the associations into the factories. You'll need to use their after callback to set an object(s) to return.
For instance, in the factories/users/account.rb file, you put something like
after(:build) do |user_account, evaluator|
user_account.user = FactoryGirl.build(:user, :account=>user_account)
end
For has_many associations, you'll need to use their *_list functions.
after(:build) do |user_account, evaluator|
user_account.users = FactoryGirl.build_list(:user, 5, :account=>user_account)
end
Note: I believe the example in the docs is a bit misleading it doesn't assign anything to the object. I believe it should be something like (note the assignment).
# the after(:create) yields two values; the user instance itself and the
# evaluator, which stores all values from the factory, including ignored
# attributes; `create_list`'s second argument is the number of records
# to create and we make sure the user is associated properly to the post
after(:create) do |user, evaluator|
user.posts = FactoryGirl.create_list(:post, evaluator.posts_count, user: user)
end
Spyle's excellent answer (still working with Rails 5.2 and RSpec 3.8) will work for most associations. I had a use case where a factory needed to use 2 different factories (or different traits) for a single has_many association (ie. for a scope type method).
What I ended up coming up with was:
# To build user with posts of category == 'Special' and category == 'Regular'
after(:create) do |user, evaluator|
array = []
array.push(FactoryBot.create_list(:post, 1, category: 'Regular')
array.push(FactoryBot.create_list(:post, 1, category: 'Special')
user.posts = array.flatten
end
This allowed the user to have 1 post of category 'Regular' and 1 post of category 'Special.'
Using RSpec and Factory Girl, I create a record that when created, has associated 'hours' records automatically created in after_create.
But I want to test with the non-default hours, preferably ones I define in factory girl factories. Here's what I'm thinking I'd like to do:
before (:all) do
#business = Factory(:business_one)
# when that business is saved, a set of default hours is automatically saved as well
# how would I now update the hours with fixtures?
# so, ideally something like:
#business.hours[0] << Factory(:day_one)
#business.hours[1] << Factory(:day_two)
...etc...
end
Is this doable somehow or do I need to approach this differently?
Why not create an alternate factory:
Factory.define :business_with_altnernate_hours, :parent => :business_one do
after_create do |business|
business.hours.clear
Factory.create(:day_one, :business => business)
Factory.create(:day_two, :business => business)
end
end
This is what you can do:
#business = Factory(:business_one)
# clear associations so it's in a known state
#business.hours.clear
#business.hours << Factory(:day_one)
#business.hours << Factory(:day_two)
They will still be inserted and saved in the order in which you push in the new association.