Class: CoPlan::CommentThread

Inherits:
ApplicationRecord show all
Defined in:
app/models/coplan/comment_thread.rb

Constant Summary collapse

STATUSES =
%w[open resolved accepted dismissed].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#anchor_occurrenceObject

Returns the value of attribute anchor_occurrence.



5
6
7
# File 'app/models/coplan/comment_thread.rb', line 5

def anchor_occurrence
  @anchor_occurrence
end

Class Method Details

.mark_out_of_date_for_new_version!(new_version) ⇒ Object

Transforms anchor positions through intervening version edits using OT. Threads without positional data (anchor_start/anchor_end/anchor_revision) are marked out-of-date unconditionally — all new threads resolve positions on creation via resolve_anchor_position.



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
# File 'app/models/coplan/comment_thread.rb', line 28

def self.mark_out_of_date_for_new_version!(new_version)
  threads = where(out_of_date: false).where.not(plan_version_id: new_version.id)
  anchored_threads = threads.select(&:anchored?)

  # Pre-fetch all versions that any thread might need (from the oldest
  # anchor_revision to the new version) in a single query.
  min_anchor_rev = anchored_threads
    .filter_map { |t| t.anchor_revision if t.anchor_start.present? && t.anchor_end.present? && t.anchor_revision.present? }
    .min

  all_versions = if min_anchor_rev
    new_version.plan.plan_versions
      .where("revision > ? AND revision <= ?", min_anchor_rev, new_version.revision)
      .order(revision: :asc)
      .to_a
  else
    []
  end

  anchored_threads.each do |thread|
    unless thread.anchor_start.present? && thread.anchor_end.present? && thread.anchor_revision.present?
      thread.update_columns(out_of_date: true, out_of_date_since_version_id: new_version.id)
      next
    end

    intervening = all_versions.select { |v| v.revision > thread.anchor_revision && v.revision <= new_version.revision }

    begin
      new_range = Plans::TransformRange.transform_through_versions(
        [thread.anchor_start, thread.anchor_end],
        intervening
      )
      thread.update_columns(
        anchor_start: new_range[0],
        anchor_end: new_range[1],
        anchor_revision: new_version.revision
      )
    rescue Plans::TransformRange::Conflict
      thread.update_columns(
        out_of_date: true,
        out_of_date_since_version_id: new_version.id
      )
    end
  end
end

Instance Method Details

#accept!(user) ⇒ Object



96
97
98
# File 'app/models/coplan/comment_thread.rb', line 96

def accept!(user)
  update!(status: "accepted", resolved_by_user: user)
end

#anchor_context_with_highlight(chars: 100) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'app/models/coplan/comment_thread.rb', line 109

def anchor_context_with_highlight(chars: 100)
  return nil unless anchored? && anchor_start.present?

  content = plan.current_content
  return nil unless content.present?

  context_start = [anchor_start - chars, 0].max
  context_end = [anchor_end + chars, content.length].min

  before = content[context_start...anchor_start]
  anchor = content[anchor_start...anchor_end]
  after = content[anchor_end...context_end]

  "#{before}**#{anchor}**#{after}"
end

#anchor_preview(max_length: 80) ⇒ Object



87
88
89
90
# File 'app/models/coplan/comment_thread.rb', line 87

def anchor_preview(max_length: 80)
  return nil unless anchored?
  anchor_text.length > max_length ? "#{anchor_text[0...max_length]}…" : anchor_text
end

#anchor_valid?Boolean

Returns:



104
105
106
107
# File 'app/models/coplan/comment_thread.rb', line 104

def anchor_valid?
  return true unless anchored?
  !out_of_date
end

#anchored?Boolean

Returns:



74
75
76
# File 'app/models/coplan/comment_thread.rb', line 74

def anchored?
  anchor_text.present?
end

#dismiss!(user) ⇒ Object



100
101
102
# File 'app/models/coplan/comment_thread.rb', line 100

def dismiss!(user)
  update!(status: "dismissed", resolved_by_user: user)
end

#line_range_textObject



82
83
84
85
# File 'app/models/coplan/comment_thread.rb', line 82

def line_range_text
  return nil unless line_specific?
  start_line == end_line ? "Line #{start_line}" : "Lines #{start_line}–#{end_line}"
end

#line_specific?Boolean

Returns:



78
79
80
# File 'app/models/coplan/comment_thread.rb', line 78

def line_specific?
  start_line.present?
end

#resolve!(user) ⇒ Object



92
93
94
# File 'app/models/coplan/comment_thread.rb', line 92

def resolve!(user)
  update!(status: "resolved", resolved_by_user: user)
end