Question

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
Was it helpful?

Solution

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

OTHER TIPS

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.id = Country.create_with(name: instance.name).find_or_create_by(slug: instance.slug).id
    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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top