Delete_all will surprise you

Delete_all will surprise you

I was working recently on a Rails project and I faced an interesting behavior of delete_all from ActiveRecord. In this post, I’ll go through the steps that I have done to understand what happened and how I did manage to get around it.


Let’s start with an example for a has-many association:

class Author < ApplicationRecord
  has_many :books
class Book < ApplicationRecord
  belongs_to :author

Let’s add also a service to make the example more realistic.

class RemoveBooks
  attr_reader :author
  def initialize(author)
    @author = author

  def call
    # Submit Notification events
    # Submit tracking events


  def delete_books

Of course, we should add a spec file :grin:

require 'rails_helper'

describe RemoveBooks do
  let(:author) { Author.create(name: 'John doe') }
  let!(:intro_to_ruby) do
    Book.create(title: 'Intro to Ruby', author: author)
  let!(:css_book) do
    Book.create(title: 'Skip to MDN', author: author)

  subject { }

  describe '#call' do
    it 'deletes the associated books' do
      expect { subject }.to change {
        Book.where(author: author).count

As we can see, we are trying to test the deletion of records with the count method. Running the spec above will result in the following error:

NOTE: if you have specified the foreign key with null: true, the result will be that the count didn’t change.


  1) RemoveBooks#call deletes the associated books
     Failure/Error: author.books.delete_all

       SQLite3::ConstraintException: NOT NULL constraint failed: books.author_id

That looks a bit weird, so if we experiment a bit and change RemoveBooks#delete_book with the following snippets, the spec will pass:

  def delete_books


  def delete_books

In case, you are wondering why I’m using delete_all, here is a reminder about the difference between destroy_all and delete_all from Rails docs:

Note: Instantiation, callback execution, and deletion of each record can be time consuming when you’re removing many records at once. It generates at least one SQL DELETE query per record (or possibly more, to enforce your callbacks). If you want to delete many rows quickly, without concern for their associations or callbacks, use delete_all instead.

So let’s get back to our debugging. We need to know what’s going on and the best place is, of course, the logs :scroll:

For convenience, I want to output SQL logs to STDOUT, so it will be easier to see the output when running RSpec.

# config/environment/test.rb

ActiveRecord::Base.logger =

After running the spec again, I noticed something interesting. As we can see from the screenshot, the method is trying to run an update query to nullify the association instead of a deleting it.

SQL logs SQL logs

But why delete_all is updating records instead of deleting them? :thinking:

Let’s head back to Rails docs and check the description of delete_all for CollectionProxy (in other words, it means calling delete_all on the association collection like: author.books.destroy_all)

Rails docs

Deletes all the records from the collection according to the strategy specified by the :dependent option. If no :dependent option is given, then it will follow the default strategy.

For has_many associations, the default deletion strategy is :nullify. This sets the foreign keys to NULL.

Does it mean that we need to add the dependent option to the association? Well, that depends on the requirements. But we can use a different approach:

def delete_books
  Book.where(author: author).delete_all

Bingo, our specs pass :smiley:


We saw together the steps to debug destroy_all from logging to checking the API docs. We should also keep in mind that we can use this approach to debug SQL queries in testing mode.

That’s it for today, happy debugging :wave: