问题
I was wondering if there's an equivalent for find_or_initialize_by
in FactoryGirl that solve teh following issue:
The objective is that the model uses two tables that have the same country. I don't want to use a sequence for the country (as I found for Emails).
There's a uniqueness constraint on Country, but my main issue is that it create twice the same record of Country when I call once FactoryGirl.create(:click)
Thus, the Validation fail in the test.
Rspec:
# models/click_spec.rb
describe Click do
it "should have a valid constructor" do
FactoryGirl.create(:click).should be_valid
end
end
Factories:
# factories/countries.rb
FactoryGirl.define do
factory :country do
name "United States"
slug "us"
end
end
# factories/offers.rb
FactoryGirl.define do
factory :offer do
association :country, factory: :country
# Other columns
end
end
# factories/users.rb
FactoryGirl.define do
factory :user do
association :country, factory: :country
# Other columns
end
end
# factories/clicks.rb
FactoryGirl.define do
factory :click do
association :offer, factory: :offer
association :user, factory: :user
# Other columns
end
end
Model:
class Country < ActiveRecord::Base
validates :name, :slug,
presence: true,
uniqueness: { case_sensitive: false }
validates :slug,
length: { is: 2 }
end
回答1:
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
回答2:
I faced similar issues, also with the Country
model of my application. Here's what I did.
To ensure FactoryBot's build
and create
still behaves as it should, we should only override the logic of to_create
, by doing:
factory :country do
to_create do |instance|
instance.attributes = Country.create_with(name: instance.name).find_or_create_by(slug: instance.slug).attributes
instance.reload
end
name { "United States" }
slug { "us" }
end
Query explained:
Country
.create_with(name: instance.name) # if not found, create with this `name` (and `slug` defined below)
.find_or_create_by(slug: instance.slug) # find by primary key `slug'
This ensures build
maintains it's default behavior of "building/initializing the object" and does not perform any database read or write so it's always fast. Only logic of create
is overridden to fetch an existing record if exists, instead of attempting to always create a new record.
Originally posted on https://stackoverflow.com/a/55235861/3956879.
Check out my article explaining this.
来源:https://stackoverflow.com/questions/22874292/find-or-initialize-by-in-factorygirl