ActiveRecord Hangs On to Destroyed Relations
Rails' ActiveRecord has a nifty #destroy
method that almost everyone already knows about. The documentation for #destroy
1 helpfully states
Deletes the record in the database and freezes this instance to reflect that no changes should be made (since they can’t be persisted).
What is less clear is how it works when you call it on a relationship. To be sure, it destroyed the database record. What it also unfortunately does it hangs on to the reference in the parent object.
Let's throw down with some sample code.
class Thing < ActiveRecord::Base
has_one :child
end
class Child < ActiveRecord::Base
belongs_to :thing
end
zengarden> thing = Thing.new
=> #<Thing id: nil, name: nil, created_at: nil, updated_at: nil>
zengarden> thing.save
=> true
zengarden> thing.create_child :name => "Child"
=> #<Child id: 4, name: "Child", thing_id: 1,...>
zengarden> thing.child
=> #<Child id: 4, name: "Child", thing_id: 1...>
zengarden> thing.child.destroy
=> #<Child id: 4, name: "Child", thing_id: 1...>
zengarden> thing.child
=> #<Child id: 4, name: "Child", thing_id: 1...>
Arg. I just destroyed it! I don't want it hanging around in my cache. If I really wanted to save it, I should have saved it before or during the destroy call. I can of course invalidate my cache by calling
thing.child(true)
which correctly returns nil. But if I don't know to do that immediately after the destroy, I get errors like
TypeError: can't modify frozen hash
because code used later naïvely calls
if thing.child
thing.child.attributes = new_attributes
end
expecting that if a child exists, it should be updated.
For the sake of argument, let's say that maybe the behavior isn't so bad, maybe this is what people expect or could use. In that case, what a terrible error message. I'm not familiar enough with the Associations internals to suggest where a better one could be injected, but something along the lines of
Can't modify a destroyed record
would be miles better.