FactoryGirl association with reference - ruby-on-rails

I have four models: User, Product, Ownership and Location. Userand Product have one Location, and Location belongs to User and to Product (Polymorphic model).
I want to use FactoryGirl to create products that have the same location as their owner.
factory :location do
sequence(:address) { |n| "#{n}, street, city" }
end
factory :user do
sequence(:name) { |n| "Robot #{n}" }
sequence(:email) { |n| "numero#{n}#robots.com"}
association :location, factory: :location
end
factory :product do
sequence(:name) { |n| "Objet #{n}" }
association :location, factory: :location
end
factory :ownership do
association :user, factory: :user
association :product, factory: :product
end
I created a method in the product model file to retrieve the product's owner just by doing product.owner.
I want to adapt the product factory in order to replace the factoried location by product.owner.location. How can I do that?
EDIT 1
I want to use it like that:
First I create a user
FactoryGirl.create(:user)
Later I create a product
FactoryGirl.create(:product)
When I associate both of them
FactoryGirl.create(:current_ownership, product: product, user: user)
I want that the location of my product becomes the one of his owner.

use the following code.
factory :user do
sequence(:name) { |n| "Robot #{n}" }
sequence(:email) { |n| "numero#{n}#robots.com"}
association :location, factory: :location
factory :user_with_product do
after(:create) do |user|
create(:product, location: user.location)
end
end
end
To create the records, just use the user_with_product factory.
UPDATE:
In response to your question update, you can add an after(:create) callback to the ownership factory
factory :ownership do
association :user, factory: :user
association :product, factory: :product
after(:create) do |ownership|
# update ownership.user.location here with ownership.user.product
end
end
The problem with this is your current association setup. Since location belongs to user or product, the foreign key is in location. so a location can't both belong to a user and a product at the same time.

using an after_create callback should do the trick
factory :ownership do
user # BONUS - as association and factory have the same name, save typing =)
product
after(:create) { |ownership| ownership.product.location = ownership.user.location }
end

Related

Factorybot, create records common polymorphic relation

How do I create(:listing, :for_car) and create(:firm, :for_car), but allow them to have the same common car record?
Ideally, I would like to do create(:listing) and have it create, User, and then Car (belongs_to User), then Listing (belongs_to Car), and then Firm (belongs_to Car). In this instance, Car is the same exact record, not two different Car records.
So I don't have to have multiple create methods called. How can I accomplish this?
FactoryBot.define do
factory :user, class: "User" do
email { Faker::Internet.email }
password { "bla" }
password_confirmation { "bla" }
end
factory :car, class: "Car" do
color { "black" }
association :user, factory: :user
end
factory :truck, class: "Truck" do
size { "15" }
association :user, factory: :user
end
factory :bike, class: "Bike" do
style { "road" }
association :user, factory: :user
end
factory :listing, class: "Listing" do
for_car # default
trait :for_car do
association :listable, factory: :car
end
trait :for_truck do
association :listable, factory: :car
end
trait :for_bike do
association :listable, factory: :car
end
end
factory :firm, class: "Firm" do
for_car # default
trait :for_car do
association :listable, factory: :car
end
trait :for_truck do
association :listable, factory: :car
end
trait :for_bike do
association :listable, factory: :car
end
end
factory :store, class: "Store" do
for_car # default
trait :for_car do
association :listable, factory: :car
end
trait :for_truck do
association :listable, factory: :car
end
trait :for_bike do
association :listable, factory: :car
end
end
end
You might go about it like:
car_factory.rb
FactoryBot.define do
factory :car, class: Car do
color { "black" }
user
end
end
(note you shouldn't need to use that verbose syntax)
listing_factory.rb
FactoryBot.define do
factory :listing, class: "Listing" do
car
end
end
This is assuming listing has a field called car. FactoryBot can find the proper factory for you in this scenario.
Then in your spec you can just override defaults like so:
let(:car) { create(:car) }
let(:truck) { create(:truck) }
let(:car_listing) { create(:listing) }
let(:truck_listing) { create(:listing, car: truck) }
Any arguments you pass after the name of the factory in the create method get passed in to overwrite any defaults inside the factory.

FactoryBot generate content for join table

I'm trying to use factory bot to generate test data for RSpec. My tables are as follows:
User -> can be pro_team_player or noob_team_player and there is a model conversation such that:
class Conversation < ActiveRecord::Base
has_many :messages
belongs_to :pro_team_player
belongs_to :noob_team_player
end
So every conversation belongs to a pro_team_player and a noob_team_player and every conversation has many messages associated with it.
Now I have the factory for user, pro_team_player, noob_team_player and messages as:
FactoryBot.define do
factory :user do
sequence(:name) { |n| "Cool Player#{n}" }
sequence(:email) { |n| "user#{n}#email.com" }
end
end
FactoryBot.define do
factory :pro_team_player do
player_type 'some type'
user
end
end
FactoryBot.define do
factory :noob_team_player do
player_type 'some type'
user
end
end
FactoryBot.define do
factory :messages do
content 'Hola! This is a message'
end
end
Now I can generate the above data as:
user1 = FactoryBot.create(:user)
pro_team_player = build(:pro_team_player)
user1 = pro_team_player.user1
user2 = FactoryBot.create(:user)
noob_team_player = build(:pro_team_player)
user2 = noob_team_player.user2
I'm still learning FactoryBot and I've no idea how to create the conversation factory or generate the data for that. Any help would be appreciated
You are on the right track. Keep following the pattern you are using:
FactoryBot.define do
factory :conversation do
pro_team_player
noob_team_player
# Other required conversation attributes, if any
end
end
FactoryBot.define do
factory :message do
conversation
# Other required message attributes, if any
end
end

FactoryGirl - How to dynamically generate, or override a callback

I have an application with archive functionality, allowing users to restore objects after they are deleted.
An Organisation has_many Contacts, and must have at least one.
A Contact must belong_to a single Organisation
Both objects can be archived, however an Organisation is only archived when all of its associated Contacts are archived.
Currently I have the following. The problem with this approach is that FactoryGirl.create(:archived_organisation) appears to trigger both of the after(:create) callbacks in the :organisation factory:
factory :contact do
name { Faker::Name.first_name }
association :organisation, :factory => :organisation, :contacts_count => 0
factory :archived_contact do
archived true
association :organisation, :factory => :archived_organisation, :contacts_count => 0
end
end
factory :organisation do
name { Faker::Company.name }
ignore do
contacts_count 2
end
after(:create) do |instance, evaluator|
FactoryGirl.create_list(:contact, evaluator.contacts_count, organisation: instance)
end
factory :archived_organisation do
archived true
after(:create) do |instance, evaluator|
FactoryGirl.create_list(:archived_contact, evaluator.contacts_count, organisation: instance)
end
end
end
How should this be achieved? Is it possible to override the :organisation after(:create) callback in the :archived_organisation factory? If not, is it possible to dynamically generate the callback based on which factory is being created?
Hopefully I'm missing something obvious, but I'm welcome to suggestions that I should be going about this a different way.
How about:
after(:create) do |instance, evaluator|
unless instance.archived
FactoryGirl.create_list(:contact, evaluator.contacts_count, organisation: instance)
end
end

Factory Girl and nested attributes validation error

I have a model Company which accepts nested attributes for Recruiters model. I need to have validation in my Company model that at least one recruiter was created during Company creation.
class Company < ActiveRecord::Base
has_many :recruiters, dependent: :destroy, inverse_of: :company
accepts_nested_attributes_for :recruiters, reject_if: ->(attributes) { attributes[:name].blank? || attributes[:email].blank? }, allow_destroy: true
validate { check_recruiters_number } # this validates recruiters number
private
def recruiters_count_valid?
recruiters.reject(&:marked_for_destruction?).count >= RECRUITERS_COUNT_MIN
end
def check_recruiters_number
unless recruiters_count_valid?
errors.add(:base, :recruiters_too_short, count: RECRUITERS_COUNT_MIN)
end
end
end
Validation works as expected but after adding this validation I have a problem with FactoryGirl. My factory for company looks like this:
FactoryGirl.define do
factory :company do
association :industry
association :user
available_disclosures 15
city 'Berlin'
country 'Germany'
ignore do
recruiters_count 2
end
after(:build) do |company, evaluator|
FactoryGirl.create_list(:recruiter, evaluator.recruiters_count, company: company)
end
before(:create) do |company, evaluator|
FactoryGirl.create_list(:recruiter, evaluator.recruiters_count, company: company)
end
end
end
In tests, when I do
company = create(:company)
I get validation error:
ActiveRecord::RecordInvalid:
Validation failed: Company has to have at least one recruiter
When I first build company and then save it, the test passes:
company = build(:company)
company = save
Of course, I don't want to change all my tests this way to make them work. How can I set up my factory to create associated model during creation Company model?
Your validate { check_recruiters_number } is unreasonable. Remove it.
Why? You need to have a valid company id to save recruiters, but your validator prevent company to be valid because it has no recruiters. This is in contradiction.
This is an old question but I have similar problem and I solved it with the following code (rewritten to match your case):
FactoryGirl.define do
factory :company do
association :industry
association :user
available_disclosures 15
city 'Berlin'
country 'Germany'
ignore do
recruiters_count 2
end
after(:build) do |company, evaluator|
company.recruiters = FactoryGirl.build_list(:recruiter, evaluator.recruiters_count, company: company)
end
end
end

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