How can I clobber factory girl associations? - ruby-on-rails

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

Related

Rails FactoryGirl pass empty string after create

I have a ruby app that I'm using rspec and factorygirl with, and I'm having trouble building a factory. Let me explain with an example:
I've got two factories which are creating registrant.user_demographic like below:
FactoryGirl.define do
factory :registrant do
first_name { "Johnny #{Faker::Name.initials}" }
(...)
after(:build) do |registrant|
registrant.user_demographic ||= build(:user_demographic, user: registrant)
end
end
end
FactoryGirl.define do
factory :user_demographic do
user { create(:registrant) }
phone "1234567890"
(...)
end
end
Now I want to have registrant without phone number sth like: registrant.user_demographic.phone == ''. I've tried with transient defined in user_demographic but it won't work:
FactoryGirl.define do
factory :registrant do
first_name { "Johnny #{Faker::Name.initials}" }
(...)
after(:build) do |registrant|
registrant.user_demographic ||= build(:user_demographic, user: registrant)
end
trait :without_phone do
after(:build) do |registrant|
registrant.user_demographic ||= build(:user_demographic, user: registrant, phone_present: false)
end
end
end
end
FactoryGirl.define do
factory :user_demographic do
(...)
transient do
phone_present { true }
end
phone { '1234567890' if phone_present }
end
end

Create FactoryBot User with has_one relationship

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

Factorybot - How to set Nested attributes

I beleive it's better to create a new question... It follows my previous question my model product has many sizes (nested attributes)
I want to create Factories but I can't make it work...
A product is valid if it has at least one size (size_nameand quantity)
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) }
attachments { [
File.open(File.join(Rails.root,"app/assets/images/seeds/image.jpg")),
] }
user { User.first || association(:user, admin: true)}
category { Category.first }
# SOLUTION 1
factory :size do
transient do
size_name {["S", "M", "L", "XL"].sample}
quantity { Faker::Number.number(2) }
end
end
# SOLUTION 2
after(:create) do |product|
create(:size, product: product)
end
# SOLUTION 3
initialize_with { attributes }
# Failure/Error: #product = create(:product, category_id: category.id)
# NoMethodError:
# undefined method `save!' for #<Hash:0x007ff12f0d9378>
end
end
In the controller spec
before(:each) do
sign_in FactoryBot.create(:user, admin: true)
category = create(:category)
#product = create(:product, category_id: category.id)
end
I don't know how to write the size attribute, my produt is still not valid (missing the size)
The error I get is validation failed,Product must exist...
You have to define a factory for size
FactoryBot.define do
factory :size do
size_name { ["S", "M", "L", "XL"].sample }
quantity { Faker::Number.number(2) }
end
end
and the product
FactoryBot.define do
factory :product do
association :size
title { Faker::Artist.name}
...
end
end
or add the build callback in the :product factory
after :build do |product|
product.sizes << create(:size)
end
Create a factory for sizes
FactoryBot.define do
factory :size do
size_name {["S", "M", "L", "XL"].sample}
quantity { Faker::Number.number(2) }
product
end
end
and one for products
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) }
attachments { [
File.open(File.join(Rails.root,"app/assets/images/seeds/image.jpg")),
] }
user { User.first || association(:user, admin: true)}
category
end
end

Unable to build has_and_belongs_to_many relation in Rspec with FactoryBot

I read the documentation of FactoryBot here: https://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md
I have users and roles and it is a has_and_belongs_to_many relation. I have tried many steps from the documentation to setup this relation but nothing works.
First, I tried this technique:
FactoryBot.define do
factory :role do
name { "marketing" }
factory :admin_role do
name { "admin" }
end
end
end
FactoryBot.define do
factory :user, aliases: [:marketing] do
email { 'marketing#mysite.io' }
password { '123456' }
password_confirmation { '123456' }
association :role
end
end
But it gives me:
NoMethodError: undefined method `role=' for #<User:0x007f9743449198>
Second, I tried this technique:
FactoryBot.define do
factory :role do
name { "marketing" }
factory :admin_role do
name { "admin" }
end
end
end
FactoryBot.define do
factory :user, aliases: [:marketing] do
email { 'marketing#mysite.io' }
password { '123456' }
password_confirmation { '123456' }
role
end
end
But again I get this error:
NoMethodError: undefined method `role=' for #<User:0x007f9743449198>
Third, I tried this technique of pluralizing the relation:
FactoryBot.define do
factory :role do
name { "marketing" }
factory :admin_role do
name { "admin" }
end
end
end
FactoryBot.define do
factory :user, aliases: [:marketing] do
email { 'marketing#mysite.io' }
password { '123456' }
password_confirmation { '123456' }
role
end
end
I get this error:
ArgumentError: Trait not registered: roles
Yet when I read the documentation, it suggests I can use these methods. So what am I doing wrong?
Not sure if this will work but since a user can have many roles you probably need to add a role as a single array item
FactoryBot.define do
factory :user, aliases: [:marketing] do
email { 'marketing#mysite.io' }
password { '123456' }
password_confirmation { '123456' }
roles { [ role ] }
end
end
Or
FactoryBot.define do
factory :user, aliases: [:marketing] do
email { 'marketing#mysite.io' }
password { '123456' }
password_confirmation { '123456' }
roles { [ create(role) ] }
end
end
The reason for your error is you can't call #user.role but you can call #user.roles with HABTM relation.

Create HABTM relationship in FactoryGirl?

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.

Resources