Reputation: 5178
I have a model contact
that has_many :locations, through: :relationships
, as well as has_many :teams, through: :contacts_teams
.
A contact must have an associated team
and location
in order to pass validations. In other words: a new contact
must have an associated relationship
record and an associated contacts_team
record. Below are the models:
#models/contact.rb
class Contact < ActiveRecord::Base
has_many :contacts_teams
has_many :teams, through: :contacts
has_many :relationships, dependent: :destroy
has_many :locations, through: :relationships
accepts_nested_attributes_for :contacts_teams, allow_destroy: true
# upon create, validate that at least one associated team and one associated location exist
validate :at_least_one_contacts_team
validate :at_least_one_relationship
private
def at_least_one_contacts_team
return errors.add :base, "Must have at least one Team" unless contacts_teams.length > 0
end
def at_least_one_relationship
return errors.add :base, "Must have at least one Location" unless relationships.length > 0
end
end
#models/contacts_team.rb
class ContactsTeam < ActiveRecord::Base
belongs_to :contact
belongs_to :team
end
#models/team.rb
class Team < ActiveRecord::Base
has_many :contacts_teams
has_many :contacts, through: :contacts_teams
end
#models/relationship.rb
class Relationship < ActiveRecord::Base
belongs_to :contact
belongs_to :location
end
#models/location.rb
class Location < ActiveRecord::Base
has_many :relationships
has_many :contacts, through: :relationships
end
For testing: with factory_girl I want to create a contact
factory that is able to successfully create a contact
record. Since each contact
record requires an associated contacts_team
record and relationship
record: when I create the contact
record is should create those as well. Likewise: the contacts_team
record should have an existing team
it is associated to, and the relation
record should have an existing location
it is associated to. So essentially it should create a location
and a team
record as well.
How can I create a contact record with a factory, which in effect creates an associated contacts_team
record and a relationship
record?
Here are my current factories:
FactoryGirl.define do
factory :contact do
first_name "Homer"
last_name "Simpson"
title "Nuclear Saftey Inspector"
end
end
FactoryGirl.define do
factory :contacts_team do
end
end
FactoryGirl.define do
factory :team do
name "Safety Inspection Team"
end
end
FactoryGirl.define do
factory :relationship do
end
end
FactoryGirl.define do
factory :location do
name "Safety Location"
end
end
If it is difficult/not possible to do this with factory_girl: how can I do it with straight rspec? The issue is that I can't create a contacts_team
record or a relationship
record, because the contact
it is associated to doesn't exist yet! And I can't create a contact
record because an associated contacts_team
record or a relationship
record doesn't exist yet. It seems like I'm trapped, but there has to be a way to do this that is not sloppy.
Upvotes: 2
Views: 2633
Reputation: 5178
Here is the answer. We need to build the associated records (the contacts_team
record and the relationship
record) and then we save all records at the exact same time to the database (just like how nested attributes get saved by rails):
#factories/contact.rb
FactoryGirl.define do
factory :contact do
first_name "Homer"
last_name "Simpson"
title "Nuclear Saftey Inspector"
agency
contacts_teams {build_list :contacts_team, 1 }
relationships {build_list :relationship, 1 }
end
end
#factories/contacts_teams.rb
FactoryGirl.define do
factory :contacts_team do
team
end
end
#factories/teams.rb
FactoryGirl.define do
factory :team do
name "Safety Inspection Team"
end
end
#factories/relationships.rb
FactoryGirl.define do
factory :relationship do
location
end
end
#factories/locations.rb
FactoryGirl.define do
factory :location do
name "Safety Location"
end
end
Then all you need to do is this:
create(:contact)
And with that it all at once creates a contact
record, a team
record, a location
record, the associated contacts_team
record, and the associated relationship
record.
Upvotes: 1
Reputation: 4825
I just had a similar requirement last week.
At the end of your factory, you can call the next factory, and they will then be related. For example:
/spec/factories/contacts.rb
FactoryGirl.define do
factory :contact do |c|
first_name "Homer"
last_name "Simpson"
title "Nuclear Saftey Inspector"
# now, call the other two factories
relationship
contacts_team
end
factory :contacts_team do
# call the team factory
team
end
factory :relationship do
# call the location factory
location
end
# define the team and location factories...
end
Now, in /spec/controllers/contacts_controller_spec.rb
contact = FactoryGirl.create(:contact)
You can just use factory girl to create a contact, even if you just need, for example, a location, because everything will be generated at once.
ALTERNATIVE (rspec)
don't "chain" your factories, instead in /spec/controllers/contacts_controller_spec.rb
contact = FactoryGirl.create(:contact)
# use .create_list(model, number, parent) to make children of a specific parent
contacts_team = FactoryGirl.create_list(:contacts_team, 3, :contact => contact)
relationship = FactoryGirl.create_list(:relationship, 3, :contact => contact)
team = FactoryGirl.create_list(:team, 3, :contacts_team => contacts_team)
location = FactoryGirl.create_list(:location, 3, :relationship => relationship)
This will create a contact, with 3 contact_teams (with 3 teams), and with 3 relationships (with 3 locations)
Hope this helps you figure out the correct pattern to make your test data :)
Upvotes: 1
Reputation: 5633
Basically, you can do it multiple ways. But one way that's often been advised is to use the FactoryGirl after(:create)
or after(:build)
. In your case if you have the factory for all your models, you can easily call:
after(:create) do |team, evaluator|
create_list(:contact_team, 5, team: team)
end
Based on the documentation, after(:create) yields the team and the evaluator, which stores all values from the factory, including transient attributes
This would create 5 contact_teams associated with the team for you. For each factory if you define all the relationships like this, you would have your full associations defined. The alternative would be to follow Noam's suggestion, which is also good, but has its own limits. You can find more information here: FactoryGirl Associations
Upvotes: 0