FactoryGirls multiple associations - ruby-on-rails

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

Related

Factory Girl has_many through validation deadlock

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.

rspec find record in database

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

FactoryGirl build strategy with nested associations

Is it possible to preserve the build strategy when I have a factory for a model that has an association to a second model, which itself has an association to a third model?
In the example below, a Post is associated with a User, and a User is associated with a City. Even when :strategy => :build is used for all associations, post.user and post.user.city end up getting saved to the database. In the interests of a speedy test suite, can I prevent these database writes from happening?
Factory.define do
factory :user do
name "A User"
association :city, :strategy => :build
end
factory :city do
name "A City"
end
factory :post do
title "A Post"
body "Some text here"
association :user, :strategy => :build
end
end
post = FactoryGirl.build(:post)
post.new_record? # True
post.user.new_record? # False
post.user.city.new_record? # False
Have you tried the alternative block syntax?
Factory.define do
factory :user do
name "A User"
city { |city| city.association :city, :strategy => :build }
end
factory :city do
name "A City"
end
end
It looks like FactoryBot (formerly FactoryGirl) added use_parent_strategy as a configuration option in v4.8.0. It is turned off by default, to turn it on add the following to your spec/rails_helper:
FactoryGirl.use_parent_strategy = true
Relevant pull request on the factory_bot repo: https://github.com/thoughtbot/factory_bot/pull/961
As #messanjah said, however for older versions (< v4.8.0) you can do the following:
association :user, :strategy => #build_strategy.class

FactoryGirl callbacks with multiple has_many relationships

I have three models: Course, Level and Subject. A course has many levels and subjects.
How do I create a course object in factory girl using callbacks without getting stuck in an infinite loop?
factories.rb
factory :subject do
name "Arabic"
after_build do |subject|
subject.courses << FactoryGirl.build(
:course,
:subject => subject,
)
end
end
factory :level do
name "Level 1"
after_build do |level|
level.courses << FactoryGirl.build(
:course,
:level => level
)
end
end
factory :course do
name "Intermediate Arabic 4"
subject
level
end
With this code FactoryGirl will try and make a new subject when the course is being built from the level callback, causing a new course to be built with no level specified.....and so on.
I've tried a few things but I'm going nowhere on this one.

Factory Girl + Mongoid embedded documents in fixtures

Let’s say you have the following mongoid documents:
class User
include Mongoid::Document
embeds_one :name
end
class UserName
include Mongoid::Document
field :first
field :last_initial
embedded_in :user
end
How do you create a factory girl factory which initializes the embedded first name and last initial? Also how would you do it with an embeds_many relationship?
I was also looking for this one and as I was researching I've stumbled on a lot of code and did pieced them all together (I wish there were better documents though) but here's my part of the code. Address is a 1..1 relationship and Phones is a 1..n relationship to events.
factory :event do
title 'Example Event'
address { FactoryGirl.build(:address) }
phones { [FactoryGirl.build(:phone1), FactoryGirl.build(:phone2)] }
end
factory :address do
place 'foobar tower'
street 'foobar st.'
city 'foobar city'
end
factory :phone1, :class => :phone do
code '432'
number '1234567890'
end
factory :phone2, :class => :phone do
code '432'
number '0987654321'
end
(And sorry if I can't provide my links, they were kinda messed up)
Here is a solution that allows you to dynamically define the number of embedded objects:
FactoryGirl.define do
factory :profile do
name 'John Doe'
email 'john#bigcorp.com'
user
factory :profile_with_notes do
ignore do
notes_count 2
end
after(:build) do |profile, evaluator|
evaluator.notes_count.times do
profile.notes.build(FactoryGirl.attributes_for(:note))
end
end
end
end
end
This allows you to call FactoryGirl.create(:profile_with_notes) and get two embedded notes, or call FactoryGirl.create(:profile_with_notes, notes_count: 5) and get five embedded notes.

Resources