In ActiveRecord, has_many/has_one associations allow you to define a parent-child relationship. In addition to that, the dependent option controls what happens to the child records when the parent record is destroyed.

The dependent option accepts the following values (Reference: Rails API doc):

  • :nullify -- used to dereference the parent by setting the foreign key of the child records as NULL.
  • :destroy -- used to delete the child records by calling .destroy on them one by one so that the callbacks are processed.
  • :delete_all -- also used to delete the child records, but skips the callback processing by calling .delete_all instead.
  • :restrict_with_exception -- causes an ActiveRecord::DeleteRestrictionError exception to be raised if there are any associated records.
  • :restrict_with_error - causes an error to be added to the owner if there are any associated objects.

With the merging of the Offer dependent: :destroy_async for associations PR, a new value is introduced in Rails 6.1, namely :destroy_async. This value makes the dependent option function the same as when passing :destroy, except the callback processing happens in the background job named ActiveRecord::DestroyAssociationAsyncJob. The main reason for introducing this option was to reduce the server response time since the end-user shouldn't have to wait for all the child records to be deleted.

With Rails >= 7.1

Rails 7.1 takes this a step further and introduces Rails.application.config.active_record.destroy_association_async_batch_size, courtesy of the Add active_record.destroy_association_async_batch_size configuration PR. If this config is set, the ActiveRecord::DestroyAssociationAsyncJob jobs are enqueued several times as necessary, with a maximum number of records per job as specified in the config.

Here's an example of one such case where the User model has many Comment records that need to be destroyed when the User record is deleted:

# app/models/user.rb
class User
  has_many :comments, dependent: :destroy_async

With destroy_async, being set, once the User instance is destroyed, all the related Comment records are to be destroyed in a single background job.

This is fine, but you may not want Rails to call .destroy on each Comment record if the number of comments is huge. The reason would be that it would take forever and other delayed jobs might not get processed due to it. The solution to this is to set the Rails.application.config.active_record.destroy_association_async_batch_size option to a reasonable value, for example 100:

# config/environments/application.rb
Rails.application.configure do
  # ...
  config.active_record.destroy_association_async_batch_size = 100
  # ...

Due to this, if a user has let's say 1000 comments, and if the user record is destroyed, ActiveRecord::DestroyAssociationAsyncJob will be enqueued with a maximum of 100 records per job, and a total of 10 such jobs will be enqueued to achieve the desired result.

By default, the value for Rails.application.config.active_record.destroy_association_async_batch_size is nil, which means that all child records will be deleted in single background job. One can specify a smaller batch size to allow a higher number of short-duration jobs or a larger batch size to allow a lower number of long-duration jobs.

With this new functionality, the developers may fine-tune their Ruby on Rails applications by controlling the frequency and duration of ActiveRecord::DestroyAssociationAsyncJob jobs.