fast_destroy_all.rb 3.04 KB
Newer Older
1
2
# frozen_string_literal: true

3
4
5
6
7
8
##
# 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.
#
Shinya Maeda's avatar
Shinya Maeda committed
9
# It's better to use `delete_all` as our best practice, however,
10
# if external data (e.g. ObjectStorage, FileStorage or Redis) are associated with database records,
Shinya Maeda's avatar
Shinya Maeda committed
11
# it is difficult to accomplish it.
12
#
Shinya Maeda's avatar
Shinya Maeda committed
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# This module defines a format to use `delete_all` and delete associated external data.
# Here is an exmaple
#
# Situation
# - `Project` has many `Ci::BuildTraceChunk` through `Ci::Build`
# - `Ci::BuildTraceChunk` stores associated data in Redis, so it relies on `dependent: :destroy` and `before_destroy` for the deletion
#
# How to use
# - Define `use_fast_destroy :build_trace_chunks` in `Project` model.
# - Define `begin_fast_destroy` and `finalize_fast_destroy(params)` in `Ci::BuildTraceChunk` model.
# - Use `fast_destroy_all` instead of `destroy` and `destroy_all`
# - Remove `dependent: :destroy` and `before_destroy` as it's no longer need
#
# Expectation
# - When a project is `destroy`ed, the associated trace_chunks will be deleted by `delete_all`,
#   and the associated data will be removed, too.
# - When `fast_destroy_all` is called, it also performns as same.
30
31
32
module FastDestroyAll
  extend ActiveSupport::Concern

33
34
  ForbiddenActionError = Class.new(StandardError)

35
  included do
36
    before_destroy do
37
      raise ForbiddenActionError, '`destroy` and `destroy_all` are forbidden. Please use `fast_destroy_all`'
Shinya Maeda's avatar
Shinya Maeda committed
38
    end
39
40
41
42
  end

  class_methods do
    ##
Shinya Maeda's avatar
Shinya Maeda committed
43
    # This method delete rows and associated external data efficiently
44
    #
Shinya Maeda's avatar
Shinya Maeda committed
45
    # This method can replace `destroy` and `destroy_all` without having `after_destroy` hook
Shinya Maeda's avatar
Shinya Maeda committed
46
47
48
49
50
51
    def fast_destroy_all
      params = begin_fast_destroy

      delete_all

      finalize_fast_destroy(params)
52
53
54
    end

    ##
Shinya Maeda's avatar
Shinya Maeda committed
55
    # This method returns identifiers to delete associated external data (e.g. file paths, redis keys)
56
    #
Shinya Maeda's avatar
Shinya Maeda committed
57
58
59
    # This method must be defined in fast destroyable model
    def begin_fast_destroy
      raise NotImplementedError
60
61
62
    end

    ##
Shinya Maeda's avatar
Shinya Maeda committed
63
64
65
66
67
    # This method deletes associated external data with the identifiers returned by `begin_fast_destroy`
    #
    # This method must be defined in fast destroyable model
    def finalize_fast_destroy(params)
      raise NotImplementedError
68
69
    end
  end
70
71
72
73
74
75

  module Helpers
    extend ActiveSupport::Concern

    class_methods do
      ##
Shinya Maeda's avatar
Shinya Maeda committed
76
77
      # This method is to be defined on models which have fast destroyable models as children,
      # and let us avoid to use `dependent: :destroy` hook
78
      def use_fast_destroy(relation)
79
        before_destroy(prepend: true) do
Shinya Maeda's avatar
Shinya Maeda committed
80
          perform_fast_destroy(public_send(relation)) # rubocop:disable GitlabSecurity/PublicSend
81
82
83
        end
      end
    end
Shinya Maeda's avatar
Shinya Maeda committed
84
85
86
87
88
89
90
91

    def perform_fast_destroy(subject)
      params = subject.begin_fast_destroy

      run_after_commit do
        subject.finalize_fast_destroy(params)
      end
    end
92
  end
93
end