summaryrefslogtreecommitdiff
path: root/app/models/concerns/resolvable_discussion.rb
blob: 141c480ea1fdbf2d08f245429ce0978afa8779ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# frozen_string_literal: true

module ResolvableDiscussion
  extend ActiveSupport::Concern
  include ::Gitlab::Utils::StrongMemoize

  included do
    # A number of properties of this `Discussion`, like `first_note` and `resolvable?`, are memoized.
    # When this discussion is resolved or unresolved, the values of these properties potentially change.
    # To make sure all memoized values are reset when this happens, `update` resets all instance variables with names in
    # `memoized_variables`. If you add a memoized method in `ResolvableDiscussion` or any `Discussion` subclass,
    # please make sure the instance variable name is added to `memoized_values`, like below.
    cattr_accessor :memoized_values, instance_accessor: false do
      []
    end

    memoized_values.push(
      :resolvable,
      :resolved,
      :first_note,
      :first_note_to_resolve,
      :last_resolved_note,
      :last_note
    )

    delegate :potentially_resolvable?,
             :noteable_id,
             :noteable_type,
             to: :first_note

    delegate  :resolved_at,
              :resolved_by,
              to: :last_resolved_note,
              allow_nil: true
  end

  def resolved_by_push?
    !!last_resolved_note&.resolved_by_push?
  end

  def resolvable?
    strong_memoize(:resolvable) do
      potentially_resolvable? && notes.any?(&:resolvable?)
    end
  end

  def resolved?
    strong_memoize(:resolved) do
      resolvable? && notes.none?(&:to_be_resolved?)
    end
  end

  def first_note
    strong_memoize(:first_note) do
      notes.first
    end
  end

  def first_note_to_resolve
    return unless resolvable?

    strong_memoize(:first_note_to_resolve) do
      notes.find(&:to_be_resolved?)
    end
  end

  def last_resolved_note
    return unless resolved?

    strong_memoize(:last_resolved_note) do
      resolved_notes.max_by(&:resolved_at)
    end
  end

  def resolved_notes
    notes.select(&:resolved?)
  end

  def to_be_resolved?
    resolvable? && !resolved?
  end

  def can_resolve?(current_user)
    return false unless current_user
    return false unless resolvable?

    current_user.can?(:resolve_note, self.noteable)
  end

  def resolve!(current_user)
    return unless resolvable?

    update { |notes| notes.resolve!(current_user) }
  end

  def unresolve!
    return unless resolvable?

    update(&:unresolve!)
  end

  def clear_memoized_values
    self.class.memoized_values.each do |name|
      clear_memoization(name)
    end
  end

  private

  def update
    # Do not select `Note.resolvable`, so that system notes remain in the collection
    notes_relation = Note.where(id: notes.map(&:id))

    yield(notes_relation)

    # Set the notes array to the updated notes
    @notes = notes_relation.fresh.to_a # rubocop:disable Gitlab/ModuleWithInstanceVariables

    clear_memoized_values
  end
end