# 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.

## Preparation

class Author < ApplicationRecord
has_many :books
end

class Book < ApplicationRecord
belongs_to :author
end


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

class RemoveBooks
def initialize(author)
@author = author
end

def call
delete_books
# Submit tracking events
end

private

def delete_books
author.books.delete_all
end
end



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)
end
let!(:css_book) do
end

subject { described_class.new(author).call }

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



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.

Failures:

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

ActiveRecord::NotNullViolation:
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
author.books.destroy_all
end


or

  def delete_books
author.books.each(&:delete)
end


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 = Logger.new(STDOUT)


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.

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
end


Bingo, our specs pass :smiley:

## Conclusion

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: