I'm trying to test a controller and I have this:
#category ||= Category.where(name: 'cat1').limit(1).first
#product ||= Product.where(...).limit(1).first
Product haves an attribute called category_id from an association one to one: Product has_one Category.
When I run the test, I create the Product object with FactoryGirl and I load the seeds.rb just with two caterories.
The result is that #product.category_id is not equals to #category.id. The id for category is always the same (because it is taken from database or the seeds in this case)
My question is how to improve this?
UPDATE
The original factory for product is
FactoryGirl.define do
factory :product do
name "Some Product"
...
trait :category_1 do
after(:build) do |product|
product.pictures << FactoryGirl.build(:picture, :product => product)
end
end
end
end
But when I add an association I get the error can't write unknown attribute 'product_id'
The new factories are
#factory for product
...
trait :category_1 do
category { create(:category) }
after(:build) do |product|
product.pictures << FactoryGirl.build(:picture, :product => product)
end
end
...
#factory for category
FactoryGirl.define do
factory :category do
name "category1"
end
end
Related
Have looked through and tried most examples and still cannot create a working HABTM. No matter how the CaseTrack and CaseTrackValue factories are constructed, I'm not able to find the CaseTrackValue[] in CaseTrack. Shouldn't creating CaseTrack properly provide a CaseTrackValue param within CaseTrack.
BTW: the only working association for HABTM seems to be putting
case_track_values { |a| [a.association(:case_track_value)] } in CaseTrack.
class CaseTrack
has_and_belongs_to_many CaseTrackValue
end
class CaseTrackValue
has_and_belongs_to_many CaseTrack
end
Rspec
it 'may exercise the create action' do
post '<route>', params: {case_track: attributes_for(:case_track)}
end
end
class CaseTrackController < ApplicationController
private:
def case_track_params
params.require(:case_track).permit(:name, :active, {case_track_values:[]})
end
end
Take a look at HABTM factories working in one of my projects. I put here the whole setup within a common example for you could understand it deeper and other stackoverflowers could easily adapt that example for their use cases.
So we have books and categories. There could be book belonging to many categories and there could be category with many books in it.
models/book.rb
class Book < ActiveRecord::Base
has_and_belongs_to_many :categories
end
models/category.rb
class Category < ActiveRecord::Base
has_and_belongs_to_many :books
end
factories/books.rb
FactoryGirl.define do
factory :book do
# factory to create one book with 1-3 categories book belongs to
factory :book_with_categories do
transient do
ary { array_of(Book) }
end
after(:create) do |book, ev|
create_list(:category, rand(1..3), books: ev.ary.push(book).uniq)
end
end
#factory to create a book and one category book belongs to
factory :book_of_category do
after(:create) do |book, ev|
create(:category, books: [book])
end
end
end
end
factories/categories.rb
FactoryGirl.define do
factory :category do
#factory to create category with 3-10 books belong to it
factory :category_with_books do
transient do
ary { array_of(Category) }
num { rand(3..10) }
end
after(:create) do |cat, ev|
create_list(:book, ev.num,
categories: ev.ary.push(cat).uniq)
end
end
end
end
My helper method which you have to put somewhere in spec/support and then include it where you need it:
# method returns 0-3 random objects of some Class (obj)
# for example 0-3 categories for you could add them as association
# to certain book
def array_of(obj)
ary = []
if obj.count > 0
Random.rand(0..3).times do
item = obj.all.sample
ary.push(item) unless ary.include?(item)
end
end
ary
end
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
I'm having the hardest time thinking through this. I'm very new to FactoryGirl, so this may be explained clearly somewhere and I apologize if that is the case. This is certainly not a unique problem, maybe my Google skills aren't quite up to par.
I am working orders, which belong to category and belong to customer. I'm trying to build a customer who's placed 5 orders, however I keep throwing unique errors when it tries to build the category (which requires a unique name).
features/customer_spec.rb
RSpec.feature "Customer management", :type => :feature do
scenario "Customer with orders has order history" do
customer = create(:customer, :with_5_completed_orders)
visit customer_path(customer)
expect(page).to have_content("Recent Orders")
end
end
factories/customers.rb
FactoryGirl.define do
factory :customer do
...
trait :with_5_completed_orders do
after :create do |customer|
create_list(:order_line, 5, :completed, :customer => customer)
end
end
end
end
factories/order_line.rb
FactoryGirl.define do
factory :order_line do
....
product
....
end
end
factories/product.rb
FactoryGirl.define do
factory :product do |f|
....
category
....
end
end
factories/categories.rb
FactoryGirl.define do
sequence :category_name do |n|
"category-#{n}"
end
factory :category do
name { generate(:category_name) }
end
end
If I get it right, you have problem with category name uniqueness.
You can use sequences to generate unique category names with FactoryGirl:
sequence :category_name do |n|
"category-#{n}"
end
factory :category do
name { generate(:category_name) }
end
FactoryGirl's documentation on sequences is worth reading too.
If your intention is to reuse the same category twice, you can first create one and reuse it in all orders:
trait :with_5_completed_orders do
after :create do |customer|
cat = generate(:category)
create_list(:order_line, 5, :completed, customer: customer, category: cat)
end
end
I have a Listing model which has_many :categories, through: :categories_listings and has_many :categories_listings. I was testing it with a Factory Girl factory which looks like this:
factory :listing do |f|
f.sequence (:title) { |n| "Listing #{n}" }
message {Faker::Lorem.paragraph}
cards {[FactoryGirl.create(:card)]}
categories {[FactoryGirl.create(:category)]}
association :delivery_condition
association :unit_of_measure
quantity 1
unit_price 1
end
And everything was OK until I added the following validation to the model:
validate :has_categories?
def has_categories?
if self.categories_listings.blank?
errors.add :base, "You have to add at least one category"
end
end
Now whenever I run the factory I get:
ActiveRecord::RecordInvalid: You have to add at least one category
I also tried with Factory Girl callbacks like before :create but the problem is that I can't add the association because I don't yet know the listing id in the callback. But I can't save the listing because validations are run before the association.
How can I get around this and make it work?
Made it work.
In my factory I removed the categories line and added a before(:create) callback which BUILDS the relationship. Emphasis on builds as it wouldn't work with create (I tried that before posting the question). Plus build bypasses the deadlock I was mentioning. So now, the working factory looks like this:
factory :listing do |f|
f.sequence (:title) { |n| "Listing #{n}" }
message { Faker::Lorem.paragraph }
cards { [FactoryGirl.create(:card)] }
association :delivery_condition
association :unit_of_measure
quantity 1
unit_price 1
before(:create) do |listing|
category = FactoryGirl.create(:category)
listing.categories_listings << FactoryGirl.build(:categories_listing, listing: listing, category: category)
end
end
Got my inspiration from this answer.
food.rb factory:
FactoryGirl.define do
factory :food do
factory :apple do
description 'apple'
sequence(:name) { |n| "apple #{n}"}
long_description 'Apple'
end
factory :burger do
description 'burger'
sequence(:name) { |n| "burger #{n}"}
long_description 'Burger'
end
after(:create) do |food|
[:fat, :protein, :carb, :fiber].each do |nutrient|
food.nutrients << FactoryGirl.create(nutrient, :measurement)
end
end
end
end
nutrient.rb factory
FactoryGirl.define do
factory :nutrient do
factory :fat do
name 'Fat'
slug 'fat'
end
factory :protein do
name 'Protein'
slug 'protein'
end
factory :carb do
name 'Carbohydrates'
slug 'carbohydrates'
end
factory :fiber do
name 'Fiber'
slug 'fiber'
end
trait :measurement do
measurement 'milligrams'
end
end
end
food_nutrient.rb factory
FactoryGirl.define do
factory :food_nutrient do
food
nutrient
qty rand(1..100)
end
end
Feature test:
feature 'Search' do
scenario 'for apples' do
user = FactoryGirl.create(:user)
apples = 15.times.map { FactoryGirl.create(:apple) }
burgers = 5.times.map { FactoryGirl.create(:burger) }
...more code
end
end
The error I get is:
Failure/Error: apples = 15.times.map { FactoryGirl.create(:apple) }
ActiveRecord::RecordInvalid:
Validation failed: Qty can't be blank
Qty is an attribute in the food_nutrient has_many :through join_table. How do I pass in that variable?
You would need to add FoodNutrient instances in your after(:create) rather than adding nutrients directly:
after(:create) do |food|
[:fat, :protein, :carb, :fiber].each do |nutrient|
food.food_nutrients << FactoryGirl.create(:food_nutrient,
nutrient: FactoryGirl.create(:nutrient, :measurement)
)
end
end
This will then use your food_nutrient factory to create the FoodNutrient instance associated with each of the four nutrients.