FactoryGirl - override association with trait? - ruby-on-rails

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

Related

How can I access to a child class instead of parent class in a 'callback' function of factorybot in ruby on rails?

I have implemented a STI in ROR. please look at the following code:
class Category < ApplicationRecord
end
class CourseCategory < Category
has_many :courses
end
I use FactoryBot to create data like this:
# category
FactoryBot.define do
factory :category do
name {"Ruby on Rails"}
end
end
# course_category
FactoryBot.define do
factory :course_category, parent: :category, class: 'Category' do
trait :with_course do
after(:create) do |course_category| # my problem is here
create :course, :with_steps, course_category: course_category
end
end
end
# course
FactoryBot.define do
factory :course do
course_category
end
end
while I run FactoryBot.create(:course_category, :with_course), into trait :with_course , I should have CourseCategory class, but I receive Category.
Can I have access to parent class instead of child class into a callback of FactoryBot?
Your factory is building :course_category as the wrong class.
This line needs to be changed...
factory :course_category, parent: :category, class: 'Category' do
Into...
factory :course_category, parent: :category, class: 'CourseCategory' do

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

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.

FactoryGirl and polymorphic associations

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)

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