Question

I'm running into an issue where FactoryGirl appears to be creating extra records with a has_many relationship.

Given these models:

class NextAction < ActiveRecord::Base
  has_many :next_actions_orders
  has_many :orders, through: :next_actions_orders
end

class NextActionsOrder < ActiveRecord::Base
  belongs_to :order
  belongs_to :next_action
end

class Order < ActiveRecord::Base
  has_many :next_actions_orders
  has_many :next_actions, through: :next_actions_orders
end

And these factories:

FactoryGirl.define do
  factory :next_action do
    status :pending

    trait :pickup do
      next_actions_orders { FactoryGirl.create_list(:next_actions_order, 1) }
      action_type :pickup
    end

    trait :multiple_pickups do
      next_actions_orders { FactoryGirl.create_pair(:next_actions_order) }
      action_type :pickup
    end
  end
end

FactoryGirl.define do
  factory :next_actions_order do
    order { FactoryGirl.create(:order) }
    next_action
  end
end

FactoryGirl.define do
  factory :order do
    status :pending
  end
end

As you can see in the NextAction factory, I ran into an issue setting up the NextActionOrder association.

I would usually have used next_actions_orders { FactoryGirl.create(:next_actions_order) } but with the has_many :next_actions_orders, I was getting an undefined method 'each' for #<NextActionsOrder... error.

next_actions_orders { FactoryGirl.create_list(:next_actions_order, 1) } seems to work as a workaround. As shown below, this doesn't seem to be the cause of the issue, since it also exists from the create_pair example.

The real issue is this:

it 'create_list generates duplicate FactoryGirl records' do
  puts NextAction.count # output: 0

  pickup = create(:next_action, :pickup)

  puts NextAction.count # output: 2

  ## binding.pry
end

Succinctly, the create(:next_action) calls seem to be generating 1 additional NextAction record than is required.

I used pry to inspect this and sure enough, you can see this.

With pry inserted at the first location shown above, queries produce the following:

pry(#<RSpec::Core::ExampleGroup::Nested_1>)> NextAction.all
=> [#<NextAction id: 2, ... created_at: "2014-04-20 16:40:57", updated_at: "2014-04-20 16:40:57", status: 0>,
 #<NextAction id: 3, ... created_at: "2014-04-20 16:40:57", updated_at: "2014-04-20 16:40:57", status: 0>]
pry(#<RSpec::Core::ExampleGroup::Nested_1>)> NextActionsOrder.all
=> [#<NextActionsOrder id: 1, next_action_id: 3, order_id: 2>]
pry(#<RSpec::Core::ExampleGroup::Nested_1>)> Order.all
=> [#<Order id: 2, created_at: "2014-04-20 16:40:57", updated_at: "2014-04-20 16:40:57", status: 0>]

Here, everything looks great, except for NextAction ID #2. It's not associated anywhere, it's just an orphan record that was created for some reason.

Here's what happens with create_pair:

it 'create_pair generates duplicate FactoryGirl records' do
  puts NextAction.count # output: 0

  pickups = create(:next_action, :multiple_pickups)

  puts NextAction.count # output: 3

  ## binding.pry
end

With pry inserted as shown, the same queries produce:

pry(#<RSpec::Core::ExampleGroup::Nested_1>)> NextAction.all
=> [#<NextAction id: 4, created_at: "2014-04-20 16:53:20", updated_at: "2014-04-20 16:53:20", status: 0>,
 #<NextAction id: 5, created_at: "2014-04-20 16:53:20", updated_at: "2014-04-20 16:53:20", status: 0>,
 #<NextAction id: 6, created_at: "2014-04-20 16:53:20", updated_at: "2014-04-20 16:53:20", status: 0>]
pry(#<RSpec::Core::ExampleGroup::Nested_1>)> NextActionsOrder.all
=> [#<NextActionsOrder id: 2, next_action_id: 6, order_id: 3>,
 #<NextActionsOrder id: 3, next_action_id: 6, order_id: 4>]
pry(#<RSpec::Core::ExampleGroup::Nested_1>)> Order.all
=> [#<Order id: 3, created_at: "2014-04-20 16:53:20", updated_at: "2014-04-20 16:53:20", status: 0>,
 #<Order id: 4, created_at: "2014-04-20 16:53:20", updated_at: "2014-04-20 16:53:20", status: 0>]

Again, everything looks good, except now we have 2 orphan NextAction records - IDs #4 and #5.

Any ideas? Thanks so much!

Was it helpful?

Solution

It's because of the next_action in the factory :next_actions_order, which creates an additional next_action...

I would rewrite the next_action factory using

after(:build) do |next_action, evaluator|
  next_action.orders << build(:order)
end

or

after(:build) do |next_action, evaluator|
  next_action.orders << build_list(:order, evaluator.orders_count)
end

if you need more than one.

This way the entire chain is created at once with all cross references filled in as they should! (and without doubles!)

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