This project is archived and is in readonly mode.

#9 ✓resolved
Andreas Neuhaus

Adding records to a has_many collection does not work on initialization

Reported by Andreas Neuhaus | April 16th, 2008 @ 05:54 PM

(from Trac http://dev.rubyonrails.org/ticke.... As far as I know, this is still an issue)

class User < AR:Base
  has_many :email_addresses
  def email= (address)
    email_addresses.build(:address => address)
  end
end

class EmailAddress < AR:Base
  belongs_to :user
end

I use User#email= to create an initial email address record for each user created. It works fine, except if you access User#email_addresses on non-saved user records:

# Works fine (creates user record and associated email address record):
User.create!(:username=>'foo', :email=>'foo@bar.baz')

# Also works fine:
u = User.new(:username=>'foo', :email=>'foo@bar.baz')
u.save!

# Does NOT work (user record saved, email address record lost):
u = User.new(:username=>'foo', :email=>'foo@bar.baz')
u.email_addresses   # => []
u.save!

Is this a bug or intended behaviour? It looks like the has_many collection cache does not recognize associated objects that are build during initialization.

minaguib suggested a temp workaround:

  def email= (address)
    email_addresses(true) unless email_addresses.loaded? # Temp fix for for rails bug #9577
    email_addresses.build(:address => address)
  end

Some more info in my blog

Comments and changes to this ticket

  • josh

    josh April 16th, 2008 @ 08:16 PM

    • Assigned user set to “josh”

    I wrote up some ActiveRecord tests for it, however they are passing for me. Did you try this on edge? Or are my test cases not covering the same thing you are referring too?

  • josh

    josh April 16th, 2008 @ 08:17 PM

    • State changed from “new” to “open”
  • Simon Jefford

    Simon Jefford April 16th, 2008 @ 10:43 PM

    I've had a quick poke around with this and I can't seem to get a failing test either. However, I was able to replicate the problem in a Rails app using the exact models and code described by zargony.

  • Simon Jefford

    Simon Jefford April 17th, 2008 @ 01:36 PM

    I can only get this to happen inside script/console.

    Again, using the models described by Andreas, I ran the following using script/runner:

    u = User.new(:username => 'foo', :email => 'foo@bar.baz')
    u.email_addresses
    u.save!
    puts u.email_addresses.first.address
    

    and got

    foo@bar.baz.

  • Andreas Neuhaus

    Andreas Neuhaus April 17th, 2008 @ 02:37 PM

    Interestingly, the test also passes for me. But the bug still occurs when using the console of a project where I disabled the described workaround.

    Though I don't see why, there must be something wrong with the test. I'll do some more tests later today.

  • Andreas Neuhaus

    Andreas Neuhaus April 17th, 2008 @ 03:33 PM

    Joshua: yes, I'm trying with rails edge pulled from git.

    The problem occurs, if you create an object of a model which builds associated objects during initialization. If you access these associated objects before saving the object itself, all associated objects are lost.

    I guess, that this problem might be related to the lazy-loading of an associated collection. If an object builds associated objects in its initializer, the association is still marked as loaded?==false; so on first access to the association proxy, it'll try to load the collection and wipes the ones built in memory.

    For some reason, it looks like accessing email_addresses.first is ok, but accessing email_addresses or doing email_addresses.inspect results in an empty array:

    >> u=User.new(:username=>'foo', :email=>'foo@bar.baz')
    => #<User id: nil, username: "foo", created_at: nil, updated_at: nil>
    >> u.email_addresses.loaded?
    => false
    >> u.email_addresses.first
    => #<EmailAddress id: nil, address: "foo@bar.baz", user_id: nil, created_at: nil, updated_at: nil>
    >> u.email_addresses.loaded?
    => true
    >> u.email_addresses
    => [#<EmailAddress id: nil, address: "foo@bar.baz", user_id: nil, created_at: nil, updated_at: nil>]
    >>
    ?> u=User.new(:username=>'foo', :email=>'foo@bar.baz')
    => #<User id: nil, username: "foo", created_at: nil, updated_at: nil>
    >> u.email_addresses.loaded?
    => false
    >> u.email_addresses
    => []
    >> u.email_addresses.loaded?
    => true
    

    I can reproduce this with script/console and with script/runner, but for some reason not with a test...

  • josh

    josh April 19th, 2008 @ 10:38 PM

    • State changed from “open” to “hold”

    Please reopen this ticket once you've got some tests.

  • Andreas Neuhaus

    Andreas Neuhaus April 21st, 2008 @ 01:54 PM

    I think I finally tracked it down. It's the inspect method of AssociationProxy that does a reload if the target isn't loaded yet and therefore wipes the collection in this case.

    The console automatically does #inspect to display an object. That's why it always happened in the console but not in tests.

    I attached a patch that fixes this problem and adds a proper test.

    Unfortunately, I cannot reopen this ticket myself.

  • Michael Koziarski

    Michael Koziarski May 2nd, 2008 @ 11:36 PM

    • State changed from “hold” to “open”
  • Repository

    Repository May 8th, 2008 @ 06:07 AM

    • State changed from “open” to “resolved”

    (from [bcb090c56b842a76397e0ea32f54c942fd11910e]) Calling ActiveRecord#inspect on an unloaded association won't wipe the collection [#9 state:resolved]

    Signed-off-by: Joshua Peek

    http://github.com/rails/rails/co...

  • Ryan Bigg

    Ryan Bigg November 8th, 2010 @ 01:49 AM

    • Tag cleared.

    Automatic cleanup of spam.

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

<h2 style="font-size: 14px">Tickets have moved to Github</h2>

The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>

Referenced by

Pages