module DeleteRecursively

DeleteRecursively

Adds a new dependent: option to ActiveRecord associations.

Constants

NEW_DEPENDENT_OPTION
VERSION

Public Class Methods

all(record_class, criteria = {}, seen = []) click to toggle source
# File lib/delete_recursively.rb, line 55
def all(record_class, criteria = {}, seen = [])
  return if seen.include?(record_class)

  seen << record_class

  record_class.reflect_on_all_associations.each do |reflection|
    AssociatedClassFinder.call(reflection).each do |assoc_class|
      if recurse_on?(reflection)
        all(assoc_class, criteria, seen)
      elsif deleting?(reflection)
        delete_with_applicable_criteria(assoc_class, criteria)
      end
    end
  end

  delete_with_applicable_criteria(record_class, criteria)
end
delete_records_recursively(record_class, record_ids, force: false) click to toggle source
# File lib/delete_recursively.rb, line 48
def delete_records_recursively(record_class, record_ids, force: false)
  record_class.reflect_on_all_associations.each do |ref|
    delete_recursively(ref, nil, record_ids, force: force)
  end
  record_class.delete(record_ids)
end
delete_recursively(reflection, _legacy_arg, owner_ids, seen: [], force: false) click to toggle source
# File lib/delete_recursively.rb, line 18
def delete_recursively(reflection, _legacy_arg, owner_ids, seen: [], force: false)
  owner_ids = Array(owner_ids)
  return if owner_ids.empty?

  # Dependent deletion can be bi-directional, so we need to avoid a loop.
  # Note, however, that an association could be reached multiple times, from
  # different starting points within the association tree, and having
  # different owner_ids. In this case, we do need to go through it again.
  recursion_identifier = [reflection, owner_ids]
  return if seen.include?(recursion_identifier)

  seen << recursion_identifier

  AssociatedClassFinder.call(reflection).each do |assoc_class|
    record_ids = nil # fetched only when needed for recursion, deletion, or both

    if recurse_on?(reflection)
      record_ids = DependentIdFinder.call(owner_ids, reflection, assoc_class)
      assoc_class.reflect_on_all_associations.each do |subref|
        delete_recursively(subref, nil, record_ids, seen: seen, force: force)
      end
    end

    if dest_method = destructive_method(reflection, force: force)
      record_ids ||= DependentIdFinder.call(owner_ids, reflection, assoc_class)
      assoc_class.send(dest_method, record_ids) unless record_ids.empty?
    end
  end
end
enabled_for?(reflection) click to toggle source
# File lib/delete_recursively.rb, line 73
def enabled_for?(reflection)
  reflection.options[:dependent] == NEW_DEPENDENT_OPTION
end

Private Class Methods

delete_with_applicable_criteria(record_class, criteria) click to toggle source
# File lib/delete_recursively.rb, line 79
def delete_with_applicable_criteria(record_class, criteria)
  applicable_criteria = criteria.select do |column_name, _value|
    record_class.column_names.include?(column_name.to_s)
  end
  record_class.where(applicable_criteria).delete_all
end
deleting?(reflection) click to toggle source
# File lib/delete_recursively.rb, line 94
def deleting?(reflection)
  [:delete, :delete_all, NEW_DEPENDENT_OPTION].include?(reflection.options[:dependent])
end
destructive?(reflection) click to toggle source
# File lib/delete_recursively.rb, line 90
def destructive?(reflection)
  %i[destroy destroy_all].include?(reflection.options[:dependent])
end
destructive_method(reflection, force: false) click to toggle source
# File lib/delete_recursively.rb, line 98
def destructive_method(reflection, force: false)
  if deleting?(reflection) || force && destructive?(reflection)
    :delete
  elsif destructive?(reflection)
    :destroy
  end
end
recurse_on?(reflection) click to toggle source
# File lib/delete_recursively.rb, line 86
def recurse_on?(reflection)
  enabled_for?(reflection) || destructive?(reflection)
end