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!
FactoryGirl in Rails - Associations w/ Unique Constraints
You need to do two things to make this work:
- Accept attributes as an argument your
execute
method. - Key off of both the factory name and the attributes when creating the singleton factories.
Note that step 1 isn't sufficient to solve your problem. Even if you allow execute
to accept attributes, the first call to execute(:matchup, attributes)
will cache that result and return it any time you execute(:matchup)
, even if you attempt to pass different attributes to execute
. That's why you also need to change what you're using as the hash key for your @@singletons
hash.
Here's an implementation I tested out:
module FactoryGirl
class Singleton
@@singletons = {}
def self.execute(factory_key, attributes = {})
# form a unique key for this factory and set of attributes
key = [factory_key.to_s, '?', attributes.to_query].join
begin
@@singletons[key] = FactoryGirl.create(factory_key, attributes)
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotUnique
# already in DB so return nil
end
@@singletons[key]
end
end
end
The key is a string consisting of the factory name and a query string representation of the attributes hash (something like "matchup?event=6&matchupable=2"
). I was able to create multiple different matchups with different attributes, but it respected the uniqueness of the event/matchupable combination.
> e = FactoryGirl.create(:event)
> bm = FactoryGirl.create(:baseball_matchup)
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm)
> m.id
2
> m = FactoryGirl::Singleton.execute(:matchup, :event => e, :matchupable => bm)
> m.id
2
> f = FactoryGirl.create(:event)
> m = FactoryGirl::Singleton.execute(:matchup, :event => f, :matchupable => bm)
> m.id
3
Let me know if that doesn't work for you.
Factory_Girl not generating unique records
Once I fixed the user factories to create the related many properly, I then discovered that I had written this factory incorrectly based on the way I was defining scopes in my models. Once that was fixed, the "broken" test began working.
in factory girl / factory bot, how can I have a conditional seed for a second lookup table
It seems that you want seed data. You can create your own seeds class to cache the necessary data:
class Seeds
def [](name)
all.fetch(name)
end
def register(name, object)
all[name.to_sym] = object
end
def setup
register :publisher_level_1, FactoryGirl.create(:publisher_level, :some_trait)
register :publisher_level_2, FactoryGirl.create(:publisher_level, :some_other_trait)
end
private
def all
@all ||= {}
end
end
And then in your test_helper.rb
, call:
require 'path/to/seeds.rb'
Seeds.setup
Finally, refer it in your factories:
factory :company do
publisher_level { Seeds[:publisher_level_1] }
end
This code is just an example of the usage, you will have to tweak it to make it behave according to your needs.
FactoryGirl to create duplicate instance
In general, testing uniqueness is pretty simple. Let the shoulda matchers do the work for you. Once you have a factory you can do this...
specify {
create :city;
is_expected.to validate_uniqueness_of(:name).scoped_to(:country_code) }
You must have at least one record created. Then the shoulda matcher will use that record, read its attributes, and make a duplicate to test your validation.
factory_girl / factory_bot deeply nested associations without after(:create)
This is as close as I got, and it excludes the validation that a blog
cannot be created without at least one post
. I couldn't figure that out (with factories, with straight rails I could do it). However, I did figure out how to get before(:create)
to work as opposed to using after(:create)
.
Ultimately: it boiled down to a misunderstanding of how associations know about each other before they are saved, and how associations naturally save at the same time.
The detailed association reference rails documentation helped me out a lot, combined with building records with associations, saving one of of those records, and then observing how they all save at the same time due to the belongs_to
or the has_many
associations specified on the model.
With that knowledge: these two validations on the Post
model is what was causing most of the issues:
validates_presence_of :user_id, :blog_id
The issue with this (in regards to the code in this question) is that the blog_id
will not exist when the associations are built but not yet saved, so in some situations it will be invalid with the factories.
So we don't really want to confirm that the foreign key is present (ex: blog_id
). Instead, we want to validate that the association exists. In other words: it was simply a misuse of the presence validation for associations. So change the validation to this:
validates_presence_of :user, :blog
Now all that is left to do is write the factory the right way:
factory :blog_with_post do
before(:create) do |blog, eval|
create(:post, blog: blog, user: blog.user)
end
end
Usage
@user = create(:user)
create(:blog_with_post, user: @user)
And with that: the blog
and the user
are both associated to the post
, the user
is associated to the post
, and all are saved at the same time.
I'm using a sequence in Factory Girl to get unique values but I'm getting validation errors
The problem was that code
and name
attributes were not so called lazy attributes. I had thought of writing something like:
Factory.define :lga do |l|
l.code { |n| "lga_#{n+10000}" }
end
but I wanted to reuse the id in the name
attribute too. You can make sure
than id
is evaluated each time Factory.create
is called by putting it in an after_build
hook.
Factory.define :lga do |l|
l.after_build do |lga|
id = Factory.next :lga_id
lga.code = "lga_#{id}"
lga.name = "LGA #{id}"
end
end
This only works in FactoryGirl 1.2.3 and above.
Related Topics
Using Factory_Girl in Rails with Associations That Have Unique Constraints. Getting Duplicate Errors
Determine Ruby Version from Within Rails
How to Get the Name of the Command Called for Usage Prompts in Ruby
Difference Between @Instance_Variable and Attr_Accessor
How to Do a Safe Join Pathname in Ruby
Paperclip Renaming Files After They'Re Saved
Open the Default Browser in Ruby
Rails 4 Unpermitted Parameters for Array
Libxml2 Missing MAC Os X 10.10
Calling Another Method in Super Class in Ruby
Get Parent Directory of Current Directory in Ruby
How to Find the Key of the Largest Value Hash
How to Combine Overlapping Time Ranges (Time Ranges Union)