Rails - Delete using destroy, delete, and paranoia
In this article, we are going to see the different methods to delete items in Rails. It's quite easy to confuse between destroy, delete, and paranoia, but they are different and after a while, you get used to it. We'll also walk you through the differences between delete and destroy in Rails.Table of Contents
- Deleting items in Rails - An Introduction
- Rails delete operation using destroy method
- Rails delete operation using delete method
- Rails soft delete operation using gems
- Other Related Concepts
Deleting items in Rails - An Introduction
To illustrate the differences between various methods in Rails to delete items, we use the following Rails Application.$ rails new bookshelf
[...]
$ cd bookshelf
$ rails generate model book title
[...]
$ rails generate model author book_id:integer first_name last_name
[...]
$ rake db:migrate
[...]
$
app/models/book.rb
class Book < ActiveRecord::Base
attr_accessible :title
has_many :authors, :dependent => :destroy
end
app/models/author.rb
class Author < ActiveRecord::Base
attr_accessible :book_id, :first_name, :last_name
belongs_to :book
end
Note that in our rails application, the author model belongs to the book model.
Rails delete operation using destroy method
By using destroy, you can delete the record from rails as well as its other existing dependencies. So in the context of our rails application, if we delete a book record using the destroy function, the authors associated with the book will also be deleted.Let's look at an example here, where we create a record in rails and then delete using destroy:
$ rails console
Loading development environment (Rails 3.2.9)
>> book = Book.create(title: 'Homo faber')
(0.1ms) begin transaction
SQL (25.8ms) INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?) [["created_at", Sun, 18 Nov 2012 15:11:41 UTC +00:00], ["title", "Homo faber"], ["updated_at", Sun, 18 Nov 2012 15:11:41 UTC +00:00]]
(2.1ms) commit transaction
=> #
>> Book.count
(0.3ms) SELECT COUNT(*) FROM "books"
=> 1
>> book.destroy
(0.1ms) begin transaction
Author Load (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."book_id" = 1
SQL (0.3ms) DELETE FROM "books" WHERE "books"."id" = ? [["id", 1]]
(3.0ms) commit transaction
=> #
>> Book.count
(0.2ms) SELECT COUNT(*) FROM "books"
=> 0
>>
>> Book.create(title: 'Homo faber').authors.create(first_name: 'Max', last_name: 'Frisch')
(0.1ms) begin transaction
SQL (0.6ms) INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?) [["created_at", Sun, 18 Nov 2012 15:12:57 UTC +00:00], ["title", "Homo faber"], ["updated_at", Sun, 18 Nov 2012 15:12:57 UTC +00:00]]
(2.6ms) commit transaction
(0.1ms) begin transaction
SQL (0.6ms) INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?) [["book_id", 2], ["created_at", Sun, 18 Nov 2012 15:12:57 UTC +00:00], ["first_name", "Max"], ["last_name", "Frisch"], ["updated_at", Sun, 18 Nov 2012 15:12:57 UTC +00:00]]
(0.9ms) commit transaction
=> #
>> Book.count
(0.3ms) SELECT COUNT(*) FROM "books"
=> 1
>> Author.count
(0.3ms) SELECT COUNT(*) FROM "authors"
=> 1
>> Book.first.destroy
Book Load (0.2ms) SELECT "books".* FROM "books" LIMIT 1
(0.1ms) begin transaction
Author Load (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."book_id" = 2
SQL (0.2ms) DELETE FROM "authors" WHERE "authors"."id" = ? [["id", 1]]
SQL (0.1ms) DELETE FROM "books" WHERE "books"."id" = ? [["id", 2]]
(2.2ms) commit transaction
=> #
>> Author.count
(0.3ms) SELECT COUNT(*) FROM "authors"
=> 0
>>
One key point to note in rails while deleting records is that the instance is frozen after removing the database field. Therefore, even though it is removed from the database, its value is still present in the program, which can no longer be modified, i.e. read only value. To check if an instance is frozen or not, you can use the method frozen?:
>> book = Book.create(title: 'Homo faber')
(0.1ms) begin transaction
SQL (1.2ms) INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?) [["created_at", Sun, 18 Nov 2012 15:14:04 UTC +00:00], ["title", "Homo faber"], ["updated_at", Sun, 18 Nov 2012 15:14:04 UTC +00:00]]
(2.5ms) commit transaction
=> #
>> book.destroy
(0.1ms) begin transaction
Author Load (0.2ms) SELECT "authors".* FROM "authors" WHERE "authors"."book_id" = 3
SQL (0.2ms) DELETE FROM "books" WHERE "books"."id" = ? [["id", 3]]
(1.9ms) commit transaction
=> #
>> Book.count
(0.3ms) SELECT COUNT(*) FROM "books"
=> 0
>> book
=> #
>> book.frozen?
=> true
>>
As mentioned above the record has been removed from the database, but the object with all its data is still present in the running Ruby program. The good news is that we can still revive the deleted record, but it will then be a new record.
>> Book.create(title: book.title)
(0.1ms) begin transaction
SQL (0.6ms) INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?) [["created_at", Sun, 18 Nov 2012 15:15:06 UTC +00:00], ["title", "Homo faber"], ["updated_at", Sun, 18 Nov 2012 15:15:06 UTC +00:00]]
(1.7ms) commit transaction
=> #
>> exit
$
Rails Delete operation using delete method
Unlike the destroy method, with delete, you can remove a record directly from the database. Any dependencies to other records in the model are not taken into account. The method delete only deletes that one row in the database and nothing else. It is important to note that the delete method is muchfaster than the destroy method.$ rails console
Loading development environment (Rails 3.2.9)
>> Book.create(title: 'Homo faber').authors.create(first_name: 'Max', last_name: 'Frisch')
(0.1ms) begin transaction
SQL (24.4ms) INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?) [["created_at", Sun, 18 Nov 2012 15:27:59 UTC +00:00], ["title", "Homo faber"], ["updated_at", Sun, 18 Nov 2012 15:27:59 UTC +00:00]]
(3.0ms) commit transaction
(0.1ms) begin transaction
SQL (0.6ms) INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?) [["book_id", 1], ["created_at", Sun, 18 Nov 2012 15:27:59 UTC +00:00], ["first_name", "Max"], ["last_name", "Frisch"], ["updated_at", Sun, 18 Nov 2012 15:27:59 UTC +00:00]]
(1.0ms) commit transaction
=> #
>> Book.count
(0.3ms) SELECT COUNT(*) FROM "books"
=> 1
>> Author.count
(0.3ms) SELECT COUNT(*) FROM "authors"
=> 1
>> Book.last.delete
Book Load (0.3ms) SELECT "books".* FROM "books" ORDER BY "books"."id" DESC LIMIT 1
SQL (2.8ms) DELETE FROM "books" WHERE "books"."id" = 1
=> #
>> Book.count
(0.3ms) SELECT COUNT(*) FROM "books"
=> 0
>> Author.count
(0.3ms) SELECT COUNT(*) FROM "authors"
=> 1
>> Author.last
Author Load (0.3ms) SELECT "authors".* FROM "authors" ORDER BY "authors"."id" DESC LIMIT 1
=> #
>> exit
$
The record of the book 'Homo faber' is deleted, but the author is still in the database. As with destroy, an object also gets frozen when you use delete. The record is already removed from the database, but the object itself is still there. Although it is often much faster than the alternative, #destroy, skipping callbacks might bypass business logic in your application that ensures referential integrity or performs other essential jobs.
Soft Deleting records using gems in Rails
Finally, we will see a brief overview about soft deleting in rails.So what does soft deletion mean?
It is an operation in which a flag is used to mark data as unusable rather than deleting it permanently from the database.Many times, we don’t want to delete a record permanently, but may not want to build an archiving system manually. Rails provides a way to do just with gems such as paranoia for soft deleting. Soft deleting has a few advantages such as:
- Easier/faster undeletes
- History tracking (keeping deleted rows around for auditing purposes)
class User < ActiveRecord::Base
acts_as_paranoid
end
If you call destroy or destroy! a second time (i.e. on a record that has already been soft deleted), it will be actually deleted from the database. Alternatively, you can call destroy_fully! from the beginning to skip the soft delete.