Factory girl has_many - ruby-on-rails

How i can create object with association
I have Post model and PostsPhoto
PostsPhoto => belongs_to :post
Post => has_many :post_photos, dependent: :destroy,class_name: 'PostsPhoto'
I have tried
FactoryGirl.define do
factory :post do
article Faker::Lorem.paragraph(4)
video 'http://youtube.com'
author Faker::Name.name
category 'article'
title Faker::Name.title
post_photos
end
end
and have got
NoMethodError: undefined method `each' for #<PostsPhoto:0x007ff68e3bd698>
i cant use after(:create) {},because i validate it on create
def check_slider_photo
errors.add(:post_photos, 'Add post photos') if self.post_photos.size <= 0
end
I want to create(:post)=> returns me post object with post_photos

There are a couple ways around this. It depends on what you're testing exactly, but if you don't need to alter the objects or use them in queries, try using build_stubbed instead of create.
That is, if you'd typically say, FactoryGirl.create(:post), try instead saying FactoryGirl.build_stubbed(:post).
Another option would be not to run your validation in test and test that validation separately. To do this you can say
post = FactoryGirl.build(:post)
post.save(validate: false)
Then create the other objects and pass them post.id Happy testing.

Try this:
FactoryGirl.define do
factory :post do
article Faker::Lorem.paragraph(4)
video 'http://youtube.com'
author Faker::Name.name
category 'article'
title Faker::Name.title
factory :post_with_photos do
ignore do
photos_count 3
end
after(:build) do |post, evaluator|
post.post_photos << build_list(:post_photo, evaluator.photos_count)
end
end
end
end

Related

FactoryGirl association duplicate key error

What is the correct way to assign associations that already exist?
I am trying to assign a has_one relationship between a user and a city, where the same city can be used by many users or other entities (e.g. an event).
Code
FactoryGirl.define do
factory :user do
name 'john'
trait :in_boston do
association :city, factory: :boston
end
end
end
Error
PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "city_pkey" because it's trying to create Boston twice in the database.
What I would like to do is simply reference the existing factory, not create a new one.
My current working (but less than ideal) solution
FactoryGirl.define do
factory :user do
name 'john'
trait :in_boston do
after(:create) do |user|
user.city = City.find_by_name('Boston') || create(:boston)
end
end
end
end
Any guidance would be appreciated, thanks.
So, I'm going to assume that your model code is golden, and show you how I'd setup the test. I'm not sure why you need the factory to have decision making powers based on if the city exists or not. Just instantiate the city in its own factory and call the association in your test setup.
Factories
# factories/cities.rb
FactoryGirl.define do
factory :city do
name 'Boston'
end
end
# factories/users.rb
FactoryGirl.define do
factory :user do
name 'john'
city
end
end
Test
describe 'blah' do
let( :city ){ create :city }
let( :user ){ create :user, city: city }
it 'user should have a city' do
expect( user.city.name ).to eq 'Boston'
end
end
I had the same issue when testing a model that belonged to another model, when a callback was creating that association.
To explain simply, let's say I have a Book model, and a Page model, with Page belongs_to Book, and a callback to create Page when a book is created.
In my factory for Page, I try to associate to Book, but by doing so I create book once, and the creation of the page itself create the same book again. By UniqueIndex condition, PostgreSQL fails.
The simplest solution in that case is to not to create Page when testing the Page model, but instead to simply create(:book) and then use book.page.

Factory Girl: want to prevent factory girl callbacks for some traits

I have a FactoryGirl for a model class. In this model, I defined some traits. In some traits, I don't want FactoryGirl callback calling but I don't know how. For example here is my code:
FactoryGirl.define do
factory :product do
sequence(:promotion_item_code) { |n| "promotion_item_code#{n}" }
after :create do |product|
FactoryGirl.create_list :product_details, 1, :product => product
end
trait :special_product do
# do some thing
# and don't want to run FactoryGirl callback
end
end
In this code, I don't want :special_product trait calls after :create. I don't know how to do this.
#Edit: the reason I want to this because sometimes I want generate data from parent -> children. But sometimes I want vice versa generate from children to parent. So When I go from children -> parent, callback at parent is called so children is created twice. That is not what I want.
#Edit 2: My question is prevent callback from FactoryGirl, not from ActiveRecord model.
Thanks
You can use transient attributes to achieve that.
Like:
factory :product do
transient do
create_products true
end
sequence(:promotion_item_code) { |n| "promotion_item_code#{n}" }
after :create do |product, evaluator|
FactoryGirl.create_list(:product_details, 1, :product => product) if evaluator.create_products
end
trait :special_product do
# do some thing
# and don't want to run FactoryGirl callback
end
end
But I think that a better way to model this problem is to define a trait for the "base case" or to have multiple factories.
You could use the same approach as described in the Factory Girl docs for a has_many relationship:
factory :product_detail do
product
#... other product_detail attributes
end
factory :product do
sequence(:promotion_item_code) { |n| "promotion_item_code#{n}" }
factory :product_with_details do
transient do
details_count 1 # to match your example.
end
after(:create) do |product, evaluator|
create_list(:product_detail, evaluator.details_count, product: product)
end
end
trait :special_product do
# do some thing
# and don't want to run FactoryGirl callback
end
end
This allows you to generate data for the parent->children:
create(:product_with_details) # creates a product with one detail.
create(:product_with_details, details_count: 5) # if you want more than 1 detail.
...and for the special product just
# does not create any product_details.
create(:product)
create(:product, :special_product)
To generate for children->parent
create(:product_detail)

Factory Girl skips "before_add" callback on Rails Association

I can't seem to get FactoryGirl to call my before_add callback with an associated model.
I've got a Course model with:
has_many :lessons, dependent: :destroy, before_add: :set_lesson_number
def set_lesson_number
#stuff
end
a Lesson model with:
belongs_to :course.
a Lesson factory with:
FactoryGirl.define do
factory :lesson do
course
end
end
and a Course factory, defined as suggested by the Factory Girl wiki:
FactoryGirl.define do
factory :course do
factory :course_with_lessons do
transient do
lessons_count 10
end
after(:create) do |course, evaluator|
create_list(:lesson, evaluator.lessons_count, course: course)
end
end
end
end
The before_add callback doesn't get called when I do FactoryGirl.create(:lesson), but it does get called if I do this:
lesson = FactoryGirl.build(:lesson)
course = lesson.course
course.lessons << l
In both cases, course.lessons ends up including lesson.
By the same token, FactoryGirl.build(:course_with_lessons) doesn't work with the above Course factory, but if I replace the create_list line with:
evaluator.lessons_count.times do
course.lessons << build(lesson)
end
it does. It seems like FactoryGirl is creating the Lessons and setting their Course ID's, but somehow not actually "adding" them to the collection, so I have to do it manually.
Am I missing something about how FactoryGirl is supposed to work? Or about how ActiveRecord works?
This is how ActiveRecord works.
If you run the following in your rails console you'll see the before_add callback on the association is not called:
course = Course.create
Lesson.create(course_id: course.id)
I imagine FactoryGirl.create_list generates objects in a similar way.
The lesson needs to be added to the collection in order for the callback to fire. This can be done in a couple of ways.
1. Create a Lesson through course
course.lessons.create
2. Explicitly add the lesson to course.lessons
course.lessons << lesson
3. Explicitly add a collection of lesson to course.lessons
course.lessons = [lesson1, lesson2]
To get the callback to fire, you could modify your factory like so:
factory :course do
factory :course_with_lessons do
transient do
lessons_count 10
end
after(:create) do |course, evaluator|
course.lessons =
build_list(:lesson, evaluator.lessons_count, course: course)
course.save
end
end
end
Hope that helps.

stub method in FactoryGirl

I have 3 models :
Article:
has_many photos
Photo:
belongs_to article
belongs_to photoType
PhotoType:
has_many articles
And a factory :
FactoryGirl.define do
factory :article do
title 'The Batcave'
content '5 Smith Street'
after_build do |article|
article.photos << FactoryGirl.build(:photo, :article => article)
article.photos << FactoryGirl.build(:photo, :article => article)
end
end
end
In article model i have a method get_photo(type) which query the databse a return one proper photo object based on type.
My question is how can i stub this method in my factory. Now get_photo always returns nil.
stubed article.get_photo(:big) should return article.photos[0]
Ok , i found a solution.
before(:all) do
#articles = FactoryGirl.build_list(:article, 10)
end
And in test i have to add (I only need first article to test):
#articles[0].stub(:photo).with(:big).and_return(#articles[0].photos[1])
However When i put this line in before(:all) block it doesnt work . https://github.com/rspec/rspec-rails/issues/279
Mocks are implicitly verified and cleared out after(:each) so they won't work in before(:all).

FactoryGirl association model trouble: "SystemStackError: stack level too deep"

I am using Ruby on Rails 3.0.9, RSpec-rails 2 and FactoryGirl. I am trying to state a Factory association model but I am in trouble.
I have a factories/user.rb file like the following:
FactoryGirl.define do
factory :user, :class => User do
attribute_1
attribute_2
...
association :account, :factory => :users_account, :method => :build, :email => 'foo#bar.com'
end
end
and a factories/users/account.rb file like the following:
FactoryGirl.define do
factory :users_account, :class => Users::Account do
sequence(:email) {|n| "foo#{n}#bar.com" }
...
end
end
The above example works as expected in my spec files, but if in the factory :users_account statement I add the association :user code so to have
FactoryGirl.define do
factory :users_account, :class => Users::Account do
sequence(:email) {|n| "foo#{n}#bar.com" }
...
association :user
end
end
I get the following error:
Failure/Error: Unable to find matching line from backtrace
SystemStackError:
stack level too deep
How can I solve that problem so to access associated models from both sides\factories (that is, in my spec files I would like to use RoR association model methods like user.account and account.user)?
P.S.: I read the Factory Girl and has_one question and my case is very close to the case explained in the linked question. That is, I have an has_one association too (between User and Users::Account classes).
According to the docs, you can't just put both sides of the associations into the factories. You'll need to use their after callback to set an object(s) to return.
For instance, in the factories/users/account.rb file, you put something like
after(:build) do |user_account, evaluator|
user_account.user = FactoryGirl.build(:user, :account=>user_account)
end
For has_many associations, you'll need to use their *_list functions.
after(:build) do |user_account, evaluator|
user_account.users = FactoryGirl.build_list(:user, 5, :account=>user_account)
end
Note: I believe the example in the docs is a bit misleading it doesn't assign anything to the object. I believe it should be something like (note the assignment).
# the after(:create) yields two values; the user instance itself and the
# evaluator, which stores all values from the factory, including ignored
# attributes; `create_list`'s second argument is the number of records
# to create and we make sure the user is associated properly to the post
after(:create) do |user, evaluator|
user.posts = FactoryGirl.create_list(:post, evaluator.posts_count, user: user)
end
Spyle's excellent answer (still working with Rails 5.2 and RSpec 3.8) will work for most associations. I had a use case where a factory needed to use 2 different factories (or different traits) for a single has_many association (ie. for a scope type method).
What I ended up coming up with was:
# To build user with posts of category == 'Special' and category == 'Regular'
after(:create) do |user, evaluator|
array = []
array.push(FactoryBot.create_list(:post, 1, category: 'Regular')
array.push(FactoryBot.create_list(:post, 1, category: 'Special')
user.posts = array.flatten
end
This allowed the user to have 1 post of category 'Regular' and 1 post of category 'Special.'

Resources