4.12. Removing a Record

To remove a database record, you have the methods destroy and delete. It's quite easy to confuse these two terms, but they are different and after a while you get used to it.
As an example, 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

destroy

With destroy you can remove a record and any existing dependencies are also taken into account (see for example :dependent => :destroy in the section called “Options”). Simply put: to be on the safe side, it's better to use destroy because then the Rails system does more for you.
Let's create a record and then destroy it again:
$ 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 id: 1, title: "Homo faber", created_at: "2012-11-18 15:11:41", updated_at: "2012-11-18 15:11:41">
>> 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 id: 1, title: "Homo faber", created_at: "2012-11-18 15:11:41", updated_at: "2012-11-18 15:11:41">
>> Book.count
   (0.2ms)  SELECT COUNT(*) FROM "books" 
=> 0
>>
As we are using the option :dependent => :destroy in the Book model, we can also automatically remove all authors:
>> 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
=> #<Author id: 1, book_id: 2, first_name: "Max", last_name: "Frisch", created_at: "2012-11-18 15:12:57", updated_at: "2012-11-18 15:12:57">
>> 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
=> #<Book id: 2, title: "Homo faber", created_at: "2012-11-18 15:12:57", updated_at: "2012-11-18 15:12:57">
>> Author.count
   (0.3ms)  SELECT COUNT(*) FROM "authors" 
=> 0
>>
When removing records, please always remember that there is a difference between the content of the database and the value of the currently removed object. The instance is frozen after removing the database field. So it is no longer in the database, but still present in the program, yet it can no longer be modified there. It is read-only. To check, 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 id: 3, title: "Homo faber", created_at: "2012-11-18 15:14:04", updated_at: "2012-11-18 15:14:04">
>> 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 id: 3, title: "Homo faber", created_at: "2012-11-18 15:14:04", updated_at: "2012-11-18 15:14:04">
>> Book.count
   (0.3ms)  SELECT COUNT(*) FROM "books" 
=> 0
>> book
=> #<Book id: 3, title: "Homo faber", created_at: "2012-11-18 15:14:04", updated_at: "2012-11-18 15:14:04">
>> book.frozen?
=> true
>> 
The record has been removed from the database, but the object with all its data is still present in the running Ruby program. So could we then revive the entire record? The answer is yes, 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
=> #<Book id: 4, title: "Homo faber", created_at: "2012-11-18 15:15:06", updated_at: "2012-11-18 15:15:06">
>> exit
$

delete

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.
Let's create a book with one author and then remove the book with delete:
$ 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
=> #<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-11-18 15:27:59", updated_at: "2012-11-18 15:27:59">
>> 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 id: 1, title: "Homo faber", created_at: "2012-11-18 15:27:59", updated_at: "2012-11-18 15:27:59">
>> 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
=> #<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-11-18 15:27:59", updated_at: "2012-11-18 15:27:59">
>> 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 (see the section called “destroy”). The record is already removed from the database, but the object itself is still there.

Updates about this book will be published on my Twitter feed.