Rails factory_girls and validation issue - ruby-on-rails

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

Related

FactoryGirl - override association with trait?

Let's say I have the following ActiveRecord models:
class Car
belongs_to :driver
end
class Driver
# Has attribute :name
has_one :car
end
And I define a couple of factories using these models:
FactoryGirl.define do
factory :car do
association :driver
trait :fast_car do
association :driver, :fast
end
end
end
FactoryGirl.define do
factory :driver do
name 'Jason'
trait :fast do
name 'Mario'
end
end
end
When I execute the following code:
car = FactoryGirl.create(:car, :fast_car)
I would expect car.driver.name to equal Mario, but instead it equals Jason. This leads me to believe that you can't use traits to override associations for factories. Is this true? If so, what would be the proper way to override the associated Driver for a fast car?
Fortunately, you can. You need to specify the factory keyword for an association with an array, where the first element is the name of the factory that you want to use for the association and the rest elements are the factory's traits:
FactoryGirl.define do
factory :car do
association :driver
trait :fast_car do
association :driver, factory: [:driver, :fast]
end
end
end
FactoryGirl.define do
factory :driver do
name 'Jason'
trait :fast do
name 'Mario'
end
end
end

Validating That A has_many Association Has At Least One Model When Using FactoryGirl

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 :)

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

Factory Girl: How to set up a has_many/through association

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)

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