---
title: "Rails 6.1 allows associations to be destroyed asynchronously"
description:
  "Rails 6.1 allows associations to support destroy_async option with dependent
  key"
canonical_url: "https://www.bigbinary.com/blog/rails-6-1-allows-associations-to-support-destroy_async-option-with-dependent-key"
markdown_url: "https://www.bigbinary.com/blog/rails-6-1-allows-associations-to-support-destroy_async-option-with-dependent-key.md"
---

# Rails 6.1 allows associations to be destroyed asynchronously

Rails 6.1 allows associations to support destroy_async option with dependent key

- Author: Srijan Kapoor
- Published: December 8, 2020
- Categories: Rails 6.1, Rails

In Rails 6.1, Rails will enqueue a background job to destroy associated records
if `dependent: :destroy_async` is setup.

Let's consider the following example.

```ruby
class Team < ApplicationRecord
  has_many :players, dependent: :destroy_async
end

class Player < ApplicationRecord
  belongs_to :team
end
```

Now, if we call the `destroy` method on an instance of class `Team` Rails would
enqueue an asynchronous job to delete the associated `players` records.

We can verify this asynchronous job with the following test case.

```ruby
class TeamTest < ActiveSupport::TestCase
  include ActiveJob::TestHelper

  test "destroying a record destroys the associations using a background job" do
    team = Team.create!(name: "Portugal", manager: "Fernando Santos")
    player1 = Player.new(name: "Bernardo Silva")
    player2 = Player.new(name: "Diogo Jota")
    team.players << [player1, player2]
    team.save!

    team.destroy

    assert_enqueued_jobs 1
    assert_difference -> { Player.count }, -2 do
      perform_enqueued_jobs
    end
  end
end

Finished in 0.232213s, 4.3064 runs/s, 8.6128 assertions/s.
1 runs, 2 assertions, 0 failures, 0 errors, 0 skips
```

Alternatively, this enqueue behavior can also be demonstrated in
`rails console`.

```ruby
irb(main):011:0> team.destroy
  TRANSACTION (0.1ms)  begin transaction
  Player Load (0.6ms)  SELECT "players".* FROM "players" WHERE "players"."team_id" = ?  [["team_id", 6]]

Enqueued ActiveRecord::DestroyAssociationAsyncJob (Job ID: 4df07c2d-f55b-48c9-8c20-545b086adca2) to Async(active_record_destroy) with arguments: {:owner_model_name=>"Team", :owner_id=>6, :association_class=>"Player", :association_ids=>[1, 2], :association_primary_key_column=>:id, :ensuring_owner_was_method=>nil}

Performed ActiveRecord::DestroyAssociationAsyncJob (Job ID: 4df07c2d-f55b-48c9-8c20-545b086adca2) from Async(active_record_destroy) in 34.5ms
```

However, this behaviour is inconsistent and the `destroy_async` option should
not be used when the association is backed by foreign key constraints in the
database.

Let us consider another example.

**CASE:** With a simple foreign key on the `team_id` column in place.

```ruby
irb(main):015:0> team.destroy
  TRANSACTION (0.1ms)  begin transaction
  Player Load (0.1ms)  SELECT "players".* FROM "players" WHERE "players"."team_id" = ?  [["team_id", 7]]

Enqueued ActiveRecord::DestroyAssociationAsyncJob (Job ID: 69e51e5f-5b59-4095-92db-90aab73a7f65) to Async(default) with arguments: {:owner_model_name=>"Team", :owner_id=>7, :association_class=>"Player", :association_ids=>[1], :association_primary_key_column=>:id, :ensuring_owner_was_method=>nil}
  Team Destroy (0.9ms)  DELETE FROM "teams" WHERE "teams"."id" = ?  [["id", 7]]
  TRANSACTION (1.1ms)  rollback transaction

Performing ActiveRecord::DestroyAssociationAsyncJob (Job ID: 69e51e5f-5b59-4095-92db-90aab73a7f65) from Async(default) enqueued at 2021-01-03T21:10:21Z with arguments: {:owner_model_name=>"Team", :owner_id=>7, :association_class=>"Player", :association_ids=>[1], :association_primary_key_column=>:id, :ensuring_owner_was_method=>nil}
Traceback (most recent call last):
        1: from (irb):15
ActiveRecord::InvalidForeignKey (SQLite3::ConstraintException: FOREIGN KEY constraint failed)
```

An exception is raised by Rails and the record is not destroyed.

**CASE:** With a cascading foreign key using `on_delete: :cascade`

Here, even though `ActiveRecord::DestroyAssociationAsyncJob` would run to
successful completion, the associated `players` records would already be deleted
inside the same transaction block destroying the `team` record, and it would
skip any destroy callbacks like `before_destroy`, `after_destroy` or
`after_commit on: :destroy`.

This makes using `destroy_async` redundant in such a case.

Check out the [pull request](https://github.com/rails/rails/pull/40157) for more
details.

## Links

- [Human page](https://www.bigbinary.com/blog/rails-6-1-allows-associations-to-support-destroy_async-option-with-dependent-key)
