Find or Create Record Through Factory_Girl Association

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



Leave a reply



Submit