FactoryGirl: how to handle belongs_to presence validations behind the scenes? - ruby-on-rails

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

Related

How to manually add a column referencing a model in FactoryGirl?

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

FactoryGirl – Retrieving attributes from another factory in a polymorphic association

I have a Board that belongs to an Artist. So far I was able to setup this polymorphic association in my boards factory as so:
FactoryGirl.define do
factory :board do
association :boardable, factory: :artist
boardable_type "Artist"
end
end
The pattern I have setup in my actual app requires the name of my board to be the name of the artist it belongs to. I tried doing something like:
name boardable.name
But ended up getting this error:
ArgumentError: Trait not registered: boardable
What is usually the best way to retrieve attributes within a belongs_to/polymorphic association?
Polymorphic associations do not need an explicit association to be declared in FactoryGirl. The following has been verified and will work:
FactoryGirl.define do
factory :board do
boardable factory: :artist
name { boardable.name }
end
end
As far as your Board's name is concerned, make sure to wrap the attribute value in brackets, or FactoryGirl might treat it as a trait :) The boardable_type attribute of your Board will be automatically set to the boardable's class, so it doesn't even need declaring.
You can use a before :create. Something like this
FactoryGirl.define do
factory :board do
...
before(:create) do |board, evaluator|
# create artist factory here to associate board to
# example below that isn't polymorphic but you get the idea
FactoryGirl.create(:artist, name: "foo").boards << board
end
end
end

Factory Girl with polymorphic association for has_many and has_one

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.

Building in model callback returning nil for value

First, thanks for taking the time to read. I'm new to Rails and have been stuck on this one for many hours.
In my Rails 3.2 app, I have three models: User, Organization, and Membership (the last is a join model between User and Organization).
When a user creates an organization, he/she should become a member upon create. So, in my Organization model, I've included a before_create callback that builds a Membership. The problem is that while the Membership builds when the new Organization is created, the user_id on the Membership object is set to "nil.," and therefore the current user is not a member.
Hardcoding in the user_id attribute in the callback actually does correctly build the membership, i.e. (:user_id => "1"), but in general asking the Organization model to be aware of current user state seems like bad MVC practice.
What's the proper way to set the current user ID on the new Membership? It seems like my associations should handle that, but I might be wrong.
Here are my models — I'm leaving out some validation lines for readability's sake. Thanks so much in advance.
user.rb
class User < ActiveRecord::Base
has_many :memberships
has_many :organizations, :through => :memberships
end
membership.rb
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :organization
end
organization.rb
class Organization < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
accepts_nested_attributes_for :memberships, :allow_destroy => true
...
before_create :add_membership
protected
def add_membership
self.memberships.build
end
end
You are right in the fact that allowing your model to magically know about the current user is bad MVC practice. So you have to somehow pass the current user id during creation. you can do this in many ways ; for example in the controller :
def create
#organization = Organization.new( params[:organization] ) do |org|
org.memberships.build( user_id: current_user.id )
end
# save, etc.
end
Doing this in the controller is fine, but it would be better if your business logic would reflect the fact that a user creating an organization should automatically belong to it. You could override new and / or create on Organization (or create your own method if you fear overrides) :
def new( params = {}, options = {} )
creator = options.delete( :creator )
super( params, options ) do |org|
org.memberships.build( user_id: creator.id ) if creator
yield org if block_given?
end
end
passing the user is easy now :
def create
#organization = Organization.new(params[:organization], creator: current_user)
end
If you don't like this approach, or if you don't want to override new or create a specific factory method, you can also make something similar to nested_attributes :
attr_accessible :creator_id
def creator_id=( user_id )
memberships.build user_id: user_id
end
then in your view :
f.hidden_field :creator_id, current_user.id
optional :
with first approach, for additional clarity / ease of use, you can also create a method on User :
def new_organization( params = {}, options = {}, &block )
Organization.new( params, options.merge(creator: self), &block )
end
... ok, Organization is hardcoded here (bad !) but your workflow is now quite understandable :
def create
# we know at first glance that the user is responsible for the organization
# creation, and that there must be specific logic associated to this
#organization = current_user.new_organization( params[:organization] )
# etc
end
with a little more thinking, it should be possible to avoid hardcoding Organization into User (using an association extension for instance)
EDIT
To be able to setup a validation on membership's organization presence, you need to do this :
class Organization < ActiveRecord::Base
has_many :memberships, inverse_of: :organization
end
class Membership < ActiveRecord::Base
belongs_to :organization, inverse_of: :memberships
validates :organization, presence: true
end
Let's explain this :
inverse_of sets up your associations to be bidirectional. By default, associations are one-way, which means that when you do organization.memberships.first.organization, rails tries to load the organisation again because it does not know how to "climb back" the association. When using inverse_of, rails knows it does not have to reload the organization.
validates MUST be setup on organization and NOT on organization_id. This way the validator knows we're "climbing back" the association, it knows that organization is a "parent" record and that it's in the process of being saved - so it does not complain.

FactoryGirl has_many association with validation

I have a standard has_many relationship (Booking has many Orders) with validation that a Booking does not get saved without at least one Order. I'm trying to replicate this with my FactoryGirl factories but the validation is preventing me from doing so.
class Booking < ActiveRecord::Base
has_many :orders
validates :orders, presence: true
end
class Order < ActiveRecord::Base
belongs_to :booking
end
Here are my FactoyGirl factory specifications for each model as followed from FactoryGirl's GitHub wiki page.
FactoryGirl.define do
factory :booking do
factory :booking_with_orders do
ignore do
orders_count 1
end
before(:create) do |booking, evaluator|
FactoryGirl.create_list(:order, evaluator.orders_count, booking: booking)
end
end
end
factory :order do
booking
end
end
When I try to run FactoryGirl.create(:booking_with_orders) from my spec, I get:
Failure/Error: #booking = FactoryGirl.create(:booking_with_orders)
ActiveRecord::RecordInvalid:
Validation failed: Orders can't be blank
It seems like the check for the validation is running even before before(:create) [...] which would theoretically create the Orders for the Booking.
This post recommends not adding has_many relationships to your factories but I would like to solve this anyways if there is a good way to do it.
Thanks in advance.
Wat? Impossible? Not at all.
Just change your code to something like this:
after :build do |booking, evaluator|
booking.orders << FactoryGirl.build_list(:order, evaluator.orders_count, booking: nil)
end
Taking off from #jassa's answer, if you just need to add a single (required) associated record with a specific attribute, this pattern worked for me:
factory :booking do
ignore do
order_name "name"
end
after :build do |factory, evaluator|
factory.orders << FactoryGirl.build(:order, name: evaluator.order_name, booking: nil)
end
end
This seems like an overly simplistic observation but what you're trying to do is in effect make sure that the Order exists before the Booking, which is impossible, as the Order cannot exist without its booking_id (meaning that the Booking needs to be created first).
There's nothing wrong with a has_many relationship in your factories, it's your validation that is a problem. Does this currently work in your application? How do you save your records in that case? What is the flow for creating Orders and Bookings?
Even the infamous accepts_nested_attributes_for won't help you here.
My advice is to rethink your record saving and validation strategy so that it is a little more sane.

Resources