Factory_bot: Passing certain attributes to #build or #create clears out other factory-defined attributes

Created on 4 May 2013  路  5Comments  路  Source: thoughtbot/factory_bot

I am using factory_girl 4.2.0 (not factory_girl_rails) with rails 4.0.0.rc1 and sequel 3.46.0.

Given these factory definitions:

factory :user do
  username    { generate :username }
  provider_id { generate :provider_id }
  provider 'soundcloud'
end

sequence :username do |i|
  Faker::Internet.user_name + i.to_s
end

sequence :provider_id do |i|
  Faker::Address.zip + i.to_s
end

When I build a user without passing any attributes, it works as expected:

puts build(:user).inspect # => #<User @values={:username=>"dana1", :provider_id=>"884101", :provider=>"soundcloud"}>

And when I provide certain attributes, say :username, it still works as expected:

puts build(:user, username: 'Alex').inspect # => #<User @values={:username=>"Alex", :provider_id=>"628062", :provider=>"soundcloud"}>

However, when I provide provider or provider_id, the other is discarded:

puts build(:user, provider: 'twitter').inspect # => #<User @values={:username=>"adella.hyatt2", :provider=>"twitter"}>
puts build(:user, provider_id: '1234').inspect # => #<User @values={:username=>"camron3", :provider_id=>"1234"}>

The issue arose because I have not null constraints on provider and provider_id columns, which were raising SQL exceptions when calling #create.

Here is my user table schema:

DB.create_table :users do
  primary_key :user_id

  String :provider_id, null: false
  String :provider,    null: false
  String :username,    null: false

  index [:provider_id, :provider], unique: true
end

And there are no validations/callbacks on the User model.

Won't be at all surprised if the bug is on my end, but I haven't been able to track anything down yet.

Thanks for your help!

Most helpful comment

@alexgenco we actually have some logic in FG to treat {name} and {name}_id as the same column and strip it out manually. This is done with an array: FactoryGirl.aliases. If you clear this array, it should resolve the issue, but the caveat is that other things may break (a lot depends on how you've structured your DB/models). I'd reconsider how you've set up your database - either by using provider_id/provider_name or just creating a Provider model, table, and leaving provider_id as a FK on users.

As for the save! issue, you can actually override to_create for all of your models:

FactoryGirl.define do
  to_create { |instance| instance.save }

  factory :foo do
    # ...
  end

  factory :bar do
    # ...
  end
end

All 5 comments

Also, I'm monkey-patching Sequel::Model to allow FactoryGirl to call #save!:

Sequel::Model.send :alias_method, :save!, :save

Unless there is a way to configure FactoryGirl to use #save instead of #save!, you will have to do this as well to reproduce the issue.

@alexgenco we actually have some logic in FG to treat {name} and {name}_id as the same column and strip it out manually. This is done with an array: FactoryGirl.aliases. If you clear this array, it should resolve the issue, but the caveat is that other things may break (a lot depends on how you've structured your DB/models). I'd reconsider how you've set up your database - either by using provider_id/provider_name or just creating a Provider model, table, and leaving provider_id as a FK on users.

As for the save! issue, you can actually override to_create for all of your models:

FactoryGirl.define do
  to_create { |instance| instance.save }

  factory :foo do
    # ...
  end

  factory :bar do
    # ...
  end
end

Ah, fair enough. provider_name it is, then. And sorry, I must have missed to_create in the docs.

Thanks again!

@alexgenco no problem, and no worries about the to_create!

For anyone finding this, the other option if you can't rename your columns is to set alias up to simply ignore that particular match like so:

FactoryGirl.aliases = [
  [/([^(?:provider)].+)_id/, '\1'],
  [/([^(?:provider)].*)/, '\1_id'],
]

This worked well for us. That should match everything except for provider and provider_id attributes.

Was this page helpful?
0 / 5 - 0 ratings