Extra arguments for Factory Girl - ruby-on-rails

I need to pass extra arguments to factory girl to be used in a callback. Something like this (but more complex really):
Factory.define :blog do |blog|
blog.name "Blah"
blog.after_create do |blog|
blog.posts += sample_posts
blog.save!
end
end
and then create it with something like this:
Factory.create(:blog, :sample_posts => [post1, post2])
Any ideas how to do it?

This is now possible without any "hacks" thanks to transient attributes (see comment on issue #49)
example:
FactoryGirl.define do
factory :user do
transient do
bar_extension false
end
name {"foo #{' bar' if bar_extension}"}
end
end
# Factory(:user).name = "foo"
# Factory(:user, :bar_extension => true).name = "foo bar"
For Factory Girl versions < 5.0:
FactoryGirl.define do
factory :user do
ignore do
bar_extension false
end
name {"foo #{' bar' if bar_extension}"}
end
end
# Factory(:user).name = "foo"
# Factory(:user, :bar_extension => true).name = "foo bar"

One option would be to create a virtual accessor for extra posts that the after_create hook checks:
class Blog
has_many :posts
attr_accessible :name, :title, ... # DB columns
attr_accessor :sample_posts # virtual column
end
Factory.define :blog do |blog|
blog.name 'Blah'
blog.after_create do |b|
b.posts += b.sample_posts
b.save!
end
end
Factory(:blog, :sample_posts => [post1, post2])

Apparently, this is not possible at the moment without workarounds that require modifying the model itself. This bug is reported in: http://github.com/thoughtbot/factory_girl/issues#issue/49

Another option would be to use build instead of create and add :autosave to the collection:
class Blog
has_many :posts, :autosave => true
end
Factory.define :blog do |blog|
blog.name 'Blah'
blog.posts { |_| [Factory.build(:post)] }
end
Factory(:blog, :posts => [post1, post2])
#or
Factory.build(:blog, :posts => [unsavedPost1, unsavedPost2])

if you are opening the class inside the factorygirl file, i suggest doing it like
require "user"
class User
attr :post_count
end
so that you are opening the class, instead of overwriting it

Related

Rails factory_girls and validation issue

I have HABTM association
my models
class Ssr < ActiveRecord::Base
has_and_belongs_to_many :ssr_groups
validates :ssr_groups, presence: true
end
class SsrGroup < ActiveRecord::Base
has_and_belongs_to_many :ssrs, dependent: :destroy
validates :name, presence: true
end
my factories
FactoryGirl.define do
factory :ssr do
type 'type'
ssr_groups
end
end
FactoryGirl.define do
factory :ssr_group, class: 'SsrGroup', aliases: [:ssr_groups] do
name { SecureRandom.hex }
end
end
My problem is when i want to create FactoryGirl.create(:ssr)
i have got NoMethodError: undefined method each for #<SsrGroup:0x007fbfdf792100>
Why it happens?
Thee problem is that your ssr factory is expecting a collection of ssr_group, and what you are doing is getting just one. That's why the error, because it's trying to do foreach on the created ssr_group.
To fix this, you can do something like this:
FactoryGirl.define do
factory :ssr do
type 'type'
after(:create) do |ssr, evaluator|
create_list(:ssr_group, 1, ssrs: [ssr])
end
end
end
You can use the build strategy instead of the create if preferred.
EDIT:
You can improve your factory a little, like this:
FactoryGirl.define do
factory :ssr do
type 'type'
factory :ssr_with_groups do
transient do
groups_count 5 # Default count of ssr_groups it will create
end
after(:create) do |ssr, evaluator|
create_list(:ssr_group, evaluator.groups_count, ssrs: [ssr])
end
end
end
end
That way, it's more flexible and you can use it like this:
create(:ssr_with_groups, groups_count: 10)
And it would create a ssr with 10 ssr_groups.
EDIT 2:
Given you have a presence validation on the association, you need to add the associations before saving the object, so use the build strategy instead, like this:
FactoryGirl.define do
factory :ssr do
type 'type'
after(:build) do |ssr, evaluator|
ssr.ssr_groups << build_list(:ssr_group, 1, ssrs: [ssr])
end
end
end
You can take a deeper look at the docs: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Associations

Using associations in hooks with FactoryGirl

I'm in a hard situation with FactoryGirl that maybe you can help me to solve. The code is like this:
class Bet
belongs_to :market
belongs_to :option
has_one :market, :through => :option
has_one :event, :through => :market
before_validation :set_event_date
scope :by_event_date, order(arel_table[:event_date].desc)
def set_event_date
self.event_date = event.date
end
end
I need to materialize the event_date attribute in Bet because the scope by_event_date is too costly without the materialization. The problem comes when I run FactoryGirl.create :bet. The hook gets executed, but bet.event is nil, and therefore an exception raises. Is there anyway to configure FactoryGirl to really create the associated objects?
what's your factory code? you can do something like
FactoryGirl.define do
factory :event do
#something
end
factory :bet do
#something
event
end
end
that should create an event for you
you can also use factorygirl callbacks to customize it a little
FactoryGirl.define do
factory :event do
#something
end
factory :bet do
#something
after_build do |bet| #for newer version it is after(:build) do |bet|...
bet.event = Factory.build(:event)
end
end
end
when the record is saved the event will be saved to
EDIT: try assigning a market then
FactoryGirl.define do
factory :event do
#something
end
factory :market do
event
end
factory :bet do
#something
market
end
end

FactoryGirl and acts_as_tree using after_create

I have a class named MenuHeader with the following:
class MenuHeader < ActiveRecord::Base
acts_as_tree :parent_id
belongs_to :menu
I'd like to create several levels of menu_headers but am not sure how to do this:
FactoryGirl.define do
factory :menu_header do
name "my menu header"
menu
after(:create) { |menu_header| do_something_else_to(user) }
end
end
Would I create a :menu_header2 or can I some how just extend the above. How do I set the :parent_id to the value of the recently created menu_header?
thx
I'd create different level of menu_header factories and build their children using after(:build). btw I prefer after :build because I can easily create non-persisted objects for testing.
FactoryGirl.define do
factory :menu_header do
name "my menu header"
menu
factory :menu_header_level1 do
after(:build) do |m|
m.children << FactoryGirl.build(:menu_header, :name => 'level0 submenu0', :parent => m)
m.children << FactoryGirl.build(:menu_header, :name => 'level0 submenu1', :parent => m)
end
end
factory :menu_header_level2 do
after(:build) do |m|
m.children << FactoryGirl.build(:menu_header_level1, :name => 'level1 submenu0', :parent => m)
end
end
end
end

factory_girl_rails has_many association includes only one from a collection of many

I'm using factory_girl_rails instead of fixtures. Here are my models:
class User < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :user
end
Here are my factories:
Factory.define :user do |u|
end
Factory.define :task do |t|
t.association :user, :factory => :user
end
In a test I do this:
user = Factory.create :user
(1..5).each { Factory.create(:task, :user => user)}
The problem that I'm experiencing is that afterward user.tasks contains only one task.
I have tried defining the user factory like this:
Factory.define :user do |u|
u.tasks {|tasks| [tasks.association(:user)] }
end
and like this:
Factory.define :user do |u|
u.tasks {|tasks| [tasks.association(:user), tasks.association(:user)] }
end
In both cases Factory.create(:user) causes an infinite loop.
I think what you're doing is backwards -- you can create Tasks from the User, but not visa versa like you're trying to do. Conceptually, the association goes from the user to the task.
What I did was create a special factory for that type of user.
Factory.define :user_with_tasks, :parent => :user do |user|
user.after_create {|u| 5.times { Factory.create(:task, :user => user) } }
end
Also, if you just want to use the default factory, you just do this
Factory.define :task do |f|
f.user :user
end
No need to specify the factory
UPDATE: Looks like others have done something similar in the past:
Populating an association with children in factory_girl
Ah, this works:
Factory.define :task do |t|
t.association :user
t.after_create {|t| t.user.tasks << t}
end
John, your approach would work but I actually couldn't use what you described because in my test the tasks need to also reference another (unmentioned) model and I don't see a way of referencing it in the user_with_tasks factory definition.

How to create has_and_belongs_to_many associations in Factory girl

Given the following
class User < ActiveRecord::Base
has_and_belongs_to_many :companies
end
class Company < ActiveRecord::Base
has_and_belongs_to_many :users
end
how do you define factories for companies and users including the bidirectional association? Here's my attempt
Factory.define :company do |f|
f.users{ |users| [users.association :company]}
end
Factory.define :user do |f|
f.companies{ |companies| [companies.association :user]}
end
now I try
Factory :user
Perhaps unsurprisingly this results in an infinite loop as the factories recursively use each other to define themselves.
More surprisingly I haven't found a mention of how to do this anywhere, is there a pattern for defining the necessary factories or I am doing something fundamentally wrong?
Here is the solution that works for me.
FactoryGirl.define do
factory :company do
#company attributes
end
factory :user do
companies {[FactoryGirl.create(:company)]}
#user attributes
end
end
if you will need specific company you can use factory this way
company = FactoryGirl.create(:company, #{company attributes})
user = FactoryGirl.create(:user, :companies => [company])
Hope this will be helpful for somebody.
Factorygirl has since been updated and now includes callbacks to solve this problem. Take a look at http://robots.thoughtbot.com/post/254496652/aint-no-calla-back-girl for more info.
In my opinion, Just create two different factories like:
Factory.define :user, :class => User do |u|
# Just normal attributes initialization
end
Factory.define :company, :class => Company do |u|
# Just normal attributes initialization
end
When you write the test-cases for user then just write like this
Factory(:user, :companies => [Factory(:company)])
Hope it will work.
I couldn´t find an example for the above mentioned case on the provided website. (Only 1:N and polymorphic assocations, but no habtm). I had a similar case and my code looks like this:
Factory.define :user do |user|
user.name "Foo Bar"
user.after_create { |u| Factory(:company, :users => [u]) }
end
Factory.define :company do |c|
c.name "Acme"
end
What worked for me was setting the association when using the factory.
Using your example:
user = Factory(:user)
company = Factory(:company)
company.users << user
company.save!
Found this way nice and verbose:
FactoryGirl.define do
factory :foo do
name "Foo"
end
factory :bar do
name "Bar"
foos { |a| [a.association(:foo)] }
end
end
factory :company_with_users, parent: :company do
ignore do
users_count 20
end
after_create do |company, evaluator|
FactoryGirl.create_list(:user, evaluator.users_count, users: [user])
end
end
Warning: Change users: [user] to :users => [user] for ruby 1.8.x
For HABTM I used traits and callbacks.
Say you have the following models:
class Catalog < ApplicationRecord
has_and_belongs_to_many :courses
…
end
class Course < ApplicationRecord
…
end
You can define the Factory above:
FactoryBot.define do
factory :catalog do
description "Catalog description"
…
trait :with_courses do
after :create do |catalog|
courses = FactoryBot.create_list :course, 2
catalog.courses << courses
catalog.save
end
end
end
end
First of all I strongly encourage you to use has_many :through instead of habtm (more about this here), so you'll end up with something like:
Employment belongs_to :users
Employment belongs_to :companies
User has_many :employments
User has_many :companies, :through => :employments
Company has_many :employments
Company has_many :users, :through => :employments
After this you'll have has_many association on both sides and can assign to them in factory_girl in the way you did it.
Update for Rails 5:
Instead of using has_and_belongs_to_many association, you should consider: has_many :through association.
The user factory for this association looks like this:
FactoryBot.define do
factory :user do
# user attributes
factory :user_with_companies do
transient do
companies_count 10 # default number
end
after(:create) do |user, evaluator|
create_list(:companies, evaluator.companies_count, user: user)
end
end
end
end
You can create the company factory in a similar way.
Once both factories are set, you can create user_with_companies factory with companies_count option. Here you can specify how many companies the user belongs to: create(:user_with_companies, companies_count: 15)
You can find detailed explanation about factory girl associations here.
You can define new factory and use after(:create) callback to create a list of associations. Let's see how to do it in this example:
FactoryBot.define do
# user factory without associated companies
factory :user do
# user attributes
factory :user_with_companies do
transient do
companies_count 10
end
after(:create) do |user, evaluator|
create_list(:companies, evaluator.companies_count, user: user)
end
end
end
end
Attribute companies_count is a transient and available in attributes of the factory and in the callback via the evaluator. Now, you can create a user with companies with the option to specify how many companies you want:
create(:user_with_companies).companies.length # 10
create(:user_with_companies, companies_count: 15).companies.length # 15

Resources