activerecord-import: ActiveRecord association loads incorrect association for an object when importing

Steps to reproduce

We’re cloning a set of waypoints objects, each associated with participations. participations are cloned for the waypoints using dup.

I noticed that some waypoints were missing their participations associations. It turns out than these problematic waypoints created using activerecord-import initialize participations that have a waypoint_id different from the id that they’re associated with. Putting a debugger in ActiveRecord#associations, here’s the output:

    150: def association(name) #:nodoc:
    151:   association = association_instance_get(name)
 => 152:   binding.pry if name == :participations && association.present? && self.id.present?
    153:
    154:   if association.nil?
    155:     raise AssociationNotFoundError.new(self, name) unless reflection = self.class._reflect_on_association(name)
    156:     association = reflection.association_class.new(self, reflection)
    157:     association_instance_set(name, association)
    158:   end
    159:
    160:   association
    161: end

association.inspect # returns an object with waypoint_id: 205
self.id # returns 206

Because 205 != 206, these participations never successfully get saved. I’m filing the bug here instead of with Rails because creating the waypoints one by one without activerecord-import never results in the bug.

System configuration

Rails version: 4.1.6

Ruby version: ruby 2.1.1

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 21

Most upvoted comments

Yikes, that is a bit of a monkey wrench. Well, that definitely means you’ll need to break up the import into a few more pieces. The following should do the trick:

alphas = []
betas  = []
deltas = []
phis   = []
mus    = []

20.times do
  alpha = Alpha.new
  betum = Betum.new(alpha: alpha)

  betas << betum

  2.times do |i|
    start_deltum = Deltum.new(alpha: alpha)
    end_deltum = Deltum.new(alpha: alpha)

    2.times do
      start_deltum.epsilons << Epsilon.new
      end_deltum.epsilons << Epsilon.new
    end

    deltas << start_deltum
    deltas << end_deltum

    kappa = Kappa.new(start_deltum: start_deltum, end_deltum: end_deltum)
    betum.kappas << kappa

    mu = Mu.new(value: "Test #{i}")
    mus << mu

    phi = Phi.new(alpha: alpha, mu: mu)
    phis << phi

    alpha.phis << phi
  end

  alpha.betum = betum
  alphas << alpha
end

# import alphas
Alpha.import alphas, validate: false

# import deltas and epsilons
# deltas should reference alphas that already have ids from database
Deltum.import deltas, recursive: true, validate: false

# import betas and kappas
# kappas should reference deltas that already have ids from database
Betum.import betas, recursive: true, validate: false

# import mus
Mu.import mus, validate: false

# import phis
# phis should reference mus that already have ids from database
Phi.import phis, validate: false

So I sat down and found that there was some improvement that could be made activerecord-import to make this import scenario easier. You can use the example below if you pull down the latest on the master branch:

alphas = []
deltas = []
mus    = []

20.times do
  alpha = Alpha.new
  betum = Betum.new

  2.times do |i|
    start_deltum = Deltum.new
    end_deltum = Deltum.new

    2.times do
      start_deltum.epsilons << Epsilon.new
      end_deltum.epsilons << Epsilon.new
    end

    deltas << start_deltum
    deltas << end_deltum

    kappa = Kappa.new(start_deltum: start_deltum, end_deltum: end_deltum)
    betum.kappas << kappa

    mu = Mu.new(value: "Test #{i}")
    alpha.phis.build(mu: mu)
    mus << mu
  end

  alpha.betum = betum
  alphas << alpha
end

# import deltas and epsilons
Deltum.import deltas, recursive: true, validate: false

# import mus
Mu.import mus, validate: false

# import alphas, betas, kappas, phis
# kappas should reference deltas that already have ids from database
# phis should reference mus that already have ids from database
Alpha.import alphas, recursive: true, validate: false