Class: PaperTrailDiff::HistoryDiagnostics

Inherits:
Object
  • Object
show all
Defined in:
lib/paper_trail_diff/diagnostics.rb,
sig/generated/paper_trail_diff/diagnostics.rbs

Overview

Performs read-only checks for known PaperTrail/PT-AT reconstruction hazards.

Instance Method Summary collapse

Constructor Details

#initialize(from_version, to_version, associations:) ⇒ HistoryDiagnostics

: (untyped, untyped, associations: Array[String | Symbol]) -> void

Parameters:

  • (Object)
  • (Object)
  • associations: (Array[String | Symbol])


69
70
71
72
73
74
# File 'lib/paper_trail_diff/diagnostics.rb', line 69

def initialize(from_version, to_version, associations:)
  @from_version = from_version
  @to_version = to_version
  @tree = AssociationTree.build(associations)
  @issues = [] #: Array[DiagnosticIssue]
end

Instance Method Details

#add_error(code, message, path = nil, version_id = nil) ⇒ void

This method returns an undefined value.

: (Symbol, String, ?String?, ?untyped) -> void

Parameters:

  • (Symbol)
  • (String)
  • (String, nil)
  • (Object)


251
252
253
# File 'lib/paper_trail_diff/diagnostics.rb', line 251

def add_error(code, message, path = nil, version_id = nil)
  add_issue(:error, code, message, path, version_id)
end

#add_issue(severity, code, message, path, version_id) ⇒ void

This method returns an undefined value.

: (Symbol, Symbol, String, String?, untyped) -> void

Parameters:

  • (Symbol)
  • (Symbol)
  • (String)
  • (String, nil)
  • (Object)


261
262
263
264
265
266
267
268
269
# File 'lib/paper_trail_diff/diagnostics.rb', line 261

def add_issue(severity, code, message, path, version_id)
  @issues << DiagnosticIssue.new(
    severity: severity,
    code: code,
    message: message,
    path: path,
    version_id: version_id
  )
end

#add_warning(code, message, path = nil) ⇒ void

This method returns an undefined value.

: (Symbol, String, ?String?) -> void

Parameters:

  • (Symbol)
  • (String)
  • (String, nil)


256
257
258
# File 'lib/paper_trail_diff/diagnostics.rb', line 256

def add_warning(code, message, path = nil)
  add_issue(:warning, code, message, path, nil)
end

#association_tracking_available?Boolean

: () -> bool

Returns:

  • (Boolean)


227
228
229
230
231
232
# File 'lib/paper_trail_diff/diagnostics.rb', line 227

def association_tracking_available?
  paper_trail = Object.const_get(:PaperTrail) #: untyped
  config = paper_trail.config #: untyped
  !!(defined?(::PaperTrailAssociationTracking) &&
    config.respond_to?(:track_associations?) && config.track_associations?)
end

#callDiagnosticReport

: () -> DiagnosticReport

Returns:



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/paper_trail_diff/diagnostics.rb', line 77

def call
  model_class = validated_model_class
  # Runs whether or not associations are selected: unorderable versions
  # corrupt a scalar timeline just as surely, and a report that inspected
  # nothing has no business answering `ok?`.
  inspect_version_order
  return DiagnosticReport.new(issues: @issues) if @tree.empty?

  unless association_tracking_available?
    add_error(:association_tracking_unavailable, tracking_unavailable_message)
    return DiagnosticReport.new(issues: @issues)
  end

  traversal = AssociationTraversal.new(@tree)
  selected = traversal.selected_reflections(model_class)
  inspect_targets(selected)
  inspect_checkpoint_timestamp(model_class)
  inspect_habtm(selected)
  DiagnosticReport.new(issues: @issues)
end

#inspect_checkpoint_timestamp(model_class) ⇒ void

This method returns an undefined value.

: (untyped) -> void

Parameters:

  • (Object)


165
166
167
168
169
170
# File 'lib/paper_trail_diff/diagnostics.rb', line 165

def inspect_checkpoint_timestamp(model_class)
  return if synchronized_timestamp_disabled?(model_class)

  message = 'manual checkpoints should set synchronize_version_creation_timestamp: false'
  add_warning(:synchronized_version_timestamp, message)
end

#inspect_habtm(selected) ⇒ void

This method returns an undefined value.

: (Array[Array[untyped]]) -> void

Parameters:

  • (Array[Array[untyped]])


130
131
132
133
134
135
136
137
# File 'lib/paper_trail_diff/diagnostics.rb', line 130

def inspect_habtm(selected)
  paths = selected.filter_map do |path, reflection|
    path if reflection.macro == :has_and_belongs_to_many
  end
  return if paths.empty?

  (paths)
end

#inspect_targets(selected) ⇒ void

This method returns an undefined value.

: (Array[Array[untyped]]) -> void

Parameters:

  • (Array[Array[untyped]])


117
118
119
120
121
122
123
124
125
126
127
# File 'lib/paper_trail_diff/diagnostics.rb', line 117

def inspect_targets(selected)
  selected.each do |path, reflection|
    if reflection.polymorphic?
      add_warning(:polymorphic_target_unverified, 'polymorphic target cannot be verified', path)
      next
    end

    inspect_versioned_model(reflection.klass, path)
    inspect_through_model(reflection, path) if reflection.options[:through]
  end
end

#inspect_through_model(reflection, path) ⇒ void

This method returns an undefined value.

: (untyped, String) -> void

Parameters:

  • (Object)
  • (String)


202
203
204
205
206
207
208
209
210
211
# File 'lib/paper_trail_diff/diagnostics.rb', line 202

def inspect_through_model(reflection, path)
  model_class = reflection.through_reflection.klass
  return if model_versioned?(model_class)

  add_warning(
    :unversioned_through_model,
    "#{model_class.name} does not appear to have PaperTrail enabled",
    path
  )
end

#inspect_transaction_metadata(paths) ⇒ void

This method returns an undefined value.

: (Array) -> void

Parameters:

  • (Array[String])


173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/paper_trail_diff/diagnostics.rb', line 173

def (paths)
  [@from_version, @to_version].each do |version|
    if !version.respond_to?(:transaction_id)
      add_error(:transaction_id_column_missing, 'version has no transaction_id column')
    elsif version.transaction_id.nil?
      paths.each do |path|
        add_error(
          :transaction_id_missing,
          'HABTM endpoint has no transaction-backed association snapshot',
          path,
          version.id
        )
      end
    end
  end
end

#inspect_version_ordervoid

This method returns an undefined value.

Ordering falls back to the id when timestamps tie, which only recovers the real sequence for ids that increase with insertion. Reported before a run rather than after a wrong answer. : () -> void



143
144
145
146
147
148
149
150
# File 'lib/paper_trail_diff/diagnostics.rb', line 143

def inspect_version_order
  pair = Support.ambiguous_pair(ordered_range_versions)
  return unless pair

  add_error(:ambiguous_version_order, Support.ambiguous_message(pair), nil, pair.first.id)
rescue StandardError
  nil
end

#inspect_versioned_model(model_class, path) ⇒ void

This method returns an undefined value.

: (untyped, String) -> void

Parameters:

  • (Object)
  • (String)


191
192
193
194
195
196
197
198
199
# File 'lib/paper_trail_diff/diagnostics.rb', line 191

def inspect_versioned_model(model_class, path)
  return if model_versioned?(model_class)

  add_warning(
    :unversioned_association_target,
    "#{model_class.name} does not appear to have PaperTrail enabled",
    path
  )
end

#model_versioned?(model_class) ⇒ Boolean

: (untyped) -> bool

Parameters:

  • (Object)

Returns:

  • (Boolean)


214
215
216
# File 'lib/paper_trail_diff/diagnostics.rb', line 214

def model_versioned?(model_class)
  model_class.respond_to?(:paper_trail_options) && !model_class.paper_trail_options.nil?
end

#ordered_range_versionsArray[untyped]

: () -> Array

Returns:

  • (Array[untyped])


153
154
155
156
157
158
159
160
161
162
# File 'lib/paper_trail_diff/diagnostics.rb', line 153

def ordered_range_versions
  bounds = [@from_version.created_at, @to_version.created_at].compact.sort
  return [] unless bounds.length == 2

  @from_version.class
               .where(item_type: @from_version.item_type, item_id: @from_version.item_id)
               .where(created_at: bounds.first..bounds.last)
               .to_a
               .sort_by { |version| Support.chronological_version_key(version) }
end

#synchronized_timestamp_disabled?(model_class) ⇒ Boolean

: (untyped) -> bool

Parameters:

  • (Object)

Returns:

  • (Boolean)


219
220
221
222
223
224
# File 'lib/paper_trail_diff/diagnostics.rb', line 219

def synchronized_timestamp_disabled?(model_class)
  options = model_class.paper_trail_options
  options[:synchronize_version_creation_timestamp] == false
rescue NoMethodError
  false
end

#tracking_unavailable_messageString

: () -> String

Returns:

  • (String)


246
247
248
# File 'lib/paper_trail_diff/diagnostics.rb', line 246

def tracking_unavailable_message
  'association tracking must be loaded and enabled to diagnose associations'
end

#validated_model_classObject

: () -> untyped

Returns:

  • (Object)


106
107
108
109
110
111
112
113
114
# File 'lib/paper_trail_diff/diagnostics.rb', line 106

def validated_model_class
  unless version_identity(@from_version) == version_identity(@to_version)
    raise VersionMismatchError, 'versions must belong to the same PaperTrail item'
  end

  Object.const_get(version_type(@from_version))
rescue NoMethodError => e
  raise VersionMismatchError, 'expected PaperTrail version endpoints', cause: e
end

#version_identity(version) ⇒ Array[String]

: (untyped) -> Array

Parameters:

  • (Object)

Returns:

  • (Array[String])


235
236
237
# File 'lib/paper_trail_diff/diagnostics.rb', line 235

def version_identity(version)
  [version.item_type.to_s, version.item_id.to_s]
end

#version_type(version) ⇒ String

: (untyped) -> String

Parameters:

  • (Object)

Returns:

  • (String)


240
241
242
243
# File 'lib/paper_trail_diff/diagnostics.rb', line 240

def version_type(version)
  subtype = version.item_subtype if version.respond_to?(:item_subtype)
  subtype.to_s.empty? ? version.item_type.to_s : subtype.to_s
end