Background:
I am trying to create a FactoryBot object which is related with has_one/belongs_to
User has_one Car
Car has_one Style
Style has an attribute {style_number:"1234"}
Question
My controller references user, user has_one Car, Car has_one Style, and I need to set these values within FactoryBot.
How do I create a User, who also has a Car object, that has a Style object?
I read the documentation https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md
However, I am not understanding how they recommend doing this. Figured out, I need to nest the three objects, but confused on the syntax.
Controller
before_action :authenticate_user!
before_action :set_steps
before_action :setup_wizard
include Wicked::Wizard
def show
#user = current_user
#form_object = form_object_model_for_step(step).new(#user)
render_wizard
end
private
def set_steps
if style_is_1234
self.steps = car_steps.insert(1, :style_car)
else
self.steps = car_steps
end
end
def style_is_1234
if params.dig(:form_object, :style_number)
(params.dig(:form_object, :style_number) & ["1234"]).present?
else
(current_user.try(:car).try(:style).try(:style_number) & ["1234"]).present?
end
end
def car_steps
[:type,:wheel, :brand]
end
Rspec Test
Factory :User
FactoryBot.define do
factory :user, class: User do
first_name { "John" }
last_name { "Doe" }
email { Faker::Internet.email }
password { "somepassword" }
password_confirmation { "some password"}
end
end
Before method
before(:each) do
#request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryBot.create(:user)
sign_in user
Test
User needs to be signed in and User.car.style.style_number needs to be set to "1234"
context "Requesting with second step CarStyle" do
it "should return success" do
get :show, params: { :id => 'car_style' }
expect(response.status).to eq 200
end
end
Currently this test fails because User.Car.Style.style_number is not set to "1234".
Trial 1 (https://github.com/thoughtbot/factory_bot_rails/issues/232)
FactoryBot.define do
factory :user, class: User do
first_name { "John" }
last_name { "Doe" }
email { Faker::Internet.email }
password { "somepassword" }
password_confirmation { "some password"}
car
end
end
FactoryBot.define do
factory :car, class: Car do
make { "Holden" }
model { "UTE" }
end
end
FactoryBot.define do
factory :style, class: Style do
color { "blue" }
for_car
trait :for_car do
association(:styable, factory: :car)
end
end
end
Error from trail 1
SystemStackError:
stack level too deep
Trail 2
I tried srng's recommendation
EDIT: For a polymorphic association try;
FactoryBot.define do
factory :car, class: Car do
make { "Holden" }
model { "UTE" }
association :stylable, factory: :style
end
end
and got error:
ActiveRecord::RecordInvalid: Validation failed: Stylable must exist
I think this is a rails 5 issue. https://github.com/rails/rails/issues/24518
However, I would like to keep my code with the adding the optional:true. Any way to do this?
Trail 3
FactoryBot.define do
factory :car, class: Car do
make { "Holden" }
model { "UTE" }
after(:create) do |car|
create(:style, stylable: car)
end
end
end
Tried Srng's second recommendation and although it worked for him, I got a slightly different error:
ActiveRecord::RecordInvalid:
Validation failed: User must exist
In order to create dependent Factories you have to create a factory for each model, and then just add the dependent Model name to your factory, ie.
FactoryBot.define do
factory :user, class: User do
first_name { "John" }
last_name { "Doe" }
email { Faker::Internet.email }
password { "somepassword" }
password_confirmation { "some password"}
car
end
end
FactoryBot.define do
factory :car, class: Car do
make { "Holden" }
model { "UTE" }
style
end
end
FactoryBot.define do
factory :style, class: Style do
color { "blue" }
end
end
EDIT:
Relevant code;
# Factories
FactoryBot.define do
factory :user, class: User do
first_name { "John" }
last_name { "Doe" }
email { Faker::Internet.email }
password { "somepassword" }
password_confirmation { "some password"}
after(:create) do |user|
user.car ||= create(:car, :user => user)
end
end
end
factory :style, class: Style do
style_number { "Blue" }
end
factory :car, class: Car do
name { "Holden" }
trait :style do
association :stylable, factory: :style
end
end
#models
class Car < ApplicationRecord
has_one :style, as: :styleable
end
class Style < ApplicationRecord
belongs_to :styleable, polymorphic: true
belongs_to :car
end
# Migrations - The belongs_to is the only important one
class CreateStyles < ActiveRecord::Migration[5.2]
def change
create_table :styles do |t|
t.string :style_number
t.belongs_to :stylable, polymorphic: true
t.timestamps
end
end
end
class CreateCars < ActiveRecord::Migration[5.2]
def change
create_table :cars do |t|
t.string :name
t.timestamps
end
end
end
There can be an another way of attaining this using a transient block in the Factory.
Hope the below snippet may help you to explore in new way.
Note: This is not tested.
## To Create a user in test case
# create(:user) # defaults to 1234 style number
# create(:user, car_style_number: 5678)
DEFAULT_STYLE_NUMBER = 1234
FactoryBot.define do
factory :user do
transient do
car_style_number { DEFAULT_STYLE_NUMBER }
end
first_name { "John" }
last_name { "Doe" }
email { Faker::Internet.email }
after(:create) do |user, evaluator|
user.car = create(:car, car_style_number: evaluator.car_style_number, user: user)
end
end
end
FactoryBot.define do
factory :car do
transient do
car_style_number { DEFAULT_STYLE_NUMBER }
end
make { "Holden" }
model { "UTE" }
after(:create) do |car, evaluator|
car.style = create(:style, style_number: evaluator.car_style_number, car: car)
end
end
end
FactoryBot.define do
factory :style do
style_number { DEFAULT_STYLE_NUMBER }
end
end
Related
I am new to Factories and I need help for the association and nested attributes....
How do I set an admin user that creates a product? OK
How do I set category to a product? Ok
How do I attach images to a product? OK
How do I set product's sizes (nested attibutes)
user.rb
has_many :products
product.rb
belongs_to :user
belongs_to :category
has_many :sizes, inverse_of: :product, dependent: :destroy #nested_attributes
size.rb
belongs_to :product
category.rb
has_many :products
factories/users.rb
FactoryBot.define do
factory :user do
first_name { Faker::Name.first_name}
last_name { Faker::Name.last_name }
admin { [false, true].sample }
sequence(:email) { |n| "#{n}#{Faker::Internet.email}" }
birth_date {"20/10/1997"}
password { 'password'}
end
end
factories/categories.rb
FactoryBot.define do
factory :category do
title { Faker::Artist.name }
end
end
factories/sizes.rb
FactoryBot.define do
factory :size do
size_name {["S", "M", "L", "XL"].sample }
quantity { Faker::Number.number(2) }
end
end
factories/products.rb
FactoryBot.define do
factory :product do
title { Faker::Artist.name}
ref { Faker::Number.number(10)}
price { Faker::Number.number(2) }
color { Faker::Color.color_name }
brand { Faker::TvShows::BreakingBad }
description { Faker::Lorem.sentence(3) }
size
category
# how to set an admin ??
end
end
Add associations like this
For product
FactoryBot.define do
factory :product do
user {User.first || association(:user)}
user {User.first || association(:user, admin: true)}
# your admin attribute (role: admin or admin: true) whatever you are using for admin
category {Category.first || association(:category)}
end
end
Read FactoryBot association hope it will help.
I'm using this factory file for user model:
FactoryBot.define do
factory :user do |f|
f.sequence(:first_name) { |n| "#{Faker::Name.first_name}foo#{n}" }
f.sequence(:last_name) { |n| "#{Faker::Name.last_name}foo#{n}" }
f.sequence(:email) { |n| "foo#{n}#example.com" }
f.password "foobar"
f.password_confirmation { |u| u.password }
f.sequence(:confirmed_at) { Date.today }
f.sequence(:telephone_number) { Faker::Number.number(10) }
f.sequence(:mobile_phone_number) { Faker::Number.number(10) }
f.sequence(:verification_code) { '0000' }
f.sequence(:is_verified) { false }
end
end
and Order.rb factory is:
FactoryBot.define do
factory :order do
association :store
association :user
total_price Faker::Number.positive
total_discount Faker::Number.positive
end
end
And the order model should have these three FKs, two of which are from User:
class Order < ApplicationRecord
belongs_to :customer, class_name: 'User'
belongs_to :carrier, class_name: 'User'
belongs_to :store
end
and in order_controllers_spec.rb file, I got these:
let(:customer) { FactoryBot.create(:user) }
let(:carrier) { FactoryBot.create(:user) }
let(:store) { FactoryBot.create(:store) }
let(:order) { FactoryBot.create(:order, customer_id: customer.id, carrier_id: carrier.id, store_id: store.id) }
Each time I run the show test,
describe "GET show" do
it 'has a 200 status code' do
get :show, params: { id: order_item.id }
expect(response.status).to eq(200)
end
end
I got this error
Failure/Error: let(:order) { FactoryBot.create(:order, customer_id: customer.id, carrier_id: carrier.id, store_id: store.id) }
NoMethodError:
undefined method `user=' for #<Order:0x00007fcd2efc5118>
Any ideas about how to solve this?
I think in your Order's factory definition you're using user, instead of customer or carrier as your Order model define.
association :customer, factory: :user
association :carrier, factory: :user
I am doing the following rspec test to test my '#clubs' method
context "#clubs" do
it "returns the users associated Clubs" do
club = create(:club)
user = club.host
expect(user.clubs).to contain(club)
end
end
The method in my User model:
class User < ActiveRecord::Base
has_many :host_clubs, :class_name => 'Club', :inverse_of => :host
has_and_belongs_to_many :volunteer_clubs, :class_name => 'Club', :inverse_of => :volunteers
def clubs
[host_clubs, volunteer_clubs].flatten
end
end
When I run the test and use p club.host, it returns the user as expected, however I cannot see why calling user.clubs => [] returns an empty array. Here is the factory for context.
factory :host, class: User do |f|
f.first_name { Faker::Name.first_name }
f.last_name { Faker::Name.last_name }
f.email { Faker::Internet.email }
f.password { Faker::Internet.password }
f.role { "host" }
f.onboarded_at { Date.today }
after(:create) do |host|
host.confirm_email_address
end
end
factory :club, class: Club do |f|
f.venue_type { "Primary school" }
f.name { Faker::Company.name }
f.address_1 { Faker::Address.street_address }
f.city { Faker::Address.city }
host
end
Can anyone give me a heads up to why it may not be returning the record?
It's worth noting that I am adding this test after the method was created. In the console the method behaves as expected.
(Making my comment as an answer with additional information)
Try user.reload before expecting the results in the test solves this issue.
This is because of club object is created independently from user. This is generally happens in rspec tests.
I have the following:
class Group < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :permissions
has_one :city
end
class User < ActiveRecord::Base
has_many :permissions, dependent: :destroy
has_and_belongs_to_many :groups
end
FactoryGirl.define do
factory :user do
username { FFaker::Name.first_name }
email { FFaker::Internet.email }
first_name { FFaker::Name.first_name }
last_name { FFaker::Name.last_name }
current_login_ip "127.0.0.1"
login_count { rand(100) + 1 }
deleted_at nil
updated_at { FactoryGirl.generate(:time) }
created_at { FactoryGirl.generate(:time) }
suspended_at nil
password "password"
password_confirmation "password"
vertical {1}
marketplace_role {1}
end
factory :admin do
admin true
end
factory :group do
users { |a| [a.association(:user)] }
name { FFaker::Company.name }
city { FactoryGirl.create(:city) }
end
end
FactoryGirl.define do
factory :permission do
user
group
asset { fail "Please specify :asset for the permission" }
updated_at { FactoryGirl.generate(:time) }
created_at { FactoryGirl.generate(:time) }
end
end
factory :city do
name { FFaker::Name.name }
end
factory :zone do
city
name { FFaker::Name.name }
end
factory :location do
zone
name { FFaker::Name.name }
end
Failure/Error: #group = FactoryGirl.create(:group)
ActiveModel::MissingAttributeError: can't write unknown attribute
group_id
This is what I get. I am unable to define and use the groups factory. I looked into this post, But the solution doesnt work.
Change city factory to the following
factory :city do
group nil # or group_id if you will
name { FFaker::Name.name }
end
and you'll most likely be good.
I have this factory generated:
FactoryGirl.define do
sequence :account_login do |n|
"login-#{ n }"
end
factory :account do
sequence :email do |n|
"someone#{n}#gmail.com"
end
email_confirmation { |account| account.send :email }
url { Faker::Internet.url }
login { generate(:account_login) }
password { Faker::Internet.password }
password_confirmation { |account| account.send(:password) }
current_password { |account| account.send(:password) }
twitter_account 'openhub'
name { Faker::Name.name + rand(999_999).to_s }
about_raw { Faker::Lorem.characters(10) }
activated_at { Time.current }
activation_code nil
country_code 'us'
email_master true
email_kudos true
email_posts true
association :github_verification
end
end
What I really need to do is to create a second factory that creates a user with a nil github_verification attribute.
Something like this:
factory :account_with_no_verifications, parent: :account do
associaton github_verification = nil
end
Is there a way to do that?
Just use FactoryGirl::Strategy::Null http://www.rubydoc.info/github/thoughtbot/factory_girl/FactoryGirl/Strategy/Null
factory :account_with_no_verifications, parent: :account do
association github_verification, strategy: :null
end