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
Related
I get the following rspec errors because the association CustomField is nil, but it should be generated by Factorybot and I cannot figure out why it is not. The project association works just fine.
Rspec Tests:
it { should belong_to(:custom_field) }
it { should validate_presence_of(:custom_field) }
Rspec Errors:
NoMethodError: undefined method `cf_type' for nil:NilClass
0) CustomFieldValue validations should validate that :custom_field_id cannot be empty/falsy
Failure/Error: ["single_select", "multi_select", "radiobutton", "checkbox", "label"].include?(custom_field.cf_type)
NoMethodError:
undefined method `cf_type' for nil:NilClass
# ./app/models/custom_field_value.rb:13:in `option_field?'
# ./spec/models/custom_field_value_spec.rb:18:in `block (3 levels) in <top (required)>'
NoMethodError: undefined method `cf_type' for nil:NilClass
0) CustomFieldValue validations should belong to custom_field required: true
Failure/Error: ["single_select", "multi_select", "radiobutton", "checkbox", "label"].include?(custom_field.cf_type)
NoMethodError:
undefined method `cf_type' for nil:NilClass
# ./app/models/custom_field_value.rb:13:in `option_field?'
# ./spec/models/custom_field_value_spec.rb:20:in `block (3 levels) in <top (required)>'
Model: CustomFieldValue
class CustomFieldValue < ApplicationRecord
belongs_to :custom_field
belongs_to :project
validates :custom_field, :project, presence: :true
validate :options_existence, if: :option_field?
validate :allowed_value_for_option_fields, if: -> cfv { cfv.option_field? && cfv.options_exist? }
# Conditions for validations
def option_field?
["single_select", "multi_select", "radiobutton", "checkbox", "label"].include?(custom_field.cf_type)
end
def options_exist?
!custom_field.custom_field_options.empty?
end
private
# Custom validations
# Validate that option fields have at least 1 option defined
def options_existence
if custom_field.custom_field_options.empty?
errors.add(:base, "No custom field option was found")
end
end
# Validation that only custom field option id can be stored for custom fields with options defined
def allowed_value_for_option_fields
if !custom_field.custom_field_options.map{|cfo| cfo.id.to_s}.include?(string_value)
errors.add(:base, "Custom field option id is the only accepted value")
end
end
Model: CustomField
class CustomField < ApplicationRecord
enum cf_type: { string: 10, text: 20, number: 30, single_select: 40,
multi_select: 50, checkbox: 60, radiobutton: 70, date: 80,
single_user: 90, multi_user: 100, label: 110 }
belongs_to :organization
has_many :custom_field_values, dependent: :destroy
has_many :custom_field_options, dependent: :destroy
accepts_nested_attributes_for :custom_field_options
validates :cf_type, :name, :organization, presence: :true
end
Model: Project
class Project < ApplicationRecord
belongs_to :organization
has_many :custom_field_values, dependent: :destroy
validates :name, :key, :organization, presence: :true
validates :key, uniqueness: { case_sensitive: false }, length: { minimum: 2, maximum: 25 }
# Validation of format
# Valid Input: abcd, xyz, aaa, aa-bb
# Invalid Input: abC, XYZ, 123, ABC123, -abc, abc-
validates_format_of :key, :with => /\A(?!-)[a-z][-\w]+(?<!-)\z/
accepts_nested_attributes_for :custom_field_values
end
RSpec: custom_field_value_spec.rb
require "rails_helper"
RSpec.describe CustomFieldValue, :type => :model do
subject {
create(:custom_field_value)
}
context "validations" do
it { should validate_presence_of :project }
it { should validate_presence_of :custom_field }
it { should belong_to(:project) }
it { should belong_to(:custom_field) }
it { expect { create(:custom_field_value, number_value: 1) }.to raise_error(ActiveRecord::RecordInvalid, /Value can be set only for one of the string_value, text_value, number_value or date_value/) }
it { expect { create(:custom_field_value_for_checkbox) }.to raise_error(ActiveRecord::RecordInvalid, /Custom field option id is the only accepted value/) }
end
end
Factory: custom_field_values.rb (factorybot)
FactoryBot.define do
factory :custom_field_value do
sequence(:string_value) { |n| "My String#{n}" }
association :project
association :custom_field, cf_type: :string
end
factory :custom_field_value_for_checkbox, class: CustomFieldValue do
sequence(:string_value) { |n| "My String#{n}" }
association :project
association :custom_field, factory: :custom_field_with_custom_field_options, cf_type: :checkbox
end
end
Factory: custom_fields.rb (factorybot)
FactoryBot.define do
factory :custom_field do
cf_type { :string }
sequence(:name) { |n| "Name#{n}" }
sequence(:description) { |n| "Description#{n}" }
association :organization
factory :custom_field_with_custom_field_values do
transient do
custom_field_values_count { 3 }
end
after(:create) do |custom_field, evaluator|
create_list(:custom_field_value, evaluator.custom_field_values_count, custom_field: custom_field)
end
end
factory :custom_field_with_custom_field_options do
transient do
custom_field_options_count { 3 }
end
after(:create) do |custom_field, evaluator|
create_list(:custom_field_option, evaluator.custom_field_options_count, custom_field: custom_field)
end
end
factory :custom_field_with_custom_field_values_and_options do
transient do
custom_field_values_and_options_count { 3 }
end
after(:create) do |custom_field, evaluator|
create_list(:custom_field_value, evaluator.custom_field_values_and_options_count, custom_field: custom_field)
create_list(:custom_field_option, evaluator.custom_field_values_and_options_count, custom_field: custom_field)
end
end
end
end
Factory: projects.rb (factorybot)
FactoryBot.define do
factory :project do
sequence(:name) { |n| "Name#{n}" }
sequence(:key) { |n| random_name }
association :organization
end
end
def random_name(length=5)
source = ('a'..'z').to_a.shuffle.join
name = ""
length.times{ name += source[rand(source.size)].to_s }
return name
end
You can stub your conditional validation to make your test pass:
RSpec.describe CustomFieldValue, :type => :model do
subject {
create(:custom_field_value)
}
context "validations" do
before { allow(subject).to receive(:option_field?).and_return(false) }
it { should validate_presence_of :custom_field }
end
end
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
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.
My associations aren't so complex but I've hit a wall making them work with FactoryGirl:
Text: blast_id:integer recipient_id:integer
class Text < ActiveRecord::Base
belongs_to :blast
belongs_to :recipient, class_name: "User"
validates :blast_id, presence: true
validates :recipient_id, presence: true
end
Blast: content:string author_id:integer
class Blast < ActiveRecord::Base
belongs_to :author, class_name: "User"
has_many :texts
validates :author_id, presence: true
end
User: name:string, etc. etc.
class User < ActiveRecord::Base
has_many :blasts, foreign_key: "author_id"
validates :name, presence: true
end
In FactoryGirl I've got:
FactoryGirl.define do
factory :user, aliases: [:author, :recipient] do |u|
sequence(:name) { Faker::Name.first_name }
end
factory :blast do
author
content "Lorem ipsum"
ignore do
texts_count 1
end
after :build do |blast, evaluator|
blast.texts << FactoryGirl.build_list(:text, evaluator.texts_count, blast: nil, recipient: FactoryGirl.create(:user) )
end
end
factory :text do
blast
association :recipient, factory: :user
end
end
Finally, some specs which all fail because Texts is not valid
require 'spec_helper'
describe Text do
User.destroy_all
Blast.destroy_all
Text.destroy_all
let!(:user) { FactoryGirl.create(:user) }
let!(:blast) { FactoryGirl.create(:blast, author: user) }
let(:text) { blast.texts.first }
subject { text }
it { should be_valid }
describe "attributes" do
it { should respond_to(:blast) }
it { should respond_to(:recipient) }
its(:blast) { should == blast }
its(:recipient) { should == recipient }
end
describe "when blast_id is not present" do
before { text.blast_id = nil }
it { should_not be_valid }
end
describe "when recipient_id is not present" do
before { text.recipient_id = nil }
it { should_not be_valid }
end
end
All the specs fail on FactoryGirl blast creation with:
1) Text
Failure/Error: let!(:blast) { FactoryGirl.create(:blast, author: user) }
ActiveRecord::RecordInvalid:
Validation failed: Texts is invalid
# ./spec/models/text_spec.rb:8:in `block (2 levels) in <top (required)>'
I've tried various iterations of the association code in the FactoryGirl docs and other question answers like this one but my situation is different enough that I can't get it to work.
If you've made it this far, thank you! Super grateful for any leads.
Your factory for "blast" should look like
factory :blast do
author
content "Lorem ipsum"
ignore do
texts_count 1
end
after :build do |blast, evaluator|
blast.texts << FactoryGirl.build_list(:text, evaluator.texts_count, blast: blast, recipient: FactoryGirl.create(:user) )
end
end
In other words, you immediately create the correct "parent" by connecting the newly created blast to the newly created tekst
To further dry your code, have a look at https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#configure-your-test-suite, describing how to get rid of using "FactoryGirl." over and over again by setting
config.include FactoryGirl::Syntax::Methods
once in your settings
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