How to specify the association with factory_bot? - ruby-on-rails

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

Related

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

Factory girl after_initialize

I have two ActiveRecord models User and posts. This is my user model.
class User < ActiveRecord::Base
has_many :posts
end
and this is my Post model.
class Post < ActiveRecord::Base
belongs_to :user
after_initialize :set_name
private
def set_name
self.name = "Post #{self.user.posts.count + 1}"
end
end
all this is working fine but when I write my factories
FactoryGirl.define do
factory :post do
content 'blah blah'
user
end
factory :user do
name 'Dummy name'
end
end
and this is my post_spec.rb file
require 'rails_helper'
RSpec.describe Post do
context 'with valid values' do
it 'should be valid' do
expect(build(:post)).to be_valid
end
end
end
and my test case fails saying that
undefined method posts for nil class in set_name
I don't know where I'm going wrong.
patkoperwas is correct, you're attempting to initialize a Post before you have an associated User object, which your after_initialize demands exist first. If you must use after_initialize, you could create a factory that creates a User before creating a Post and build with that instead.
FactoryGirl.define do
factory :user do
name "blah blah"
factory :user_with_posts do
transient do
post_count = 1
end
after(:create) do |user, evaluator|
evaluator.post_count.times do
create :post, user: user
end
end
end
end
end
I don't believe there is before(:build) functionality built into FactoryGirl. So you can't really use a callback to create a User before building your Post object. I would either create a user_with_post or explicitly create a user and pass it in when you create a post object.
RSpec.describe Post do
context 'with valid values' do
it 'should be valid' do
user = FactoryGirl.create :user_with_posts
post = user.posts.first
expect(post).to be_valid
end
end
You need to configure that association https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#associations
FactoryGirl.define do
factory :user do
name 'Dummy name'
end
factory :post do
content 'blah blah'
association :user
end
end
If you create a post using the factory without a user, FactoryGirl will create the user for you, if you create the user before and pass it when creating a post, FactoryGirl will use the user you provided.
You can use with_initialize in your Post factory like this:
factory :post do
content 'blah blah'
user
initialize_with { new(user: user) }
end
The reason for the issue is described in the factory_bot docs: https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#custom-construction
For maximum compatibility with ActiveRecord, the default initializer builds all instances by calling new on your build class without any arguments. It then calls attribute writer methods to assign all the attribute values.
This is why the Post factory doesn't have the associated User within the after_initialize hook.

FactoryGirl nested transient has_many association with validation

My problem is that of FactoryGirl has_many association with validation, with the added complexity of the association being a "transient" attribute, and is quite nested
ie. my classes are (I'm using mongoid, assume Mongoid::Document is included in all models)
class User
has_many :company_admin_profiles
end
class CompanyAdminProfile
belongs_to :company
belongs_to :user # Cannot exist standalone
end
class Company
has_many :company_admin_profiles
validate :has_at_least_one_admin
end
So far with FactoryGirl I've written
FactoryGirl.define do
factory :user do
...
trait(:company_admin) do
transient do
company_admins_count 1
company { create(:company, admins_count: 0) }
end
after(:build) do |user, evaluator|
create_list(:company_admin_profile, evaluator.company_admins_count,
company: evaluator.company,
user: user,
first_name: user.first_name,
last_name: user.last_name,
email: user.email)
end
end
factory :company_admin_user, traits: [:company_admin]
end
end
FactoryGirl.define do
factory :company_admin_profile, class: Company::Admin do
company
end
end
FactoryGirl.define do
factory :company do
transient do
admins_count 1 # need one admin to pass validation
end
after(:build) do |company, evaluator|
build_list(:company_admin_user, evaluator.admins_count,
company: company)
end
end
end
I've tried several variations on this, the last error is
* company - Attribute already defined: company (FactoryGirl::AttributeDefinitionError)
* company_admin_profile - Attribute already defined: company (FactoryGirl::AttributeDefinitionError)
* company_admin_user - Attribute already defined: company (FactoryGirl::AttributeDefinitionError)
I just saw an upvote on the question, so maybe someone is actually interested in the solution I have for now.
I don't remember exactly where the problem was, but here's a code that works :
# factories/company.rb
FactoryGirl.define do
factory :company do
...
transient do
admins_count 1 # need one admin to pass validation
end
after(:build) do |comp, evaluator|
if evaluator.admins_count == 1
build(:company_admin_user, company: comp)
end
end
end
end
# factories/user.rb
FactoryGirl.define do
factory :user do
...
after(:build) do |user|
user.skip_confirmation!
end
# Company admin
trait(:company_admin) do
transient do
company_admins_count 1
company { build(:company, admins_count: 0) }
end
after(:build) do |user, evaluator|
create_list(:company_admin_profile, evaluator.company_admins_count,
company: evaluator.company,
user: user)
evaluator.company.save
end
end
factory :company_admin_user, traits: [:company_admin]
end
end
# factories/company_admin_profile.rb
FactoryGirl.define do
factory :company_admin_profile, class: CompanyAdminProfile do
...
company
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 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