Using Factory_Girl in Rails with Associations That Have Unique Constraints. Getting Duplicate Errors

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:

  1. Accept attributes as an argument your execute method.
  2. 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



Leave a reply



Submit