Forum: Ruby on Rails has_one prematurely nullifies existing association when assi

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Cf4ad1e4c5e0b18193eb0d677c2739e9?d=identicon&s=25 Brian (Guest)
on 2007-03-18 23:15
(Received via mailing list)
Like a number of other Rails newbies, I'm seeing some unexpected
behavior with the has_one relationship.  I attempted to search for a
solution or explanation here and on Rails Trac, but came up short.
I'm using Rails 1.2.2.  Here are some simplified models:

class Order < ActiveRecord::Base
  has_one :invoice
  validates_associated :invoice
end

class Invoice < ActiveRecord::Base
  belongs_to :order
  validates_presence_of :description
end

The following works as expected (with output edited for brevity):

>> order = Order.create
=> #<Order:0x3506fb0 ... @attributes={"id"=>1}>
>> invoice = Invoice.create(:description => "Foo")
=> #<Invoice:0x34f24e8 ... @attributes={"order_id"=>nil, "id"=>1,
"description"=>"Foo"}>
>> order.invoice = invoice
=> #<Invoice:0x34f24e8 ... @attributes={"order_id"=>1, "id"=>1,
"description"=>"Foo"}>
>> Order.find(1).invoice
=> #<Invoice:0x34e7688 @attributes={"order_id"=>"1", "id"=>"1",
"description"=>"Foo"}>

However, when I assign an invalid Invoice to the has_one association,
the valid association is nullified in the database:

>> order.invoice = Invoice.new
=> #<Invoice:0x34e5810 ... @errors={"description"=>["can't be
blank"]} ... >
>> Order.find(1).invoice
=> nil

This behavior is surprising.  Here's another example (starting by
reassigning the valid Invoice)

>> order.invoice = invoice
>> order.build_invoice
=> #<Invoice:0x34bbd08 @new_record=true, @attributes={"order_id"=>1,
"description"=>nil}>
>> Order.find(1).invoice
=> nil

I realize that understanding how and when things are saved can be
confusing, especially with the has_one relationship.  I'm also aware
of several ways to work around this, either by adding special code to
Order, or just assigning to the belongs_to association instead.  But
can someone explain why ActiveRecord seems to be nullifying a valid
association before the new association is validated or saved?

Thanks,
Brian
Cf4ad1e4c5e0b18193eb0d677c2739e9?d=identicon&s=25 Brian Rutledge (Guest)
on 2007-03-19 20:43
(Received via mailing list)
Anyone?  Watching the development log show that ActiveRecord is
setting the order_id to null when the assignment is made.
631d1677786f0725da6ccee46043686e?d=identicon&s=25 Jonathan Viney (Guest)
on 2007-03-19 22:20
(Received via mailing list)
Save the invoice after associating it with the order.

-Jonathan.
Cf4ad1e4c5e0b18193eb0d677c2739e9?d=identicon&s=25 Brian Rutledge (Guest)
on 2007-03-20 20:36
(Received via mailing list)
I'm not sure what you're trying to say...yes, if I just assigned the
the order to the invoice, then saved the invoice, that would
workaround this issue.  But that doesn't answer my question.  While
playing around with this, it also looks like AR is nullifying the
association in the Invoice object, as well.  So, for example:

>> order = Order.create
=> #<Order:0x3506fb0 ... @attributes={"id"=>1}>
>> invoice = invoice.create(:description => "Foo")
=> #<Invoice:0x34f24e8 ... @attributes={"order_id"=>nil, "id"=>1,
"description"=>"Foo"}>
>> order.invoice = invoice
=> #<Invoice:0x34f24e8 ... @attributes={"order_id"=>1, "id"=>1,
"description"=>"Foo"}>

>> Order.find_first.invoice
=> #<Invoice:0x34e79f8 @attributes={"order_id"=>"1", "id"=>"1",
"description"=>"Foo"}>
>> Invoice.find_first.order
=> #<Order:0x34e2cc8 @attributes={"id"=>"1"}>

>> order.build_invoice
=> #<Invoice:0x34e1a58 @new_record=true, @attributes={"order_id"=>1,
"description"=>nil}>
>> invoice.order
=> nil
>> Invoice.find_first.order
=> nil

Why is the Invoice with id = 1 no longer referencing order with id =
1?  All I've done is build a new invoice...I haven't tried to save it,
and it's not even valid.  And I can't just re-save the original
Invoice object, because it also no longer references the order.  This
seems like a bad side-effect to me.

As I've said, I'm a Rails newbie, and it seems like the general wisdom
is to assign to the belongs_to side of an association, instead of the
has_one side.  But it would be nice if the has_one side didn't have
surprising side-effects.  Is this a well-known bug that I just didn't
search for long enough before I posted to this group?

Brian
40db9e75b3f5899258e3bdc0c9210154?d=identicon&s=25 Conrad Taylor (Guest)
on 2007-03-20 21:08
(Received via mailing list)
Hi, if the invoice is invalid, it isn't saved to the database.
Therefore, you're overwriting the previous value of the invoice
associated to the order.  Thus, invoking find returns nil.  Well, I
wish that this helps.

Good luck,

-Conrad
40db9e75b3f5899258e3bdc0c9210154?d=identicon&s=25 Conrad Taylor (Guest)
on 2007-03-21 07:02
(Received via mailing list)
Hi, here's a better explaination with the relevant code that you have
posted:

1)  >> order.invoice = Invoice.new
     => #<Invoice:0x34e5810 ... @errors={"description"=>["can't be
     blank"]} ... >
2)  >> Order.find(1).invoice
     => nil

Line 1 =>  We're assigning the result or 'Invoice.new' where all field
are initialized to
                their respective default values.  Also, the
validations are not invoked until
                you attempt to invoke save or save! methods.  Thus,
you're assigning an
                invalid invoice instance to the order.invoice

Line 2 =>  Now, we're trying to find an invoice instance that results
in nil being returned
                because the invoice instance in (1) was never saved
the database.

Finally, I would recommend taking a look at the relevant chapters in
AWDwRv2 for additional information.

Good luck,

-Conrad
This topic is locked and can not be replied to.