FactoryGirl Associations - ruby-on-rails

I have a pretty basic rails app that I'm working on.
Parts are in certain states (state_id), they are created by a user (user_id) and have an associated type with them (type_id).
Trying to create a factory for part, I have:
FactoryGirl.define do
factory :part do
name "blah"
association :state_id, factory: :state
association :user_id, factory: :user
association :techtype_id, factory: :techtype
end
factory :state do
name "blah"
end
factory :user do
login "blah"
end
factory :techtype do
name "blah"
example "bleh"
end
end
Yet FactoryGirl.create(:part) doesn't seem to work:
2.0.0p353 :001 > part = FactoryGirl.create(:part)
[SQL insert for State, User, and Techtype outputs here and succeeds, then...]
ActiveRecord::RecordInvalid: Validation failed:
State can't be blank, Techtype can't be blank, User can't be blank
I've tried removing the _id attribute (i.e. association :state, factory: :state) but that doesnt work either, I just get a NoMethodError: undefined method 'state=' for #<Part:0x007fa3e8e798a0>. I've also just tried using the short form association (i.e. state instead of association :state_id, factory: :state) but I get the same NoMethodError.

Your model should look like this
class Part < ActiveRecord::Base
belongs_to :state
belongs_to :user
belongs_to :techtype
end
And your factory like this
factory :part do
name "blah"
association :state
association :user
association :techtype
end

Related

Build factories for self referential associations in rails

I have a typical requirement, I have to address user object as follows
user.referrer and user.referrers.
Basically, user can refer more than one person and one person should be referred by one particular user. So I build associations as follows. They are working great.
class User < ActiveRecord::Base
attr_accessible :account_number, :display_name, :referrer_id
has_many :referrers, :class_name => "User", :foreign_key => "referrer_id"
belongs_to :referrer, :class_name => "User"
end
Now I would like to test assoications in Rspec. I am using factory girl so any one help me to build factories.
I tried as follows but end up with an errors
factory :user do
gender :male
name "super test"
.....
.....
factory :referrer do
end
association :referrer
end
You need to build two factories here, one for user with a referrer and second one for user without a referer - otherwise you'll end up in the infinite creation loop. You might use traits for this:
factory :user do
gender :male
name "super test"
trait :with_referrer do
association :referrer, factory: :user
end
end
FactoryGirl.create(:user, :with_referrer)

Factory girl : validates associated leads to undefined method valid? for nil:Nilclass

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

FactoryGirl Name "can't be blank" VS "already taken"

I'm trying to run some tests on a model "Click".
# models/click_spec.rb
describe Click do
it "should have a valid constructor" do
FactoryGirl.create(:click).should be_valid
end
end
The objective is that the model uses two tables that have the same country. I don't want to use a sequence for the country (as I found for Emails).
But it raise this error:
Validation failed: Name has already been taken, Slug has already been taken
The problem is that it seems that it create twice the country [name:"United States", slug:"us"]
Here's the factories used.
# factories/countries.rb
FactoryGirl.define do
factory :country do
name "United States"
slug "us"
end
end
# factories/offers.rb
FactoryGirl.define do
factory :offer do
association :country, factory: :country
# Other columns
end
end
# factories/users.rb
FactoryGirl.define do
factory :user do
association :country, factory: :country
# Other columns
end
end
# factories/clicks.rb
FactoryGirl.define do
factory :click do
association :offer, factory: :offer
association :user, factory: :user
# Other columns
end
end
and the model of Country:
class Country < ActiveRecord::Base
validates :name, :slug,
presence: true,
uniqueness: { case_sensitive: false }
validates :slug,
length: { is: 2 }
end
I've tried to change the association strategy to something like this:
association :country, factory: :country, strategy: :build
But it raise this error:
Validation failed: Country can't be blank
Any idea?
Thanks,
As per the shared code,
when you call FactoryGirl.create(:click),
it will go to execute factory :click where it finds association :offer, factory: :offer
which in turn calls factory: :offer where you create a country with name "United States" and slug "us" for the first time.
Again, in factory :click, it finds association :user, factory: :user which in turn calls factory: :user where you create a country again with the same name "United States" and slug "us" for the second time.
Issue #1: Validation failed: Name has already been taken, Slug has already been taken
The above error is because of the uniqueness constraint on Country model for name and slug.
Issue #2: Validation failed: Country can't be blank
When you do association :country, factory: :country, strategy: :build then strategy: :build only creates an instance of Country, it does create a record in database.
The Country can't be blank error is because you didn't create a country record in the database for user and offer. And you must be having a validation presence: true in these two models for country OR schema level check of not null.

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)

Resources