FactoryGirl - How to dynamically generate, or override a callback - ruby-on-rails

I have an application with archive functionality, allowing users to restore objects after they are deleted.
An Organisation has_many Contacts, and must have at least one.
A Contact must belong_to a single Organisation
Both objects can be archived, however an Organisation is only archived when all of its associated Contacts are archived.
Currently I have the following. The problem with this approach is that FactoryGirl.create(:archived_organisation) appears to trigger both of the after(:create) callbacks in the :organisation factory:
factory :contact do
name { Faker::Name.first_name }
association :organisation, :factory => :organisation, :contacts_count => 0
factory :archived_contact do
archived true
association :organisation, :factory => :archived_organisation, :contacts_count => 0
end
end
factory :organisation do
name { Faker::Company.name }
ignore do
contacts_count 2
end
after(:create) do |instance, evaluator|
FactoryGirl.create_list(:contact, evaluator.contacts_count, organisation: instance)
end
factory :archived_organisation do
archived true
after(:create) do |instance, evaluator|
FactoryGirl.create_list(:archived_contact, evaluator.contacts_count, organisation: instance)
end
end
end
How should this be achieved? Is it possible to override the :organisation after(:create) callback in the :archived_organisation factory? If not, is it possible to dynamically generate the callback based on which factory is being created?
Hopefully I'm missing something obvious, but I'm welcome to suggestions that I should be going about this a different way.

How about:
after(:create) do |instance, evaluator|
unless instance.archived
FactoryGirl.create_list(:contact, evaluator.contacts_count, organisation: instance)
end
end

Related

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

Factory Girl - How to create a factory for a model that has associations?

I am attempting to create a factory for my user model, along with its associations. However, I cannot seem to get the syntax right in my Factory Girl code. I've read through the Factory Girl documentation but cannot seem to find any help with my specific use case. The errors I am currently receiving when I run my test suite are:
undefined method `subscription_args' for #<FactoryGirl::SyntaxRunner...
and
Trait not registered: valid_card_data
Here are my models and associations:
User.rb
has_one :subscription
has_one :plan, :through => :subscription
has_many :projects
Project.rb
belongs_to :user
Plan.rb
has_many :subscriptions
Subscription.rb
belongs_to :plan
belongs_to :user
and
And here is my Factory Girl code:
FactoryGirl.define do
factory :user do
first_name "Joel"
last_name "Brewer"
email { "#{first_name}.#{last_name}#example.com".downcase }
password "foobar"
password_confirmation "foobar"
user_type "entrepreneur"
subscription { build(:subscription, subscription_args) }
after(:create) do |user|
user.subscription.save!
end
end
factory :subscription do
user
plan_id '4'
## I am trying to access a helper method from support/utilities ##
## This call to valid_card_data doesn't seem to be working... ##
stripe_card_token valid_card_data
email "joel.brewer#example.com"
end
factory :project do
title "Sample Project"
user
end
end
Here's how I've done it in the past. Certainly not the only way:
(Note I am using cucumber.)
require 'factory_girl'
FactoryGirl.define do
factory :user do |f|
f.username 'superman'
end
factory :message do |f|
f.association :user
f.content 'Test message content'
end
end
This establishes that the message factory should associate the message to a user. Which user? I establish that at the point of use:
steps.rb:
Given(/^there is a user$/) do
#user = FactoryGirl.create(:user)
end
Given(/^the user has posted the message "(.*?)"$/) do |message_text|
FactoryGirl.create(:message, :content => message_text, :user => #user)
end
When(/^I visit the page for the user$/) do
visit user_path(#user)
end
Then(/^I should see "(.*?)"$/) do |text|
page.should have_content(text)
end
My approach, specifying at the point of use makes sense for this use case. e.g. Given is a user (user must be established first) and that user has posted a message (now the relationship between the existing user and the message can be established)...
That may or may not work out well for you, but it's how I've done it. This may or may not have helped you, but here's hoping.
There are several ways to do it. Here is one example:
after(:build) do |keyword, evaluator|
keyword.text = FactoryGirl.build(:keyword_text, :value => evaluator.keyword_text)
end
You dont need subscription_args - these can be set when you call the factory.
Where are you defining your trait?
In my factories they look like this:
trait :with_category_associations do
..
For more complicated relationships you probably want to use:
after(:create) do |keyword, evaluator|
evaluator.categories.each do |category|
FactoryGirl.create(:join_inventory_keyword, final: keyword, category: category)
end
end

Factory Girl and nested attributes validation error

I have a model Company which accepts nested attributes for Recruiters model. I need to have validation in my Company model that at least one recruiter was created during Company creation.
class Company < ActiveRecord::Base
has_many :recruiters, dependent: :destroy, inverse_of: :company
accepts_nested_attributes_for :recruiters, reject_if: ->(attributes) { attributes[:name].blank? || attributes[:email].blank? }, allow_destroy: true
validate { check_recruiters_number } # this validates recruiters number
private
def recruiters_count_valid?
recruiters.reject(&:marked_for_destruction?).count >= RECRUITERS_COUNT_MIN
end
def check_recruiters_number
unless recruiters_count_valid?
errors.add(:base, :recruiters_too_short, count: RECRUITERS_COUNT_MIN)
end
end
end
Validation works as expected but after adding this validation I have a problem with FactoryGirl. My factory for company looks like this:
FactoryGirl.define do
factory :company do
association :industry
association :user
available_disclosures 15
city 'Berlin'
country 'Germany'
ignore do
recruiters_count 2
end
after(:build) do |company, evaluator|
FactoryGirl.create_list(:recruiter, evaluator.recruiters_count, company: company)
end
before(:create) do |company, evaluator|
FactoryGirl.create_list(:recruiter, evaluator.recruiters_count, company: company)
end
end
end
In tests, when I do
company = create(:company)
I get validation error:
ActiveRecord::RecordInvalid:
Validation failed: Company has to have at least one recruiter
When I first build company and then save it, the test passes:
company = build(:company)
company = save
Of course, I don't want to change all my tests this way to make them work. How can I set up my factory to create associated model during creation Company model?
Your validate { check_recruiters_number } is unreasonable. Remove it.
Why? You need to have a valid company id to save recruiters, but your validator prevent company to be valid because it has no recruiters. This is in contradiction.
This is an old question but I have similar problem and I solved it with the following code (rewritten to match your case):
FactoryGirl.define do
factory :company do
association :industry
association :user
available_disclosures 15
city 'Berlin'
country 'Germany'
ignore do
recruiters_count 2
end
after(:build) do |company, evaluator|
company.recruiters = FactoryGirl.build_list(:recruiter, evaluator.recruiters_count, company: company)
end
end
end

Is it the correct way to implement belongs_to relation with factory girl?

I have a realy easy model. A user have a role_id which depends of a role table (id, name) with a role reference.
I want create users of any types on my rspec test, with factory girl.
My first idea is something like that
factory :role do
name "guest"
factory :role_admin do
name "admin"
end
factory :role_supervisor do
name "supervisor"
end
etc... I have a lot a different roles
end
factory :user do
email
password '123456'
password_confirmation '123456'
association :role, factory: :role
factory :admin do
association :role, factory: :role_admin
end
factory :supervisor do
association :role, factory: :role_supervisor
end
etc... I have a lot a different roles
end
In my model I have a simple method :
def is(role_name)
return self.role.name == role_name
end
It's the correct way do to? Do I realy need to create a factory for the role?
Can I make a stub for this function in factory girl for each role?
I realy new with all that test stuff, thanks.
Factories should reflect your models.
class User
has_many :products
end
class Product
belongs_to :user
end
factory :user do
products
end
factory :product do
end
If you want to have special cases (understand roles in your case) you can define traits:
factory :user do
traits :admin do
end
factory :admin_user, traits: [:admin]
end
More information on traits here.

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)

Resources