2019-09-18 15:09:14 +02:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2021-04-16 10:34:02 +02:00
|
|
|
require 'integrity/checksum'
|
2023-03-23 17:39:06 +01:00
|
|
|
require 'json'
|
2021-02-23 14:04:42 +01:00
|
|
|
|
2019-09-18 15:09:14 +02:00
|
|
|
# Provides helper methods to compute footprints
|
|
|
|
class FootprintService
|
2021-02-23 14:04:42 +01:00
|
|
|
class << self
|
2023-03-23 17:39:06 +01:00
|
|
|
# @param item [Footprintable]
|
|
|
|
# @param previous_footprint [String,NilClass]
|
2023-03-24 17:21:44 +01:00
|
|
|
# @param columns [Array<String>]
|
|
|
|
# @return [Hash<Symbol->String,Integer,Hash>]
|
|
|
|
def chained_data(item, previous_footprint = nil, columns = nil)
|
|
|
|
columns ||= footprint_columns(item.class)
|
2023-03-23 17:39:06 +01:00
|
|
|
res = {}
|
|
|
|
columns.each do |column|
|
|
|
|
next if column.blank? || item[column].blank?
|
|
|
|
|
|
|
|
res[column] = comparable(item[column])
|
2021-04-20 17:22:53 +02:00
|
|
|
rescue ActiveModel::MissingAttributeError
|
2023-03-23 17:39:06 +01:00
|
|
|
res[column] = nil
|
2021-04-20 17:22:53 +02:00
|
|
|
end
|
2023-03-23 17:39:06 +01:00
|
|
|
res['previous'] = previous_footprint
|
|
|
|
res.sort.to_h
|
2021-02-23 14:04:42 +01:00
|
|
|
end
|
2020-07-21 19:25:21 +02:00
|
|
|
|
2021-02-23 14:04:42 +01:00
|
|
|
# Return an ordered array of the columns used in the footprint computation
|
2023-03-23 17:39:06 +01:00
|
|
|
# @param klass [Class] a class inheriting from Footprintable
|
2021-02-23 14:04:42 +01:00
|
|
|
def footprint_columns(klass)
|
2023-03-23 17:39:06 +01:00
|
|
|
%w[id].concat(klass.columns.map(&:name).delete_if do |column|
|
|
|
|
%w[id footprint updated_at].concat(klass.columns_out_of_footprint).include?(column)
|
|
|
|
end.sort)
|
2021-02-23 14:04:42 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
# Logs a debugging message to help finding why a footprint is invalid
|
2023-03-23 17:39:06 +01:00
|
|
|
# @param klass [Class] a class inheriting from Footprintable
|
|
|
|
# @param item [Footprintable] an instance of the provided class
|
2021-02-23 14:04:42 +01:00
|
|
|
def debug_footprint(klass, item)
|
2023-03-24 17:21:44 +01:00
|
|
|
current = chained_data(item, item.chained_element.previous&.footprint, item.chained_element.columns)
|
2023-03-23 17:39:06 +01:00
|
|
|
saved = item.chained_element&.content&.sort&.to_h&.transform_values { |val| val.is_a?(Hash) ? val.sort.to_h : val }
|
2021-05-31 12:19:28 +02:00
|
|
|
|
2021-05-17 17:04:52 +02:00
|
|
|
if saved.nil?
|
2023-03-23 17:39:06 +01:00
|
|
|
" #{klass} [ id: #{item.id} ] is not chained"
|
2021-05-17 17:04:52 +02:00
|
|
|
else
|
2023-03-23 17:39:06 +01:00
|
|
|
"Debug footprint for #{klass} [ id: #{item.id} ]\n" \
|
|
|
|
"-----------------------------------------\n" \
|
|
|
|
"=== current ===\n#{JSON.pretty_generate(current)}\n\n=== saved ===\n#{JSON.pretty_generate(saved)}\n" \
|
|
|
|
"-----------------------------------------\n" +
|
|
|
|
item.footprint_children.map(&:debug_footprint).join("\n\n")
|
2021-05-17 17:04:52 +02:00
|
|
|
end
|
2021-02-23 14:04:42 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
# Return a comparable value for jsonb fields (with keys ordered alphabetically)
|
|
|
|
def comparable(value)
|
2023-03-23 17:39:06 +01:00
|
|
|
return value.iso8601 if value.is_a? Time
|
2021-04-26 11:40:26 +02:00
|
|
|
return value unless value.is_a? Hash
|
2020-07-21 19:25:21 +02:00
|
|
|
|
2023-03-23 17:39:06 +01:00
|
|
|
value.sort.to_h.transform_values! { |v| comparable(v) }
|
2021-02-23 14:04:42 +01:00
|
|
|
end
|
2019-09-18 15:09:14 +02:00
|
|
|
end
|
|
|
|
end
|