Find or create record through factory_girl association
I ended up using a mix of methods found around the net, one of them being inherited factories as suggested by duckyfuzz in another answer.
I did following:
# in groups.rb factory
def get_group_named(name)
# get existing group or create new one
Group.where(:name => name).first || Factory(:group, :name => name)
end
Factory.define :group do |f|
f.name "default"
end
# in users.rb factory
Factory.define :user_in_whatever do |f|
f.group { |user| get_group_named("whatever") }
end
find_or_initialize_by in FactoryGirl
You should be able to make this work by using initialize_with
:
FactoryGirl.define do
factory :country do
name "United States"
slug "us"
initialize_with { Country.find_or_create_by_name(name) }
end
end
This will always use the same country. You may want to nest the factory to allow other factories to use different names:
FactoryGirl.define do
factory :country do
initialize_with { Country.find_or_create_by_name(name) }
factory :united_states do
name "United States"
slug "us"
end
end
end
Factory Girl create association with existing object
You could achieve what you want by both not using a factory for your Gender instances and using callbacks in your Customer factory.
You're not gaining a lot from the Gender factory as it stands (although I appreciate it may be simplified for the purposes of asking this question). You could just as easily do this to get a Gender:
Gender.find_or_create_by_code!('Male')
Then in your Customer factory you can use a before callback to set the Gender:
factory :dumas, :class => Customer do
first_name 'Mary'
last_name 'Dumas'
before_create do |customer|
customer.gender = Gender.find_or_create_by_code!('Female')
end
end
The format for using callbacks in factory_girl has recently changed so if you're using 3.3 or later you'll need to do:
before(:create) do |customer|
instead of before_create
.
If creating a gender is more complex than your example you can still create the genders using a factory but hook them up with your customers using callbacks using find_by_code!
insteand of find_or_create_by_code!
.
Rails Factory Girl: Create record associated with user but with role
You can always override fields by passing them to the build
or create
methods. To set event_operator
to your desired user
, you would do this:
user = build(:user)
event = build(:event, event_operator: user)
Rails with FactoryGirl, parent-child association. Omit creating one more record in child model
FactoryGirl
creates all the stated associations for the model during the build process. Which means a FactoryGirl.build :keyword
will do a FactoryGirl.create :tag
so it will have an id for Keyword#tag_id
to help pass validations on the Keyword
model.
This is consistent with the database activity you are seeing.
irb(main):023:0> FactoryGirl.create :keyword
### keywordA = Keyword.new
### call create(:tag) because of association
### tag1 = Tag.new
### call build(:keyword) in after(:build)
###.keywordB.new(tag: tag1) # which prevents trying to make a new tag!
### tag1.save # which saves the keywordB
(0.1ms) BEGIN
Keyword Exists (0.7ms) SELECT 1 AS one FROM "keywords" WHERE "keywords"."name" = $1 LIMIT $2 [["name", "Keyword1"], ["LIMIT", 1]]
Tag Exists (0.3ms) SELECT 1 AS one FROM "tags" WHERE "tags"."name" = $1 LIMIT $2 [["name", "Tag1"], ["LIMIT", 1]]
SQL (0.5ms) INSERT INTO "tags" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["name", "Tag1"], ["created_at", 2017-01-21 19:20:14 UTC], ["updated_at", 2017-01-21 19:20:14 UTC]]
SQL (0.6ms) INSERT INTO "keywords" ("tag_id", "name", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["tag_id", 36], ["name", "Keyword1"], ["created_at", 2017-01-21 19:20:14 UTC], ["updated_at", 2017-01-21 19:20:14 UTC]]
(10.4ms) COMMIT
### keywordA.tag = tag1
### keywordA.save
(0.1ms) BEGIN
Keyword Exists (0.4ms) SELECT 1 AS one FROM "keywords" WHERE "keywords"."name" = $1 LIMIT $2 [["name", "Keyword2"], ["LIMIT", 1]]
SQL (0.4ms) INSERT INTO "keywords" ("tag_id", "name", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["tag_id", 36], ["name", "Keyword2"], ["created_at", 2017-01-21 19:20:14 UTC], ["updated_at", 2017-01-21 19:20:14 UTC]]
(4.4ms) COMMIT
### Since keywordA gets saved after keywordB,
### keywordB gets a 1 from the sequence and
### keywordA gets a 2 from the sequence
=> #<Keyword id: 63, tag_id: 36, name: "Keyword2", created_at: "2017-01-21 19:20:14", updated_at: "2017-01-21 19:20:14">
irb(main):024:0>
This is just the gist of what happens. Personally, I cannot imagine wanting a keyword
without it's tag
based on the database's schema so I would just call create(:tag)
and get the first keyword
as mentioned before. But the schema is simple enough so the following should hold up in 100% of the situations we would want to test:
FactoryGirl.define do
factory :tag do
sequence(:name) { |n| "Tag#{n}" }
after(:build) do |this|
this.keywords << build(:keyword) if this.keywords.empty?
end
end
end
FactoryGirl.define do
factory :keyword do
sequence(:name) { |n| "Keyword#{n}" }
after(:build) do |this|
this.tag ||= build(:tag)
end
end
end
build(:tag) # unsaved
build(:tag).keyword # also unsaved
create(:tag) # saved
create(:tag).keyword # also saved
build(:keyword) # unsaved
build(:keyword).tag # also unsaved
create(:keyword) # saved
create(:keyword).tag # also saved
# And it still lets you be specific
create(:tag, keywords: [create(:keyword, name: "More of a phrase")])
create(:keyword, tag: create(:tag, name: "Pop Me!"))
A few more options to consider:
# Fake the association
FactoryGirl.define do
factory :keyword do
sequence(:name) { |n| "Keyword#{n}" }
tag_id 1 # Danger!
# Will make it pass validation but you
# will forget and #tag will not be found
# or not what you expect
end
end
# use a trait
FactoryGirl.define do
factory :keyword do
sequence(:name) { |n| "Keyword#{n}" }
trait :with_tag do
tag
end
end
end
# make a new factory
FactoryGirl.define do
# do not need parent if inside the "factory :tag do"
factory :tag_with_keyword, parent: :tag do
sequence(:name) { |n| "Tag#{n}" }
keyword
end
end
# but now we are back to it creating the keyword on build(:tag)
FactoryGirl
does give you enough options to solve many situations but the trick is understanding how it sets up the associations and try to stay away from setting up implicit circular ones.
How to create an associated record within factory girl to pass validations at creation
factory :donation_item do
name "20 Cookies"
help_request
end
factory :help_request do
name "Mrs. Bourque's holiday party"
description "We need volunteers to help out with Mrs. Bourque's holiday party."
end
Then in your spec:
let(:donation_item) { FactoryGirl.create(:donation_item, help_request: FactoryGirl.create(:help_request)) }
Edit
Do not include help_request
assocation in the :donation_item factory, and do this in your test:
let(:help_request) do
help_request = build(:help_request)
help_request.donation_items << build(:donation_item)
help_request.save!
help_request
end
subject { help_request }
Factory Girl: Creating associated records
you should use after :build
and build_list
and assign the list to the user's association, like so:
after(:build) do |user, evaluator|
user.stories << build_list(:story, evaluator.stories_count, user: user)
end
Using factory_girl in Rails with associations that have unique constraints. Getting duplicate errors
I encountered the same problem and added a lambda at the top of my factories file that implements a singleton pattern, which also regenerates the model if the db has been cleared since the last round of tests/specs:
saved_single_instances = {}
#Find or create the model instance
single_instances = lambda do |factory_key|
begin
saved_single_instances[factory_key].reload
rescue NoMethodError, ActiveRecord::RecordNotFound
#was never created (is nil) or was cleared from db
saved_single_instances[factory_key] = Factory.create(factory_key) #recreate
end
return saved_single_instances[factory_key]
end
Then, using your example factories, you can use a factory_girl lazy attribute to run the lambda
Factory.define :product do |p|
p.product_type { single_instances[:add_product_type] }
#...this block edited as per comment below
end
Voila!
rails 3 - FactoryGirl create associated records
So my other answer helped me do what i needed to do; however, i was needing to basically go one level of associations deeper and i was hitting walls with that solution. I ended re-reading the FactoryGirl documentation for associations and came up with this solution that works in all my cases. It creates songs, song_arrangements, and song_arrangement_files. I'm sure the code isn't pretty, but it works and can be improved upon later. Hope this helps anyone running into the same type of roadblocks.
FactoryGirl.define do
factory :song do |s|
s.sequence(:title) { |n| "Song Title #{n}" }
s.sequence(:artist) { |n| "Song Artist #{n}" }
factory :song_with_song_arrangements do
ignore do
song_arrangements_count 100
end
after(:create) do |song, evaluator|
FactoryGirl.create_list(:song_arrangement, evaluator.song_arrangements_count, song: song)
end
end
end
factory :song_arrangement do |sa|
song
sa.sequence(:title) { |n| "Arrangement #{n}" }
original_key 'A'
bpm 75
sa.sequence(:chart_content) { |n| "This is the chart content for Arrangement #{n}." }
chart_mapping 'V1, C, V2, C, B, C, C'
sa.sequence(:notes) { |n| "These are notes for the Arrangement #{n}." }
factory :song_arrangement_with_song_arrangement_files do
ignore do
song_arrangement_files_count 100
end
after(:create) do |song_arrangement, evaluator|
FactoryGirl.create_list(:song_arrangement_file, evaluator.song_arrangement_files_count, song_arrangement: song_arrangement)
end
end
end
factory :song_arrangement_file do |saf|
song_arrangement
song
saf.sequence(:title) { |n| "Attachment #{n}" }
url 'http://www.google.com'
saf.sequence(:description) { |n| "This is the description of Attachment #{n}." }
end
end
Code used to call these factories:
Songs:
before(:each) { FactoryGirl.create_list(:song, 25) }
Song Arrangements:
before(:each) { FactoryGirl.create(:song_with_song_arrangements) }
Song Arrangement Files:
before(:each) { FactoryGirl.create(:song_arrangement_with_song_arrangement_files) }
Related Topics
Openssl VS Gpg for Encrypting Off-Site Backups
How to Generate a Random Number Between a and B in Ruby
How to Make Capybara Check for Visibility After Some Js Has Run
Rails If Object.Nil? Then Magic '' in Views
How to Write a Web Scraper in Ruby
How to Install Ruby on Rails 3 on Osx
Rake Aborted, Error While Trying to Load the Gem 'Uglifier'
Converting Upper-Case String into Title-Case Using Ruby
Error Installing Debugger-Linecache in Ruby 1.9.3
Ruby - Elegantly Convert Variable to an Array If Not an Array Already
Finding What Is Common to Two Arrays
What Should Be Removed from Public Source Control in Ruby on Rails
Are There Any Ipython-Like Shells for Ruby or Rails
Rails - Invalid Authenticity Token After Deploy