Delete or delete? Removing things in Rails
Yesterday, while I was refactoring some code, I inadvertently confused myself about the various delete and destroy methods in Ruby and Rails. Between the two, there are over 20 different ways to remove objects, so I suppose I shouldn’t feel too bad I didn’t pick the right one. If you ever wanted to know the best way to make things go away in Rails, you’ve come to the right article.
Let’s start with an array of message objects, like this:
messages = Message.find(:all)
If I want to remove an element from this collection, it’s easy enough using the Ruby array.delete method. If I pass in an object and it matches one of the objects in the array, it is removed (or if the object doesn’t exist in the array, it just returns nil).
messages.delete(message)
Now let’s say I have a World model that has_many :messages, and I want to create a method to delete a message. My first thought is to use the same Ruby delete function:
class World < ActiveRecord::Base
has_many :messages
def remove_message(message_to_delete)
messages = Message.find(:all)
messages.delete(message_to_delete)
end
end
The tricky part here is that message_to_delete is removed from the array in memory, but ActiveRecord knows nothing about it — message_to_delete is still sitting in the database, untouched. Then it occurred to me: because I set up a has_many relationship, my world instance already has a collection of messages just waiting for me, and ActiveRecord knows all about it! I don’t have to bother instantiating a collection of messages with the find method:
class World < ActiveRecord::Base
has_many :messages
def remove_message(message_to_delete)
messages = Message.find(:all)
messages.delete(message_to_delete)
end
end
Rails Persistence Inconsistence
However, there’s another problem here, and it has to do with how Rails persists changes to collections managed by ActiveRecord. The message I want to delete is removed from memory, but Rails does not actually delete it from the database. Instead, Rails breaks the relationship between the message and the world by setting the world_id to null for that message. Here’s the SQL that Rails generates for the messages.delete call above:
UPDATE messages SET world_id = NULL WHERE (world_id = 1 AND id IN (1))
In other words, message_to_delete is not actually deleted, it is orphaned in the database. Far be it from me to question the wisdom of the Rails GodsTM, but this seems like an odd choice. Preserving data integrity starts with managing foreign key relationships — and avoids leaving unattached data cluttering up my tables. If I delete something, it should be gone.
Realization Sets In
However, then I realized my real mistake: I was using Ruby’s array.delete rather than one of the handy methods provided by ActiveRecord::Base in Rails. There are several choices:
- Class.delete(id)
- Class.delete_all(conditions)
- object.destroy
- Class.destroy(id)
- Class.destroy_all(conditions)
The ones that start with “Class” are all static class methods, so I might use them like this:
Message.delete(message_to_delete.id) Message.delete_all(:conditions => ["world_id = ?", world.id])
What’s the difference between delete and destroy methods? The delete methods all send SQL delete statements directly to the database without instantiating any objects. The destroy methods first instantiate objects to trigger any callbacks and filters before the record is deleted. Of course, destroy is much less efficient than delete, but it lets you perform any cleanup operations on an object before it goes away forever.
Destruction is the answer
What have I learned? Since my message object has already been instantiated, the correct method is the instance destroy method. The finished product looks like this:
class World < ActiveRecord::Base
has_many :messages
def remove_message(message_to_delete)
message_to_delete.destroy
end
end
I hope this clarifies some of the ways that Rails handles the elimination of objects. Now that you have this knowledge, go forth and destroy.
Posted on February 15th, 2008 in the Rails category | PermalinkYou can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
Leave a Reply
You must be logged in to post a comment.

