HABTM Association with FactoryGirl - ruby-on-rails

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

Related

query about create has_many & belongs_to records using FactoryBot

I am writing Rspec requests spec, and before it I want to build some test data using FactoryBot.
And now I have a model Game:
class Game < ApplicationRecord
has_many :game_levels
and a model GameLevel:
class GameLevel < ApplicationRecord
belongs_to :game
In my /spec/factories/game.rb:
FactoryBot.define do
factory :game do
name { :Mario }
end
end
In my spec/factories/game_level.rb:
FactoryBot.define do
factory :game_level do
name { :default }
min_level { 0 }
max_level { 100 }
game
end
end
In my spec/requests/user_plays_game_spec.rb, I simply wrote code to create game & game_level, and printed game.id, game_level.game_id. I found they are not the same. besides, game.game_levels returns nil.
before(:all) do
#game = create(:game)
#game_level = create(:game_level)
end
describe do
it do
puts #game,id, #game_level.game_id
puts #game.game_levels
expect(#game.id).to eql(#game_level.game_id)
end
end
So how do I associate a belongs_to record to a has_many record using FactoryBot?
You can associate it during the create
#game = create(:game)
#game_level = create(:game_level, game: #game)
Associations in factories are intuitive:
FactoryBot.define do
factory :template do
template_category { create(:template_category) }
end
end
Nothing fancy. If it's not working for you you might have a config issue.

FactoryGirl parent class factory randomly selecting one of its STI subclasses

I have an Animal parent class.
Bird and Monkey are subclasses that extend Animal through STI.
class Animal < ActiveRecord::Base
end
class Bird < Animal
end
class Monkey < Animal
end
Is there a way to define my FactoryGirl factories so that FactoryGirl.create(:animal) will randomly call either FactoryGirl.create(:bird) or FactoryGirl.create(:monkey) for me?
See https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md#custom-construction. You can do:
FactoryGirl.define do
factory :animal do
initialize_with do
if [true, false].sample
Monkey.new
else
Bird.new
end
end
end
end
Just a matter of preference, but I thought I'd share this alternative for future googlers:
FactoryGirl.define do
factory(:animal) do
type { %w[Monkey Bird].sample }
initialize_with do
record = new(attributes)
record.becomes(record.type.constantize)
end
end
end

Accessing an instance method in an active record query

Given the following two models:
class Wheel
belongs_to :car
def self.flat
where(flat: true)
end
and
class Car
has_many :wheels
def flats
self.wheels.flat
end
def has_flats?
flats.count > 0
end
I need a query for all cars with flat tires. I'm wondering why this isn't working in the cars model?:
def self.with_flats
where(:has_flats?)
end
or
def self.with_flats
where(:has_flats? == true)
end
This isn't returning the correct records. Any ideas?
Define a scope in Car model:
class Car
has_many :wheels
scope :having_flat_wheels, joins(:wheels).where("wheels.flat=?", true).uniq
......
end
Then to get all cars with flat tires:
Car.having_flat_wheels

Factory Girl: How to set up a has_many/through association

I've been struggling with setting up a has_many/through relationship using Factory Girl.
I have the following models:
class Job < ActiveRecord::Base
has_many :job_details, :dependent => :destroy
has_many :details, :through => :job_details
end
class Detail < ActiveRecord::Base
has_many :job_details, :dependent => :destroy
has_many :jobs, :through => :job_details
end
class JobDetail < ActiveRecord::Base
attr_accessible :job_id, :detail_id
belongs_to :job
belongs_to :detail
end
My Factory:
factory :job do
association :tenant
title { Faker::Company.catch_phrase }
company { Faker::Company.name }
company_url { Faker::Internet.domain_name }
purchaser_email { Faker::Internet.email }
description { Faker::Lorem.paragraphs(3) }
how_to_apply { Faker::Lorem.sentence }
location "New York, NY"
end
factory :detail do
association :detail_type <--another Factory not show here
description "Full Time"
end
factory :job_detail do
association :job
association :detail
end
What I want is for my job factory to be created with a default Detail of "Full Time".
I've been trying to follow this, but have not had any luck:
FactoryGirl Has Many through
I'm not sure how the after_create should be used to attach the Detail via JobDetail.
Try something like this. You want to build a detail object and append it to the job's detail association. When you use after_create, the created job will be yielded to the block. So you can use FactoryGirl to create a detail object, and add it to that job's details directly.
factory :job do
...
after_create do |job|
job.details << FactoryGirl.create(:detail)
end
end
I faced this issue today and I found a solution. Hope this helps someone.
FactoryGirl.define do
factory :job do
transient do
details_count 5 # if details count is not given while creating job, 5 is taken as default count
end
factory :job_with_details do
after(:create) do |job, evaluator|
(0...evaluator.details_count).each do |i|
job.details << FactoryGirl.create(:detail)
end
end
end
end
end
This allows to create a job like this
create(:job_with_details) #job created with 5 detail objects
create(:job_with_details, details_count: 3) # job created with 3 detail objects
This worked for me
FactoryGirl.define do
factory :job do
# ... Do whatever with the job attributes here
factory :job_with_detail do
# In later (as of this writing, unreleased) versions of FactoryGirl
# you will need to use `transitive` instead of `ignore` here
ignore do
detail { create :detail }
end
after :create do |job, evaluator|
job.details << evaluator.detail
job.save
job_detail = job.job_details.where(detail:evaluator.detail).first
# ... do anything with the JobDetail here
job_detail.save
end
end
end
end
Then later
# A Detail object is created automatically and associated with the new Job.
FactoryGirl.create :job_with_detail
# To supply a detail object to be associated with the new Job.
FactoryGirl.create :job_with_detail detail:#detail
Since FactoryBot v5, associations preserve build strategy. Associations are the best way to solve this and the docs have good examples for it:
FactoryBot.define :job do
job_details { [association(:job_detail)] }
end
FactoryBot.define :detail do
description "Full Time"
end
FactoryBot.define :job_detail do
association :job
association :detail
end
You can solve this problem in the following way:
FactoryBot.define do
factory :job do
# job attributes
factory :job_with_details do
transient do
details_count 10 # default number
end
after(:create) do |job, evaluator|
create_list(:details, evaluator.details_count, job: job)
end
end
end
end
With this, you can create a job_with_details, that has options to specify how many details you want.
You can read this interesting article for more details.
With the current factory_bot(previously factory_girl) implementation, everything is taken care by the gem, you don't need to create and then push the records inside the jobs.details. All you need is this
factory :job do
...
factory :job_with_details do
transient do
details_count { 5 }
end
after(:create) do |job, evaluator|
create_list(:detail, evaluator.details_count, jobs: [job])
job.reload
end
end
end
Below code will produce 5 detail jobs
create(:job_with_details)

How to create has_and_belongs_to_many associations in Factory girl

Given the following
class User < ActiveRecord::Base
has_and_belongs_to_many :companies
end
class Company < ActiveRecord::Base
has_and_belongs_to_many :users
end
how do you define factories for companies and users including the bidirectional association? Here's my attempt
Factory.define :company do |f|
f.users{ |users| [users.association :company]}
end
Factory.define :user do |f|
f.companies{ |companies| [companies.association :user]}
end
now I try
Factory :user
Perhaps unsurprisingly this results in an infinite loop as the factories recursively use each other to define themselves.
More surprisingly I haven't found a mention of how to do this anywhere, is there a pattern for defining the necessary factories or I am doing something fundamentally wrong?
Here is the solution that works for me.
FactoryGirl.define do
factory :company do
#company attributes
end
factory :user do
companies {[FactoryGirl.create(:company)]}
#user attributes
end
end
if you will need specific company you can use factory this way
company = FactoryGirl.create(:company, #{company attributes})
user = FactoryGirl.create(:user, :companies => [company])
Hope this will be helpful for somebody.
Factorygirl has since been updated and now includes callbacks to solve this problem. Take a look at http://robots.thoughtbot.com/post/254496652/aint-no-calla-back-girl for more info.
In my opinion, Just create two different factories like:
Factory.define :user, :class => User do |u|
# Just normal attributes initialization
end
Factory.define :company, :class => Company do |u|
# Just normal attributes initialization
end
When you write the test-cases for user then just write like this
Factory(:user, :companies => [Factory(:company)])
Hope it will work.
I couldn´t find an example for the above mentioned case on the provided website. (Only 1:N and polymorphic assocations, but no habtm). I had a similar case and my code looks like this:
Factory.define :user do |user|
user.name "Foo Bar"
user.after_create { |u| Factory(:company, :users => [u]) }
end
Factory.define :company do |c|
c.name "Acme"
end
What worked for me was setting the association when using the factory.
Using your example:
user = Factory(:user)
company = Factory(:company)
company.users << user
company.save!
Found this way nice and verbose:
FactoryGirl.define do
factory :foo do
name "Foo"
end
factory :bar do
name "Bar"
foos { |a| [a.association(:foo)] }
end
end
factory :company_with_users, parent: :company do
ignore do
users_count 20
end
after_create do |company, evaluator|
FactoryGirl.create_list(:user, evaluator.users_count, users: [user])
end
end
Warning: Change users: [user] to :users => [user] for ruby 1.8.x
For HABTM I used traits and callbacks.
Say you have the following models:
class Catalog < ApplicationRecord
has_and_belongs_to_many :courses
…
end
class Course < ApplicationRecord
…
end
You can define the Factory above:
FactoryBot.define do
factory :catalog do
description "Catalog description"
…
trait :with_courses do
after :create do |catalog|
courses = FactoryBot.create_list :course, 2
catalog.courses << courses
catalog.save
end
end
end
end
First of all I strongly encourage you to use has_many :through instead of habtm (more about this here), so you'll end up with something like:
Employment belongs_to :users
Employment belongs_to :companies
User has_many :employments
User has_many :companies, :through => :employments
Company has_many :employments
Company has_many :users, :through => :employments
After this you'll have has_many association on both sides and can assign to them in factory_girl in the way you did it.
Update for Rails 5:
Instead of using has_and_belongs_to_many association, you should consider: has_many :through association.
The user factory for this association looks like this:
FactoryBot.define do
factory :user do
# user attributes
factory :user_with_companies do
transient do
companies_count 10 # default number
end
after(:create) do |user, evaluator|
create_list(:companies, evaluator.companies_count, user: user)
end
end
end
end
You can create the company factory in a similar way.
Once both factories are set, you can create user_with_companies factory with companies_count option. Here you can specify how many companies the user belongs to: create(:user_with_companies, companies_count: 15)
You can find detailed explanation about factory girl associations here.
You can define new factory and use after(:create) callback to create a list of associations. Let's see how to do it in this example:
FactoryBot.define do
# user factory without associated companies
factory :user do
# user attributes
factory :user_with_companies do
transient do
companies_count 10
end
after(:create) do |user, evaluator|
create_list(:companies, evaluator.companies_count, user: user)
end
end
end
end
Attribute companies_count is a transient and available in attributes of the factory and in the callback via the evaluator. Now, you can create a user with companies with the option to specify how many companies you want:
create(:user_with_companies).companies.length # 10
create(:user_with_companies, companies_count: 15).companies.length # 15

Resources