gitlab-org--gitlab-foss/app/models/concerns/fast_destroy_all.rb

90 lines
3.5 KiB
Ruby
Raw Normal View History

2018-05-01 08:06:44 +00:00
##
# This module is for replacing `dependent: :destroy` and `before_destroy` hooks.
#
# In general, `destroy_all` is inefficient because it calls each callback with `DELETE` queries i.e. O(n), whereas,
# `delete_all` is efficient as it deletes all rows with a single `DELETE` query.
#
# It's better to use `delete_all` as much as possible, however, in the real cases, it's hard to adopt because
2018-05-01 08:06:44 +00:00
# in some cases, external data (in ObjectStorage/FileStorage/Redis) is assosiated with rows.
#
# This module introduces a protocol to support the adoption with easy way.
# You can use in the following scnenes
# - When calling `destroy_all` explicitly
# e.g. `job.trace_chunks.fast_destroy_all``
# - When a parent record is deleted and all children are deleted subsequently (cascade delete)
# e.g. `before_destroy -> { run_after_commit(&build_trace_chunks.after_commit_cleanup_proc) }``
2018-05-01 08:06:44 +00:00
module FastDestroyAll
extend ActiveSupport::Concern
ForbiddenActionError = Class.new(StandardError)
2018-05-01 08:06:44 +00:00
included do
class_attribute :_delete_method, :_delete_params_generator
before_destroy do
raise ForbiddenActionError, '`destroy` is forbbiden, please use `fast_destroy_all`'
end
2018-05-01 08:06:44 +00:00
end
class_methods do
##
# This method is for registering :delete_method and :delete_params_generator
# You have to define the method if you want to use `FastDestroyAll`` module.
#
# e.g. fast_destroy_all_with :delete_all_redis_data, :redis_all_data_keys
def fast_destroy_all_with(delete_method, delete_params_generator)
self._delete_method = delete_method
self._delete_params_generator = delete_params_generator
end
##
# This method generates a proc to delete external data.
# It's useful especially when you want to hook parent record's deletion event.
#
# e.g. before_destroy -> { run_after_commit(&build_trace_chunks.after_commit_cleanup_proc) }
def after_commit_cleanup_proc
params = send self._delete_params_generator # rubocop:disable GitlabSecurity/PublicSend
subject = self # Preserve the subject class, otherwise `proc` uses a different class
2018-05-01 08:06:44 +00:00
proc do
subject.send subject._delete_method, params # rubocop:disable GitlabSecurity/PublicSend
2018-05-01 08:06:44 +00:00
true
end
end
##
# This method deletes all rows at first and delete all external data at second.
# Before deleting the rows, it generates a proc to delete external data.
# So it won't lose the track of deleting external data, even if it happened after rows had been deleted.
def fast_destroy_all
after_commit_cleanup_proc.tap do |delete_all_external_data|
2018-05-01 08:06:44 +00:00
delete_all
delete_all_external_data.call
end
end
end
module Helpers
extend ActiveSupport::Concern
class_methods do
##
# This is a helper method for performaning fast_destroy_all when parent relations are deleted
# Their children must include `FastDestroyAll` module.
#
# `use_fast_destroy` must be defined **before** `has_many` and `has_one`, such as `has_many :relation, depenedent: :destroy`
# Otherwise `use_fast_destroy` performs against **deleted** rows, which fails to get identifiers of external data
#
# e.g. use_fast_destroy :build_trace_chunks
def use_fast_destroy(relation)
before_destroy do
subject = public_send(relation)
after_commit_proc = subject.after_commit_cleanup_proc
run_after_commit(&after_commit_proc)
end
end
end
end
2018-05-01 08:06:44 +00:00
end