The problem, I believe, stems from the fact that ActiveRecord
associations are not bidirectional. In other words, doing
@category.products << some_product does not automatically populate
the corresponding some_product.categories collection with @category,
until after you save.
That's true; perhaps a more graphic example of the design is this:
p = Page.find(1) # select * from pages where id = 1
=> #<Page:0xb7381ec4 @attributes={...}>
g.page_fragments.first # select * from page_fragments
# where page_id = 1
=> #<PageFragment:0xb73498f8 @attributes={...}>
g.page_fragments.first.page # select * from pages where id = 1
=> #<Page:0xb72f89bc @attributes={...}>
Note that the Page object returned by the last expression is a different
object from 'p'. And, as I've annotated above, the last expression will
do another database query, even though the 'correct' object is already
available in memory. Objects created by loading an association don't
have back references to the object they are associated with.
One place where this can bite is if you're trying to use callbacks to
update an associated record in the same way you might have used database
triggers in the past. For example, imagine an order table and a line
item table. You might want the order table to have a total field that is
maintained as the total value of the associated line items. One way that
you might implement part of this would be to have the LineItem
before_destroy callback subtract the line item value from the order
total:
def before_destroy
order.total -= total
end
and you might expect to write something like
o = Order.find(id)
o.line_items.first.destroy
within the callback 'order' does not refer to the same object as 'o',
which has two implications:
1) the instance of order that the callback updates is never saved.
2) if you add a 'save!' call to the callback the total gets updated but
'o' then refers to an object that is out of date, so you need to reload
it before you use the value of 'total'.
I found it difficult to implement model objects that transparently
maintained a cached total like this in a way that worked reliably: most
of the ways I tried would only work properly when the client code was
written in certain ways (i.e. based on an understanding of what the
implementation was doing). I wondered whether it was a bug, but as Jamis
said it seems to be more an artifact of the current design and
implementation. I do wonder whether it would be possible to make the
behaviour more consistent with what might be expected, though.
-Mark.