I've been struggling with setting up a has_many/through relationship using Factory Girl.
I have the following models:
class Job < ActiveRecord::Base
has_many :job_details, :dependent => :destroy
has_many :details, :through => :job_details
end
class Detail < ActiveRecord::Base
has_many :job_details, :dependent => :destroy
has_many :jobs, :through => :job_details
end
class JobDetail < ActiveRecord::Base
attr_accessible :job_id, :detail_id
belongs_to :job
belongs_to :detail
end
My Factory:
factory :job do
association :tenant
title { Faker::Company.catch_phrase }
company { Faker::Company.name }
company_url { Faker::Internet.domain_name }
purchaser_email { Faker::Internet.email }
description { Faker::Lorem.paragraphs(3) }
how_to_apply { Faker::Lorem.sentence }
location "New York, NY"
end
factory :detail do
association :detail_type <--another Factory not show here
description "Full Time"
end
factory :job_detail do
association :job
association :detail
end
What I want is for my job factory to be created with a default Detail of "Full Time".
I've been trying to follow this, but have not had any luck:
FactoryGirl Has Many through
I'm not sure how the after_create should be used to attach the Detail via JobDetail.
Try something like this. You want to build a detail object and append it to the job's detail association. When you use after_create, the created job will be yielded to the block. So you can use FactoryGirl to create a detail object, and add it to that job's details directly.
factory :job do
...
after_create do |job|
job.details << FactoryGirl.create(:detail)
end
end
I faced this issue today and I found a solution. Hope this helps someone.
FactoryGirl.define do
factory :job do
transient do
details_count 5 # if details count is not given while creating job, 5 is taken as default count
end
factory :job_with_details do
after(:create) do |job, evaluator|
(0...evaluator.details_count).each do |i|
job.details << FactoryGirl.create(:detail)
end
end
end
end
end
This allows to create a job like this
create(:job_with_details) #job created with 5 detail objects
create(:job_with_details, details_count: 3) # job created with 3 detail objects
This worked for me
FactoryGirl.define do
factory :job do
# ... Do whatever with the job attributes here
factory :job_with_detail do
# In later (as of this writing, unreleased) versions of FactoryGirl
# you will need to use `transitive` instead of `ignore` here
ignore do
detail { create :detail }
end
after :create do |job, evaluator|
job.details << evaluator.detail
job.save
job_detail = job.job_details.where(detail:evaluator.detail).first
# ... do anything with the JobDetail here
job_detail.save
end
end
end
end
Then later
# A Detail object is created automatically and associated with the new Job.
FactoryGirl.create :job_with_detail
# To supply a detail object to be associated with the new Job.
FactoryGirl.create :job_with_detail detail:#detail
Since FactoryBot v5, associations preserve build strategy. Associations are the best way to solve this and the docs have good examples for it:
FactoryBot.define :job do
job_details { [association(:job_detail)] }
end
FactoryBot.define :detail do
description "Full Time"
end
FactoryBot.define :job_detail do
association :job
association :detail
end
You can solve this problem in the following way:
FactoryBot.define do
factory :job do
# job attributes
factory :job_with_details do
transient do
details_count 10 # default number
end
after(:create) do |job, evaluator|
create_list(:details, evaluator.details_count, job: job)
end
end
end
end
With this, you can create a job_with_details, that has options to specify how many details you want.
You can read this interesting article for more details.
With the current factory_bot(previously factory_girl) implementation, everything is taken care by the gem, you don't need to create and then push the records inside the jobs.details. All you need is this
factory :job do
...
factory :job_with_details do
transient do
details_count { 5 }
end
after(:create) do |job, evaluator|
create_list(:detail, evaluator.details_count, jobs: [job])
job.reload
end
end
end
Below code will produce 5 detail jobs
create(:job_with_details)
Related
Putting aside arguments on whether or not you should test existence of a model's associations, I have a model called Order and I am validating that it has at least one item in its has_many association using:
class Order < ActiveRecord::Base
has_many :items
validates :items, presence: true
end
I have set FactoryGirl to lint my factories (checking for validity). So my order factory is not valid unless I create an item for its has_many collection.
My orders factory looks like this:
FactoryGirl.define do
factory :order do
ignore do
items_count 1
end
after(:build) do |order, evaluator|
create_list(:item, evaluator.items_count, order: order)
end
end
end
According to Factory Girl's Getting Started:
FactoryGirl.lint builds each factory and subsequently calls #valid? on it
However when I run my specs, Factory Girl throws an FactoryGirl::InvalidFactoryError because the order factory is invalid.
Workaround
after(:build) do |order, evaluator|
evaluator.items_count.times do
order.items << FactoryGirl.create(:item)
end
#create_list(:item, evaluator.items_count, order: order)
end
According to the definition, it will call .valid? AFTER building. It seems that it will call this before running the after(:build) block.
Try writing you factory like this:
FactoryGirl.define do
factory :order do
ignore do
items_count 1
end
items { build_list(:item, items_count) }
end
end
This should build the item before the .valid? is called.
Let me know if this works :)
Here is an example from the FactoryGirl documentation:
FactoryGirl.define do
factory :post do
name "Post name"
user
end
end
In this example, user is invoking another factory. What I would like to do is effectively call user.id, but to set it as the definition of an attribute. Here's a stripped-down example:
**models/job.rb**
...
belongs_to :assignee, :class_name => "User"
belongs_to :user
...
attr_accessible :assignee_id, :user_id
...
end
**factories/jobs.rb**
FactoryGirl.define do
factory :job do
assignee_id user.id #what I would like to do, but triggers "undefined method 'id'" error
user_id user.id #user_id is an attribute of the model and is the job assignor
end
I've tried to incorporate the part of the documentation that discusses aliases, but with no luck:
FactoryGirl.define do
factory :user, :aliases => [:assignee] do
....
I feel like (hope?) I'm close here, but any insight is appreciated. Thanks.
EDIT: This code gets my specs running!
**factories/jobs.rb**
FactoryGirl.define do
factory :job do
before(:create) do |job|
user = FactoryGirl.create(:user)
job.assignee = user
job.user = user
end
association :assignee, factory: :user
association :user, factory: :user
sequence(:user_id) { |n| n }
sequence(:assignee_id) { |n| n }
...
end
And it passes my it { should be_valid } spec, so it seems that the factory is fine, though I think I have some refactoring in the spec itself when I'm calling FactoryGirl.create.
The code above incorporates the suggestions from mguymon. Thanks!
FINAL UPDATE
After going back and re-reading Hartl's discussion on model associations, I was able to put this matter to rest. What I have above was techincally valid, but didn't actually pass the attributes in properly when i built or created jobs in my spec. Here's what I should have had:
FactoryGirl.define do
factory :job do
association :assignee, factory: :user
user
end
end
My problem also stemmed from how I was creating factories in my spec, so here's how I should have been doing it (but wasn't...sigh):
let(:user) { create(:user) }
before { #job = create(:job, user: #user) }
It seems that I don't explicitly have to have association :user in my factory, nor do I need the before block from above.
As an aside, I also learned that I can debug by including puts #job within an expect statement, or call #job.assignee_id to make sure that the attributes are being loaded properly. When that particular spec is run, the puts statement will output right by the F or . from the spec.
For the latest version of FactoryGirl, use association to map to other ActiveRecord Models:
factory :job do
# ...
association :assignee, factory: :user
end
This is straight from the docs.
From the error you posted, it is stating you are trying to get the user.id but user is not an ActiveRecord instance but a proxy from FactoryGirl. You will not get this error if you are using the association method. If you need to directly access a model, you have to manually build it first. You can do this by passing a block in your factory:
factory :job do
assignee_id { FactoryGirl.create(:user).id }
end
It looks like you are trying to associate the same model twice, for this you can use before_create callback to create a User model and assign to user and assignee:
factory :job do
before(:create) do |job|
user = FactoryGirl.create(:user)
job.assignee = user
job.user = user
end
end
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
The design
I have a User model that belongs to a profile through a polymorphic association. The reason I chose this design can be found here. To summarize, there are many users of the application that have really different profiles.
class User < ActiveRecord::Base
belongs_to :profile, :dependent => :destroy, :polymorphic => true
end
class Artist < ActiveRecord::Base
has_one :user, :as => :profile
end
class Musician < ActiveRecord::Base
has_one :user, :as => :profile
end
After choosing this design, I'm having a hard time coming up with good tests. Using FactoryGirl and RSpec, I'm not sure how to declare the association the most efficient way.
First attempt
factories.rb
Factory.define :user do |f|
# ... attributes on the user
# this creates a dependency on the artist factory
f.association :profile, :factory => :artist
end
Factory.define :artist do |a|
# ... attributes for the artist profile
end
user_spec.rb
it "should destroy a users profile when the user is destroyed" do
# using the class Artist seems wrong to me, what if I change my factories?
user = Factory(:user)
profile = user.profile
lambda {
user.destroy
}.should change(Artist, :count).by(-1)
end
Comments / other thoughts
As mentioned in the comments in the user spec, using Artist seems brittle. What if my factories change in the future?
Maybe I should use factory_girl callbacks and define an "artist user" and "musician user"? All input is appreciated.
Although there is an accepted answer, here is some code using the new syntax which worked for me and might be useful to someone else.
spec/factories.rb
FactoryGirl.define do
factory :musical_user, class: "User" do
association :profile, factory: :musician
#attributes for user
end
factory :artist_user, class: "User" do
association :profile, factory: :artist
#attributes for user
end
factory :artist do
#attributes for artist
end
factory :musician do
#attributes for musician
end
end
spec/models/artist_spec.rb
before(:each) do
#artist = FactoryGirl.create(:artist_user)
end
Which will create the artist instance as well as the user instance. So you can call:
#artist.profile
to get the Artist instance.
Use traits like this;
FactoryGirl.define do
factory :user do
# attributes_for user
trait :artist do
association :profile, factory: :artist
end
trait :musician do
association :profile, factory: :musician
end
end
end
now you can get user instance by FactoryGirl.create(:user, :artist)
Factory_Girl callbacks would make life much easier. How about something like this?
Factory.define :user do |user|
#attributes for user
end
Factory.define :artist do |artist|
#attributes for artist
artist.after_create {|a| Factory(:user, :profile => a)}
end
Factory.define :musician do |musician|
#attributes for musician
musician.after_create {|m| Factory(:user, :profile => m)}
end
You can also solve this using nested factories (inheritance), this way you create a basic factory for each class then
nest factories that inherit from this basic parent.
FactoryGirl.define do
factory :user do
# attributes_for user
factory :artist_profile do
association :profile, factory: :artist
end
factory :musician_profile do
association :profile, factory: :musician
end
end
end
You now have access to the nested factories as follows:
artist_profile = create(:artist_profile)
musician_profile = create(:musician_profile)
Hope this helps someone.
It seems that polymorphic associations in factories behave the same as regular Rails associations.
So there is another less verbose way if you don't care about attributes of model on "belongs_to" association side (User in this example):
# Factories
FactoryGirl.define do
sequence(:email) { Faker::Internet.email }
factory :user do
# you can predefine some user attributes with sequence
email { generate :email }
end
factory :artist do
# define association according to documentation
user
end
end
# Using in specs
describe Artist do
it 'created from factory' do
# its more naturally to starts from "main" Artist model
artist = FactoryGirl.create :artist
artist.user.should be_an(User)
end
end
FactoryGirl associations: https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#associations
I currently use this implementation for dealing with polymorphic associations in FactoryGirl:
In /spec/factories/users.rb:
FactoryGirl.define do
factory :user do
# attributes for user
end
# define your Artist factory elsewhere
factory :artist_user, parent: :user do
profile { create(:artist) }
profile_type 'Artist'
# optionally add attributes specific to Artists
end
# define your Musician factory elsewhere
factory :musician_user, parent: :user do
profile { create(:musician) }
profile_type 'Musician'
# optionally add attributes specific to Musicians
end
end
Then, create the records as usual: FactoryGirl.create(:artist_user)
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