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.
Related
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.
I am attempting to create a factory for my user model, along with its associations. However, I cannot seem to get the syntax right in my Factory Girl code. I've read through the Factory Girl documentation but cannot seem to find any help with my specific use case. The errors I am currently receiving when I run my test suite are:
undefined method `subscription_args' for #<FactoryGirl::SyntaxRunner...
and
Trait not registered: valid_card_data
Here are my models and associations:
User.rb
has_one :subscription
has_one :plan, :through => :subscription
has_many :projects
Project.rb
belongs_to :user
Plan.rb
has_many :subscriptions
Subscription.rb
belongs_to :plan
belongs_to :user
and
And here is my Factory Girl code:
FactoryGirl.define do
factory :user do
first_name "Joel"
last_name "Brewer"
email { "#{first_name}.#{last_name}#example.com".downcase }
password "foobar"
password_confirmation "foobar"
user_type "entrepreneur"
subscription { build(:subscription, subscription_args) }
after(:create) do |user|
user.subscription.save!
end
end
factory :subscription do
user
plan_id '4'
## I am trying to access a helper method from support/utilities ##
## This call to valid_card_data doesn't seem to be working... ##
stripe_card_token valid_card_data
email "joel.brewer#example.com"
end
factory :project do
title "Sample Project"
user
end
end
Here's how I've done it in the past. Certainly not the only way:
(Note I am using cucumber.)
require 'factory_girl'
FactoryGirl.define do
factory :user do |f|
f.username 'superman'
end
factory :message do |f|
f.association :user
f.content 'Test message content'
end
end
This establishes that the message factory should associate the message to a user. Which user? I establish that at the point of use:
steps.rb:
Given(/^there is a user$/) do
#user = FactoryGirl.create(:user)
end
Given(/^the user has posted the message "(.*?)"$/) do |message_text|
FactoryGirl.create(:message, :content => message_text, :user => #user)
end
When(/^I visit the page for the user$/) do
visit user_path(#user)
end
Then(/^I should see "(.*?)"$/) do |text|
page.should have_content(text)
end
My approach, specifying at the point of use makes sense for this use case. e.g. Given is a user (user must be established first) and that user has posted a message (now the relationship between the existing user and the message can be established)...
That may or may not work out well for you, but it's how I've done it. This may or may not have helped you, but here's hoping.
There are several ways to do it. Here is one example:
after(:build) do |keyword, evaluator|
keyword.text = FactoryGirl.build(:keyword_text, :value => evaluator.keyword_text)
end
You dont need subscription_args - these can be set when you call the factory.
Where are you defining your trait?
In my factories they look like this:
trait :with_category_associations do
..
For more complicated relationships you probably want to use:
after(:create) do |keyword, evaluator|
evaluator.categories.each do |category|
FactoryGirl.create(:join_inventory_keyword, final: keyword, category: category)
end
end
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
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.