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
Related
spec/factories/company.rb
FactoryBot.define do
factory :company do
name { Faker::Name.unique.name }
time_zone_name { Faker::Address.time_zone }
users { [ FactoryBot.create(:user) ] }
end
end
spec/factories/company.rb
FactoryBot.define do
factory :user do
email { Faker::Internet.email }
password {'123qwe123'}
name { Faker::Superhero::name }
association :company, factory: :company
end
end
app/model/company.rb
class Company < ApplicationRecord
validate :check_users_present?
has_many :users
private
def check_users_present?
errors.add(:base, "You must provide at least one user") if users.size < 1
end
end
app/model/user.rb
class User < ApplicationRecord
belongs_to :company
end
when I am trying to make a :company it throws SystemStackError (stack level too deep).
since we have validation for creating at least one user with the company and user requires company. we are having a circular dependency issue.
i tried this solution, but that doesnt seems to fix it. or i am not sure how to do it properly (How to avoid circular creation of associated models in factory_girl? )
i tried using transient but i am not sure how to configure it for has_many associations. please can someone help me in figuring this out
Here's the circular dependency:
FactoryBot.define do
factory :company do
...
users { [ FactoryBot.create(:user) ] } # HERE
end
end
FactoryBot.define do
factory :user do
...
association :company, factory: :company # AND HERE
end
end
When trying to create a company with FactoryGirl, it invokes FactoryBot.create to create a new user, which at the same time is verifying the company factory.
If you want to add associations, I'd personally advice you to do it in your examples, not in the factories. Since the factories are meant to be used along your whole projects, and there are tests that aren't going to share the same logic as you´re testing today, as a matter of fact, that might change sooner or later.
My problem is that of FactoryGirl has_many association with validation, with the added complexity of the association being a "transient" attribute, and is quite nested
ie. my classes are (I'm using mongoid, assume Mongoid::Document is included in all models)
class User
has_many :company_admin_profiles
end
class CompanyAdminProfile
belongs_to :company
belongs_to :user # Cannot exist standalone
end
class Company
has_many :company_admin_profiles
validate :has_at_least_one_admin
end
So far with FactoryGirl I've written
FactoryGirl.define do
factory :user do
...
trait(:company_admin) do
transient do
company_admins_count 1
company { create(:company, admins_count: 0) }
end
after(:build) do |user, evaluator|
create_list(:company_admin_profile, evaluator.company_admins_count,
company: evaluator.company,
user: user,
first_name: user.first_name,
last_name: user.last_name,
email: user.email)
end
end
factory :company_admin_user, traits: [:company_admin]
end
end
FactoryGirl.define do
factory :company_admin_profile, class: Company::Admin do
company
end
end
FactoryGirl.define do
factory :company do
transient do
admins_count 1 # need one admin to pass validation
end
after(:build) do |company, evaluator|
build_list(:company_admin_user, evaluator.admins_count,
company: company)
end
end
end
I've tried several variations on this, the last error is
* company - Attribute already defined: company (FactoryGirl::AttributeDefinitionError)
* company_admin_profile - Attribute already defined: company (FactoryGirl::AttributeDefinitionError)
* company_admin_user - Attribute already defined: company (FactoryGirl::AttributeDefinitionError)
I just saw an upvote on the question, so maybe someone is actually interested in the solution I have for now.
I don't remember exactly where the problem was, but here's a code that works :
# factories/company.rb
FactoryGirl.define do
factory :company do
...
transient do
admins_count 1 # need one admin to pass validation
end
after(:build) do |comp, evaluator|
if evaluator.admins_count == 1
build(:company_admin_user, company: comp)
end
end
end
end
# factories/user.rb
FactoryGirl.define do
factory :user do
...
after(:build) do |user|
user.skip_confirmation!
end
# Company admin
trait(:company_admin) do
transient do
company_admins_count 1
company { build(:company, admins_count: 0) }
end
after(:build) do |user, evaluator|
create_list(:company_admin_profile, evaluator.company_admins_count,
company: evaluator.company,
user: user)
evaluator.company.save
end
end
factory :company_admin_user, traits: [:company_admin]
end
end
# factories/company_admin_profile.rb
FactoryGirl.define do
factory :company_admin_profile, class: CompanyAdminProfile do
...
company
end
end
I have model Student, which has_one :account.
All input-related data is stored inside of account. Student model just plays it's role when it comes to relations (some other models belong to student).
Problem: I can't test it with factory girl.
factory :student do
end
As I can't define anything besides it.
What I get on every attempt of #student = FactoryGirl.create(:student):
undefined method `valid?' for nil:NilClass
Any fixes?
Additional code
class Account < ActiveRecord::Base
belongs_to :account_holder, :polymorphic => true
...
end
factory :account do
sequence :name do |n|
"Name#{n}"
end
sequence :surname do |n|
"Surname#{n}"
end
sequence :phone do |n|
"8911222332#{n}"
end
sequence :email do |n|
"somemail#{n}#mail.ru"
end
student
end
Source of issue
Student has:
validates_associated_extended :account
which is basically usual validate but with error extraction for parent model.
So when FactoryGirl attempts to create student, it validates account, which is nil.
I tried this:
before(:create) {|student| build(:account, :account_holder =>student )}
in student factory, while in account factory:
association :account_holder, :factory=>:student
But it still doesn't work.
factory :student do
after :build do |student|
student.account << create(:account, :account_holder => student) if student.account_holder.nil?
end
end
This allows you to have both a valid student and to specify an account if you want to force one.
I think latest versions of FactoryGirl even allow that to be written as lazy attribute syntax:
factory :student do
account { create(:account) }
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