Create HABTM relationship in FactoryGirl? - ruby-on-rails

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.

Related

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

Factory-bot - How to build association and nested attributes

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.

FactoryBot one record associated with many attributes

I have User, Assignment and Department models.
My Assignment model has 2 belongs_to associations with the User model as requestor and assignee.
The User and Assignment model also in turn belongs_to a department.
I want to run a validation on my Assignment such that the assignment, requestor and assignee all belong to the same department.
This is my code for the models
# app/models/assignment.rb
class Assignment < ApplicationRecord
belongs_to :requestor, class_name: "User", foreign_key: "requested_by"
belongs_to :assignee, class_name: "User", foreign_key: "assigned_to"
belongs_to :department
end
# app/models/user.rb
class User < ApplicationRecord
has_many :user_departments, class_name: "UserDepartment"
has_many :departments, through: :user_departments
belongs_to :department
end
# app/models/department.rb
class Department < ApplicationRecord
has_many :user_departments, class_name: "UserDepartment"
has_many :users, through: :user_departments
has_many :assignments
has_many :users
end
This is the code for my test
# spec/factories/assignment.rb
FactoryBot.define do
sequence(:title) { |n| "Title #{n}" }
sequence(:description) { |n| "Description #{n}" }
factory :assignment do
title
description
image { Rack::Test::UploadedFile.new(Rails.root.join('spec/support/Floyd.jpeg'), 'image/jpeg') }
release_date { DateTime.now >> 1 }
department { create(:department) }
requestor create(:user, :bm, department: "I want to use the same department as created in earlier line here")
assignee create(:user, :mu, department: "I want to use the same department as created in earlier line here")
end
end
Where should I define the department so I can use the same department across all three associations.
Have you tried self.department? It worked in my code:
# spec/factories/assignment.rb
FactoryBot.define do
sequence(:title) { |n| "Title #{n}" }
sequence(:description) { |n| "Description #{n}" }
factory :assignment do
title
description
image { Rack::Test::UploadedFile.new(Rails.root.join('spec/support/Floyd.jpeg'), 'image/jpeg') }
release_date { DateTime.now >> 1 }
department { create(:department) }
requestor { create(:user, :bm, department: self.department) }
assignee { create(:user, :mu, department: self.department) }
end
end
Hope I got the question right. You want the same department on all the models.
This is a thing you actually do in the spec.
describe Foo::Bar do
before(:each) do
#department = create(:deparment)
#assignment = create(:assignment, department: #department)
#user = create(:user, department: department)
# ....
end
But this always depends on how your overall structure is. When the assignment is the center model and always has a department you could do something like
describe Foo::Bar do
before(:each) do
#assignment = create(:assignment)
#user = create(:user, department: #assignment.department)
# ....
end

How can I clobber factory girl associations?

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

rspec association creation error

I have a model item has_many ratings and a ratings belongs_to item ratings belongs_to user I want to force a user who is creating an item to rate it too. Other users can then rate it later on. item and user have no association in my model.
I am doing the following in my item_spec which is giving me an error no implicit conversion of Symbol into Integer on line #item = Item.new(name: "Item1", below.
class Item < ActiveRecord::Base
has_many :ratings, dependent: :destroy, inverse_of: :item
accepts_nested_attributes_for :ratings, :allow_destroy => true
validates :name , :length => { minimum: 3 }
validates :category , :length => { minimum: 3 }
validates_presence_of :ratings
end
require 'spec_helper'
describe Item do
before do
#item = Item.new(name: "Item1",
url: "www.item1.com",
full_address: "Item1Address",
city: "Item1City",
country: "Item1Country",
category: "Item1Type",
ratings_attributes: {"rating" => "3", "comment" => "Ahh Good"} )
end
Also using FactoryGirl I am doing something like this
factory :item do
before_create do |r|
r.ratings<< FactoryGirl.build(:ratings, item: r )
end
name "Item1"
url "www.Item1.com"
full_address "Item1Address"
city "Item1City"
country "Item1Country"
category "Item1Category"
end
factory :ratings do
rating 3
comment "Its not that bad"
user
end
end
which again is not yeilding the desired result.
can anyone help me solve this problem please.Thanks!
Working Code, now having problem testing some association order, but at least the desired functionality working.
factory :item do
name "Item1"
url "www.Item1.com"
full_address "Item1Address"
city "Item1City"
country "Item1Country"
category "Item1Category"
end
factory :ratings, :class => 'Ratings' do
association :item, factory: :item, strategy: :build
user
rating 3
comment "Its not that bad"
end
factory :item_with_rating, parent: :item do
ratings {[FactoryGirl.create(:ratings)]}
end
Here is the spec file
require 'spec_helper'
describe Item do
before do
#item = FactoryGirl.create(:item_with_rating)
end
subject { #item }
it { should respond_to(:name) }
it { should respond_to(:url) }
it { should respond_to(:full_address)}
it { should respond_to(:city) }
it { should respond_to(:country) }
it { should respond_to(:category) }
it { should respond_to(:ratings) }
it { should_not respond_to(:type) }
it { should_not respond_to(:user_id) }
it { should be_valid }
There is no change in the Model file for item

Resources