Factorybot, create records common polymorphic relation - ruby-on-rails

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.

Related

How can I access to a child class instead of parent class in a 'callback' function of factorybot in ruby on rails?

I have implemented a STI in ROR. please look at the following code:
class Category < ApplicationRecord
end
class CourseCategory < Category
has_many :courses
end
I use FactoryBot to create data like this:
# category
FactoryBot.define do
factory :category do
name {"Ruby on Rails"}
end
end
# course_category
FactoryBot.define do
factory :course_category, parent: :category, class: 'Category' do
trait :with_course do
after(:create) do |course_category| # my problem is here
create :course, :with_steps, course_category: course_category
end
end
end
# course
FactoryBot.define do
factory :course do
course_category
end
end
while I run FactoryBot.create(:course_category, :with_course), into trait :with_course , I should have CourseCategory class, but I receive Category.
Can I have access to parent class instead of child class into a callback of FactoryBot?
Your factory is building :course_category as the wrong class.
This line needs to be changed...
factory :course_category, parent: :category, class: 'Category' do
Into...
factory :course_category, parent: :category, class: 'CourseCategory' do

FactoryGirl - override association with trait?

Let's say I have the following ActiveRecord models:
class Car
belongs_to :driver
end
class Driver
# Has attribute :name
has_one :car
end
And I define a couple of factories using these models:
FactoryGirl.define do
factory :car do
association :driver
trait :fast_car do
association :driver, :fast
end
end
end
FactoryGirl.define do
factory :driver do
name 'Jason'
trait :fast do
name 'Mario'
end
end
end
When I execute the following code:
car = FactoryGirl.create(:car, :fast_car)
I would expect car.driver.name to equal Mario, but instead it equals Jason. This leads me to believe that you can't use traits to override associations for factories. Is this true? If so, what would be the proper way to override the associated Driver for a fast car?
Fortunately, you can. You need to specify the factory keyword for an association with an array, where the first element is the name of the factory that you want to use for the association and the rest elements are the factory's traits:
FactoryGirl.define do
factory :car do
association :driver
trait :fast_car do
association :driver, factory: [:driver, :fast]
end
end
end
FactoryGirl.define do
factory :driver do
name 'Jason'
trait :fast do
name 'Mario'
end
end
end

How to set foreign key ID in factory girl factory?

Here are my models:
class Section < ActiveRecord::Base
belongs_to :organization
end
class Organization < ActiveRecord::Base
has_many :sections
end
In my Loan factory, I would like to automatically create an organization and set it for it. How can I accomplish this?
FactoryGirl.define do
factory :section do
organization_id???
title { Faker::Lorem.words(4).join(" ").titleize }
subtitle { Faker::Lorem.sentence }
overview { Faker::Lorem.paragraphs(5).join("\n") }
end
end
It's possible to set up associations within factories. You need first to have a factory for your organization :
FactoryGirl.define do
factory :section do
...
end
end
Then you can just call organization, FactoryGirl will take care on generating your organization
FactoryGirl.define do
factory :section do
organization
title { Faker::Lorem.words(4).join(" ").titleize }
subtitle { Faker::Lorem.sentence }
overview { Faker::Lorem.paragraphs(5).join("\n") }
end
end
if you want to know more you can go here : http://rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md

FactoryGirl association with reference

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

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