How to Use the Destroy and Delete Methods in Rails

Daniel Pericich
4 min readDec 30, 2021
Photo by Moritz Mentges on Unsplash

On the surface, basic API operations are very straightforward. A normal app will usually follow the CRUD format of allowing users to make requests to Create, Read, Update and Delete resources. While the request methods are fairly uniform, the way in which the API, or backend of your app, implements them can vary drastically.

When we make a DELETE request to a Rails API, there are two basic methods that this call can trigger. The two calls are delete and destroy. To a beginner these may seem to accomplish the same action, deleting a resource. However, looking closer at the Rails docs we find that they are different.

Before we dive into the nuances of these methods, let’s create a hypothetical situation to highlight these method’s differences.

You are currently an engineer at a high growth social media startup. You are on a team that handles managing user profiles and their associated posts and content. Recently you were assigned to build a controller action that allows users to delete their profiles when they choose to leave the platform. The feature was a quick build and has now been deployed for weeks.

One day your manager calls you in to talk about an issue with posts that are missing a profile. Though users have been able to delete their profiles, their content is still on the platform. When a current user tries to navigate to the a post’s profile, they are met with a 404. Your manager wants to know why these posts still exist and how to fix the problem going forward.

Delete

In this scenario you most likely set up your controller’s ‘delete’ action using the Rails Model delete method. This method is great for scaling as it is very fast, but it is fast at a cost. While the other common ‘delete’ method ties into the model lifecycle and allows for using model callbacks, delete lacks this ability in order to stay quick. Whatever model instance or record delete is called upon, it removes this record from the model database table and moves on to the next operation.

This is great for speed, but has the side effect of leaving us with orphan records. Orphan records are any child records that are not deleted when a parent record is deleted. Let’s take a look at the following example to see visually how this happens:

Figure 1. Using ‘delete’ to remove Profile record without deleting the child records

Here we see that we remove the profile record, but do not go any deeper to remove our associated posts. Again, while we have accomplished our goal of removing the profile, we have left a lot of records behind that will bloat our database and lead to inconsistencies in how our app works in the future.

Destroy

If we care about removing all records associated with our designated profile then we need to use destroy. Destroy, unlike delete, allows us to access the models lifecycle which gives us access to all sorts of callbacks. These callbacks will help us to check for any dependent records and remove them before deleting our record.

How this works can be seen in the following figures. First, we start the process of deleting our profile record:

Figure 2. Begin callback for deleting Profile record

Then we look at all dependent (child) records and delete them, one at a time:

Figure 3. Delete both child Post records for the Profile

Finally we delete our main profile record and we are set:

Figure 4. Once child records are deleted, delete parent Profile record

Knowing to use destroy instead of delete only gets you half way to our solution. To get the most out of our destroy call, we will need to add arguments to the relationship declaration on our model:

Figure 5. Declaring child record relationships

Here we declare on the Profile model that the profile has many posts, and that when the profile is deleted, we should destroy all the associated post model instances as well. It is important to note that we set dependent to destroy instead of something like delete. This allows our associated instances cleanup to work from the root (profile) to terminal nodes (our lowest relationship through a post).

Conclusion

If you want to be fast, use delete. If you want to be thorough, use destroy. Unless you are performing very complex and frequent data purges, you should always use destroy as the ability to access the model’s lifecycle will help ensure that your records are properly and fully deleted. Let me know in the comments about bugs you’ve found our issues you’ve experienced with incorrect delete vs. destroy method calls.

Notes

https://stackoverflow.com/questions/22757450/difference-between-destroy-and-delete

https://blog.getcensus.com/cascading-deletes-in-rails/

--

--

Daniel Pericich

Former Big Beer Engineer turned Full Stack Software Engineer