I had a factory like the following:
FactoryGirl.define do
factory :product do
name 'John'
end
end
I have a lot of specs using this factory like this:
product = create :product
I now added a Product belongs_to :user association and validate it on presence:
class Product < ApplicationRecord
validates :user, presence: true
end
I would have to refactor all my factory calls like this now (or something similar):
product = create :product, user: create(:user)
This seems like a hassle to me. I'd like FactoryGirl to do this for me automatically. It should be something like this:
Whenever creating a product, FactoryGirl automatically adds the user:
If there's already a user in the DB, it assigns this user
If there's no user in the DB, one is created
Is there a good way to do this? I want to avoid querying the DB for the existence of a user whenever a product is created.
Also, it could be dangerous, as an existing user could be destroyed during a test and then would be missing to any subsequent created product.
Are there better patterns for this?
I've seen stuff like validating the ID instead of the object:
validates :user_id, presence: true
And then simply assigning a non-existent ID when creating the object:
product = create :product, user_id: 123456
Is this a good pattern?
All my deeply nested/associated resources should have a belongs_to :user association soon. I want to avoid having to write user: create(:user) (or user: #user) everywhere in my specs.
I'm sure there is a well-proven pattern for this...?
It's possible to set up associations within factories. If the factory
name is the same as the association name, the factory name can be left
out.
http://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md#Associations
FactoryGirl.define do
factory :product do
name 'John'
user
end
end
I have the current modeling:
A House belongs_to User
A User has_one House
Which means that the House model has a reference to User. My Factory for User, looks like this:
FactoryGirl.define do
factory :user do
house
end
end
What this does is basically creating a User and a House that references that User.
Now, I have introduced a column in User called house_id and I want to be able to allow the Factory to work as it is, but also, fill the house_id with the House that was created. The reason why I am doing this is because I want to change the reference direction incrementally.
I have done it like this:
FactoryGirl.define do
factory :user do
house
house_id { House.find_by(user_id: id).id }
end
end
But I suspect there might be a better way to do this. Any thoughts?
First of all you have the wrong idea of how has_one and belongs_to relations work.
belongs_to sets up a 1-1 or 1-to-many relationship and places the foreign key column on this models table. In the case its houses.user_id which references users.id:
class User < ActiveRecord::Base
has_one :house
end
class House < ActiveRecord::Base
belongs_to :user
end
Secondly specs should not hinge on records being created sequentially. It will create brittle specs and ordering issues. Instead let factory girl do its job in creating objects for you.
FactoryGirl.define do
factory :user do
house
end
end
FactoryGirl.define do
factory :house do
user
end
end
And don't assume that records have a given id:
# bad
get :show, id: 1
# good
get :show, id: user.to_param
If you need to setup a record which is explicitly associated somewhere you can just pass a user or house option:
let(:user) { create(:user) }
let(:house) { create(:house, user: user) }
it 'should be associated with the correct user' do
expect(house.user).to eq user
end
I am currently working on a project and I wanted to create tests using factory girl but I'm unable to make it work with polymorphic has_many association. I've tried many different possibilities mentioned in other articles but it still doesn't work. My model looks like this:
class Restaurant < ActiveRecord::Base
has_one :address, as: :addressable, dependent: :destroy
has_many :contacts, as: :contactable, dependent: :destroy
accepts_nested_attributes_for :contacts, allow_destroy: true
accepts_nested_attributes_for :address, allow_destroy: true
validates :name, presence: true
#validates :address, presence: true
#validates :contacts, presence: true
end
class Address < ActiveRecord::Base
belongs_to :addressable, polymorphic: true
# other unimportant validations, address is created valid, the problem is not here
end
class Contact < ActiveRecord::Base
belongs_to :contactable, polymorphic: true
# validations ommitted, contacts are created valid
end
So bassically I want to create factory for Restaurant with address and contacts (with validations on Restaurant for presence, but if it's not possible, even without them) but I'm unable to do so. Final syntax should be like:
let(:restaurant) { FactoryGirl.create(:restaurant) }
Which should also create associated address and contacts. I have read many articles but I always get some sort of error. Currently my factories (sequences are defined correctly) are like this:
factory :restaurant do
name
# address {FactoryGirl.create(:address, addressable: aaa)}
# contacts {FactoryGirl.create_list(:contact,4, contactable: aaa)}
# Validations are off, so this callback is possible
after(:create) do |rest|
# Rest has ID here
rest.address = create(:restaurant_address, addressable: rest)
rest.contacts = create_list(:contact,4, contactable: rest)
end
end
factory :restaurant_address, class: Address do
# other attributes filled from sequences...
association :addressable, factory: :restaurant
# addressable factory: restaurant
# association(:restaurant)
end
factory :contact do
contact_type
value
association :contactable, :factory => :restaurant
end
Is there a way to create restaurant with one command in test with addresses and contacts set? Do I really need to get rid off my validations because of after(:create) callback?
Current state is as fllowing:
Restaurant is created with name and id.
Than the address is being created - all is correcct, it has all the values including addressable_id and addressable_type
After that all contacts are being creaed, again everything is fine, cntacts has the right values.
After that, restaurant doesn't have any ids from associated objects, no association to adddress or contacts
After than, for some reason restaurant is build again (maybe to add those associations?) and it fails: I get ActiveRecord:RecordInvalid.
I'm using factory_girl_rails 4.3.0, ruby 1.9.3 and rails 4.0.1. I will be glad for any help.
UPDATE 1:
Just for clarification of my goal, I want to be able to create restaurant in my spec using one command and be able to access associated address and contacts, which should be created upon creation of restaurant. Let's ignore all validations (I had them commented out in my example from the beginning). When I use after(:build), first restaurant is created, than address is created with restaurant's ID as addressable_id and class name as addressable_type. Same goes to contacts, all are correct. The problem is, that restaurant doesn't know about them (it has no IDs of address or contacts), I can't access them from restaurant which I want to.
After really thorough search I have found an answer here - stackoverflow question.This answer also point to this gist. The main thing is to build associations in after(:build) callback and then save them in after(:create) callback. So it looks like this:
factory :restaurant do
name
trait :confirmed do
state 1
end
after(:build) do |restaurant|
restaurant.address = build(:restaurant_address, addressable: restaurant)
restaurant.contacts = build_list(:contact,4, contactable: restaurant)
end
after(:create) do |restaurant|
restaurant.contacts.each { |contact| contact.save! }
restaurant.address.save!
end
end
I had also a bug in my rspec, because I was using before(:each) callback instead of before(:all). I hope that this solution helps someone.
The Problem
Validating the length of a related list of rows is a difficult problem to frame in SQL, so it's a difficult problem to frame in ActiveRecord as well.
If you're storing a restaurant foreign key on the addresses table, you can't ever actually create a restaurant that has addresses by the time it's saved, because you need to save the restaurant to get its primary key. You can get around this problem in ActiveRecord by building up the associated objects in memory, validating against those, and then committing the entire object graph in one SQL transaction.
How to do what you're asking
You can generally get around this by moving things into an after(:build) hook instead of after(:create). ActiveRecord will save its dependent has_one and has_many associations once it saves itself.
You're getting errors now because you can't modify an object to satisfy validations in an after(:create) block, because validations have already run by the time the callback runs.
You can change your restaurant factory to look something like this:
factory :restaurant do
name
after(:build) do |restaurant|
restaurant.address = build(:restaurant_address, addressable: nil)
restaurant.contacts = build(:contact, 4, contactable: nil)
end
end
The nils there are to break the cyclic relationship between the factories. If you do it this way, you can't have a validation on the addressable_id or contactable_id keys, because they won't be available until the restaurant is saved.
Alternatives
Although you can get both ActiveRecord and FactoryGirl to do what you're asking, it sets up a precarious list of dependencies which are difficult to understand and are likely to result in leaky validations or unexpected errors like the ones you're seeing now.
If you're validating contacts this way from the restaurant model because of a form in which you create both a restaurant and its corresponding contacts, you can save yourself a lot of pain by creating a new ActiveModel object to represent that form. You can collect the attributes you need for each object there, move some of the validations (especially the ones which validate the length of the contacts list), and then create the object graph on that form in a way that's much clearer and less likely to break.
This has the added benefit of making it easy to create lightweight restaurant objects in other tests which don't need to worry about contacts or addresses. If you force your factories to create these dependent objects every time, you'll quickly run into two problems:
Your tests will be painfully slow. Creating five dependent records every time you want to work with a restaurant won't scale very far.
If you ever want to specify different contacts or addresses in your tests, you'll constantly be fighting with your factories.
I need some help getting my factory_girl settings correct on this has one through many with nested attributes. Here's the three models for reference.
location.rb
class Location < ActiveRecord::Base
has_many :person_locations
has_many :people, through: :person_locations
end
person_location.rb
class PersonLocation < ActiveRecord::Base
belongs_to :person
belongs_to :location
accepts_nested_attributes_for :location, reject_if: :all_blank
end
person.rb
class Person < ActiveRecord::Base
has_many :person_locations
has_many :locations, through: :person_locations
accepts_nested_attributes_for :person_locations, reject_if: :all_blank
end
Notice that locations is nested under the person record, but it needs to go through two models to be nested. I can get the tests working like this:
it "creates the objects and can be called via rails syntax" do
Location.all.count.should == 0
#person = FactoryGirl.create(:person)
#location = FactoryGirl.create(:location)
#person_location = FactoryGirl.create(:person_location, person: #person, location: #location)
#person.locations.count.should == 1
#location.people.count.should == 1
Location.all.count.should == 1
end
I should be able to create all three of these records within one line but haven't figured out how to yet. Here's the structure I would like to have work correctly :
factory :person do
...
trait :location_1 do
person_locations_attributes { location_attributes { FactoryGirl.attributes_for(:location, :location_1) } }
end
end
I have other models which are able to create via a similar syntax, but it has only one nested attribute versus this deeper nesting.
As entered above, I get the following error:
FactoryGirl.create(:person, :location_1)
undefined method `location_attributes' for #<FactoryGirl::SyntaxRunner:0x007fd65102a380>
Furthermore, I want to be able to test properly my controller setup for creating a new user with nested location. It will be tough to do this if I can't get the call down to one line.
Thanks for your help!! Hopefully I provided enough above to help others as well when they are creating a has many through relationship with nested attributes.
A couple of days later I figured it out after reading blog 1 and blog 2. In the process of refactoring all of my FactoryGirl code now.
FactoryGirl should look as follows:
factory :person do
...
trait :location_1 do
after(:create) do |person, evaluator|
create(:person_location, :location_1, person: person)
end
end
end
The person_location factory should be pretty straight forward then following the above code. You can either do the location_attributes which is in the original question or create a similar block to this answer to handle it there.
I have two models here, courses and studyunits, where a studyunit has content for a course.
Here are the two models:
class Studyunit < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :courses
class Course < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :studyunits
The issue is that adding studyunits to courses seems to update the course. studyunits attribute, but not studyunit.courses. Excerpt of the rspec code:
before(:each) do
course.studyunits << studyunit
Studyunit.connection.clear_query_cache
end
it "should be associated with a course" do
course.studyunits.first.should_not eql(nil)
studyunit.courses.first.should_not eql(nil)
end
The first condition passes, the second fails. How to solve this? I need to access both sides in my code. I tried clearing the query cache as per this thread, but it didn't solve the issue.
Are you sure this doesn't just have to do with caching? I suspect it does. Try this code instead:
it "should be associated with a course" do
course.studyunits(true).first.should_not eql(nil)
studyunit.courses(true).first.should_not eql(nil)
end