FactoryBot generate content for join table - ruby-on-rails

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

Related

How to specify the association with factory_bot?

For example I have two models a user and a post. A post belongs_to a user and a user has many posts
#spec/factories/post.rb
FactoryBot.define do
factory :post do
user
body Faker::Movie.quote
posted_at "2018-04-03 13:33:05"
end
end
#spec/factories/user.rb
FactoryBot.define do
factory :user do
first_name 'Jake'
end
end
Using Rspec in a test I want to do this:
user = create(:user, first_name: 'Barry') #id 1
post = create(:post, user: user)
I would expect that the user_id of post to be 1 however it is creating another user prior and the user_id is 2.
How can you specify the association when you are creating the object with factory_bot / factory_girl?
You should use explicit associations instead of implicit ones:
#spec/factories/post.rb
FactoryBot.define do
factory :post do
association :user # <<<--- here the change
body Faker::Movie.quote
posted_at "2018-04-03 13:33:05"
end
end
#spec/factories/user.rb
FactoryBot.define do
factory :user do
first_name 'Jake'
end
end
https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#associations
Another option is to use #trait method within the parent.
FactoryBot.define do
factory :post do
user nil
body Faker::Movie.quote
posted_at "2018-04-03 13:33:05"
end
end
FactoryBot.define do
factory :user do
first_name 'Jake'
end
trait :with_post do
after(:create) do |user|
create(:post, user_id: user.id)
end
end
end
FactoryBot.create(:user, :with_post)
Here we have another solution in case your association name and factory name is different then you can follow below syntax.
#spec/factories/post.rb
FactoryBot.define do
factory :post do
association :author, factory: :user
body Faker::Movie.quote
posted_at "2018-04-03 13:33:05"
end
end
in case your factory name is author and model name is user then you can use above syntax

FactoryGirl : 'Factory not registered' error when added a factory to a has_many relation

I've got 2 factories files
spec/factories/cars.rb
spec/factories/users.rb
A user can have many cars and I would like to create a special trait for this case.
Into my car factory :
FactoryGirl.define do
factory :car do
...
trait :is_blue do
color 'blue'
end
end
factory :blue_car, parent: :car do
is_blue
end
end
My user factory
FactoryGirl.define do
factory :user do
...
trait :with_cars do
cars [ FactoryGirl.create(:blue_car) ]
end
end
factory :user_with_cars, parent: :user do
with_cars
end
end
When I want to use the 'user_with_cars' factory into my 'user_spec' file I've got a 'Factory not registered: blue_car' error
Example :
context 'with cars' do
subject { create(:user_with_cars) }
it 'should make some stuff' do
expect(subject.cars).not_to be_empty
...
end
end
I found a solution.
I change the way I create relations.
In the trait wich add relations I use the 'after(:create)' callback.
spec/factories/users.rb
FactoryGirl.define do
factory :user do
...
trait :with_cars do
after(:create) do |user|
user.cars << create(:blue_car)
end
end
factory :user_with_cars, parent: :user do
with_cars
end
end

How to call nested factory_girl attribute

I am using rspec and factory girl and am having a weird problem getting a sub class from a factory. I am creating a designer, which is a sub cat of a user, however the test is still receiving a user, not a designer.
FactoryGirl.define do
factory :user do
factory :designer do
role: "designer"
end
end
end
describe StoreRating do
it "should have a rating" do
user = FactoryGirl.create(:designer)
store = FactoryGirl.create(:store)
StoreRating.create(designer: user, store: store, rating: 5)
end
end
Try:
FactoryGirl.define do
factory :user do
factory :designer, class: Designer do
role: "designer"
end
end
end

Factory Girl - How to create a factory for a model that has associations?

I am attempting to create a factory for my user model, along with its associations. However, I cannot seem to get the syntax right in my Factory Girl code. I've read through the Factory Girl documentation but cannot seem to find any help with my specific use case. The errors I am currently receiving when I run my test suite are:
undefined method `subscription_args' for #<FactoryGirl::SyntaxRunner...
and
Trait not registered: valid_card_data
Here are my models and associations:
User.rb
has_one :subscription
has_one :plan, :through => :subscription
has_many :projects
Project.rb
belongs_to :user
Plan.rb
has_many :subscriptions
Subscription.rb
belongs_to :plan
belongs_to :user
and
And here is my Factory Girl code:
FactoryGirl.define do
factory :user do
first_name "Joel"
last_name "Brewer"
email { "#{first_name}.#{last_name}#example.com".downcase }
password "foobar"
password_confirmation "foobar"
user_type "entrepreneur"
subscription { build(:subscription, subscription_args) }
after(:create) do |user|
user.subscription.save!
end
end
factory :subscription do
user
plan_id '4'
## I am trying to access a helper method from support/utilities ##
## This call to valid_card_data doesn't seem to be working... ##
stripe_card_token valid_card_data
email "joel.brewer#example.com"
end
factory :project do
title "Sample Project"
user
end
end
Here's how I've done it in the past. Certainly not the only way:
(Note I am using cucumber.)
require 'factory_girl'
FactoryGirl.define do
factory :user do |f|
f.username 'superman'
end
factory :message do |f|
f.association :user
f.content 'Test message content'
end
end
This establishes that the message factory should associate the message to a user. Which user? I establish that at the point of use:
steps.rb:
Given(/^there is a user$/) do
#user = FactoryGirl.create(:user)
end
Given(/^the user has posted the message "(.*?)"$/) do |message_text|
FactoryGirl.create(:message, :content => message_text, :user => #user)
end
When(/^I visit the page for the user$/) do
visit user_path(#user)
end
Then(/^I should see "(.*?)"$/) do |text|
page.should have_content(text)
end
My approach, specifying at the point of use makes sense for this use case. e.g. Given is a user (user must be established first) and that user has posted a message (now the relationship between the existing user and the message can be established)...
That may or may not work out well for you, but it's how I've done it. This may or may not have helped you, but here's hoping.
There are several ways to do it. Here is one example:
after(:build) do |keyword, evaluator|
keyword.text = FactoryGirl.build(:keyword_text, :value => evaluator.keyword_text)
end
You dont need subscription_args - these can be set when you call the factory.
Where are you defining your trait?
In my factories they look like this:
trait :with_category_associations do
..
For more complicated relationships you probably want to use:
after(:create) do |keyword, evaluator|
evaluator.categories.each do |category|
FactoryGirl.create(:join_inventory_keyword, final: keyword, category: category)
end
end

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

Resources