Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #9577 (new defect)

Opened 2 years ago

Last modified 2 years ago

Adding records to a has_many collection does not work on initialization

Reported by: Zargony Assigned to: core
Priority: normal Milestone: 2.x
Component: ActiveRecord Version: edge
Severity: normal Keywords: ActiveRecord has_many collection initialization
Cc:

Description

This morning I encountered a weird problem with a has_many association. Consider the following example:

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.

I posted a more detailled story in my blog.

Change History

09/17/07 12:18:42 changed by Zargony

Btw I was able to workaround the problem by forcing a collection reload in email=:

class User
  def email= (address)
    email_addresses(true).build(:address => address)
  end
end

11/08/07 04:46:08 changed by adamc

I ran into this same problem today. Here's another example that shows the same behaviour.

class Product < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :product
end

#works as expected
>> p = Product.new 
=> #<Product id: nil, name: nil>
>> p.comments
=> []
>> p.comments.build 
=> #<Comment id: nil, title: nil, product_id: nil>
>> p.comments.loaded?
=> false

# why is p.comments an empty array? We just 
# added a new comment to the collection via 
# the above p.comments.build line
>> p.comments
=> []

>> p.comments.loaded?
=> true

# when we issued p.comments, it must've loaded the
# comments association, as p.comments.build now works
>> p.comments.build
>> p.comments
=> [#<Comment id: nil, product_id: nil>]

what's the word on this? Is the fix that we should use object.association(true).build ?

Is this related to #9989 and #9987? It seems similar to me, except in this instance the problem is occurring when using build instead of <<.

02/26/08 17:52:31 changed by minaguib

I think [8049] fixed the same problem for #9989 with regards to the << method

I think the same thing needs to be done for .build

In the meantime here's a fix you can do in your app itself. Change

  def email= (address)
    email_addresses.build(:address => address)
  end

to

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

04/16/08 17:14:37 changed by Zargony

Reported as Ticket #9 to the new bug tracker: http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/9