Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

Adding records to a has_many collection does not work on initialization

#9

(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

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

State: resolved
Milestone: none
Assigned to: josh josh
Importance: none

Activity

  1. josh
    josh
    • 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?

    April 16th, 2008 @ 08:16 PM

  2. josh
    josh
    • State changed from new to open

    April 16th, 2008 @ 08:17 PM

  3. Simon Jefford
    Simon Jefford

    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.

    April 16th, 2008 @ 10:43 PM

  4. Simon Jefford
    Simon Jefford

    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.

    April 17th, 2008 @ 01:36 PM

  5. Andreas Neuhaus
    Andreas Neuhaus

    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.

    April 17th, 2008 @ 02:37 PM

  6. Andreas Neuhaus
    Andreas Neuhaus

    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...

    April 17th, 2008 @ 03:33 PM

  7. josh
    josh
    • State changed from open to hold

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

    April 19th, 2008 @ 10:38 PM

  8. Andreas Neuhaus
    Andreas Neuhaus

    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.

    April 21st, 2008 @ 01:54 PM

  9. Michael Koziarski
    Michael Koziarski
    • State changed from hold to open

    May 2nd, 2008 @ 11:36 PM

  10. Repository
    Repository
    • 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...

    May 8th, 2008 @ 06:07 AM

  11. Ryan Bigg
    Ryan Bigg
    • Tag cleared.

    Automatic cleanup of spam.

    November 8th, 2010 @ 01:49 AM