Module: PaperTrailDiff::Support

Defined in:
lib/paper_trail_diff/support.rb,
sig/generated/paper_trail_diff/support.rbs

Overview

Internal helpers for isolating and serializing values held by result objects.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.ambiguous_message(pair) ⇒ Object

Signature:

  • (Array[untyped]) -> String



90
91
92
93
94
95
96
# File 'lib/paper_trail_diff/support.rb', line 90

def ambiguous_message(pair)
  left, right = pair
  "versions #{left.id.inspect} and #{right.id.inspect} share the timestamp " \
    "#{left.created_at.inspect} and have ids that do not order them, so their " \
    'sequence cannot be recovered; record versions at sub-second precision or ' \
    'with sequential ids'
end

.ambiguous_pair(sorted) ⇒ Object

Signature:

  • (Array[untyped]) -> Array[untyped]?



68
69
70
71
72
73
# File 'lib/paper_trail_diff/support.rb', line 68

def ambiguous_pair(sorted)
  sorted.each_cons(2).find do |left, right|
    left.created_at == right.created_at &&
      !(sequential_id?(left.id) && sequential_id?(right.id))
  end
end

.association_path(parent, name) ⇒ Object

Signature:

  • (String, String) -> String



105
106
107
# File 'lib/paper_trail_diff/support.rb', line 105

def association_path(parent, name)
  parent.empty? ? name : "#{parent}.#{name}"
end

.chronological_sort(versions) ⇒ Object

Ordering falls back to the id whenever timestamps tie, which recovers the real order only while ids increase with insertion. An autoincrement id does; a UUID does not, so a tie between UUID-keyed versions is genuinely unorderable and any timeline built from it would be fiction.

Signature:

  • (Array[untyped]) -> Array[untyped]

Raises:



59
60
61
62
63
64
65
# File 'lib/paper_trail_diff/support.rb', line 59

def chronological_sort(versions)
  sorted = versions.sort_by { |version| chronological_version_key(version) }
  ambiguous = ambiguous_pair(sorted)
  return sorted unless ambiguous

  raise AmbiguousVersionOrderError, ambiguous_message(ambiguous)
end

.chronological_version_key(version) ⇒ Object

Signature:

  • (untyped) -> Array[untyped]



50
51
52
# File 'lib/paper_trail_diff/support.rb', line 50

def chronological_version_key(version)
  [version.created_at, version.id.to_s.rjust(32, '0')]
end

.compare_versions(left, right) ⇒ Object

Signature:

  • (untyped, untyped) -> Integer



99
100
101
102
# File 'lib/paper_trail_diff/support.rb', line 99

def compare_versions(left, right)
  chronological_version_key(left) <=> chronological_version_key(right) ||
    raise(ConfigurationError, 'versions have incomparable timestamps')
end

.immutable_copy(value) ⇒ Object

Signature:

  • (untyped) -> untyped



10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/paper_trail_diff/support.rb', line 10

def immutable_copy(value)
  return value if value.frozen?

  case value
  when Hash
    immutable_hash(value)
  when Array
    value.map { |item| immutable_copy(item) }.freeze
  else
    duplicate_and_freeze(value)
  end
end

.merge_record_groups(existing, incoming) ⇒ Object

Signature:

  • (Hash[String, Hash[Symbol, untyped]], Hash[String, Hash[Symbol, untyped]]) -> Hash[String, Hash[Symbol, untyped]]



110
111
112
113
114
115
116
# File 'lib/paper_trail_diff/support.rb', line 110

def merge_record_groups(existing, incoming)
  existing.merge(incoming) do |_name, left, right|
    merged = left.merge(right, ids: (left.fetch(:ids) | right.fetch(:ids)))
    owners = merge_record_owners(left[:owners], right[:owners])
    owners ? merged.merge(owners: owners) : merged
  end
end

.sequential_id?(id) ⇒ Boolean

Signature:

  • (untyped) -> bool

Returns:

  • (Boolean)


85
86
87
# File 'lib/paper_trail_diff/support.rb', line 85

def sequential_id?(id)
  id.is_a?(Integer) || id.to_s.match?(/\A\d+\z/)
end

.serialize(value) ⇒ Object

Signature:

  • (untyped) -> untyped



34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/paper_trail_diff/support.rb', line 34

def serialize(value)
  case value
  when Hash
    serialized = {} #: Hash[untyped, untyped]
    value.each do |key, item|
      serialized[key] = serialize(item)
    end
    serialized
  when Array
    value.map { |item| serialize(item) }
  else
    paper_trail_diff_value?(value) ? value.to_h : value
  end
end

.tied_timestamp_pair(versions) ⇒ Object

Versions sharing a timestamp, whether or not their ids order them. PT-AT indexes association membership per version but resolves it by timestamp, so association state cannot be told apart across such a pair even when the scalar sequence is perfectly recoverable.

Signature:

  • (Array[untyped]) -> Array[untyped]?



80
81
82
# File 'lib/paper_trail_diff/support.rb', line 80

def tied_timestamp_pair(versions)
  versions.each_cons(2).find { |left, right| left.created_at == right.created_at }
end

Instance Method Details

#self?.ambiguous_messageString

: (Array) -> String

Parameters:

  • (Array[untyped])

Returns:

  • (String)


39
# File 'sig/generated/paper_trail_diff/support.rbs', line 39

def self?.ambiguous_message: (Array[untyped]) -> String

#self?.ambiguous_pairArray[untyped]?

: (Array) -> Array?

Parameters:

  • (Array[untyped])

Returns:

  • (Array[untyped], nil)


26
# File 'sig/generated/paper_trail_diff/support.rbs', line 26

def self?.ambiguous_pair: (Array[untyped]) -> Array[untyped]?

#self?.association_pathString

: (String, String) -> String

Parameters:

  • (String)
  • (String)

Returns:

  • (String)


45
# File 'sig/generated/paper_trail_diff/support.rbs', line 45

def self?.association_path: (String, String) -> String

#self?.chronological_sortArray[untyped]

Ordering falls back to the id whenever timestamps tie, which recovers the real order only while ids increase with insertion. An autoincrement id does; a UUID does not, so a tie between UUID-keyed versions is genuinely unorderable and any timeline built from it would be fiction. : (Array) -> Array

Parameters:

  • (Array[untyped])

Returns:

  • (Array[untyped])


23
# File 'sig/generated/paper_trail_diff/support.rbs', line 23

def self?.chronological_sort: (Array[untyped]) -> Array[untyped]

#self?.chronological_version_keyArray[untyped]

: (untyped) -> Array

Parameters:

  • (Object)

Returns:

  • (Array[untyped])


16
# File 'sig/generated/paper_trail_diff/support.rbs', line 16

def self?.chronological_version_key: (untyped) -> Array[untyped]

#self?.compare_versionsInteger

: (untyped, untyped) -> Integer

Parameters:

  • (Object)
  • (Object)

Returns:

  • (Integer)


42
# File 'sig/generated/paper_trail_diff/support.rbs', line 42

def self?.compare_versions: (untyped, untyped) -> Integer

#self?.duplicate_and_freezeObject

: (untyped) -> untyped

Parameters:

  • (Object)

Returns:

  • (Object)


51
# File 'sig/generated/paper_trail_diff/support.rbs', line 51

def self?.duplicate_and_freeze: (untyped) -> untyped

#self?.immutable_copyObject

: (untyped) -> untyped

Parameters:

  • (Object)

Returns:

  • (Object)


7
# File 'sig/generated/paper_trail_diff/support.rbs', line 7

def self?.immutable_copy: (untyped) -> untyped

#self?.immutable_hashHash[untyped, untyped]

: (Hash[untyped, untyped]) -> Hash[untyped, untyped]

Parameters:

  • (Hash[untyped, untyped])

Returns:

  • (Hash[untyped, untyped])


10
# File 'sig/generated/paper_trail_diff/support.rbs', line 10

def self?.immutable_hash: (Hash[untyped, untyped]) -> Hash[untyped, untyped]

#self?.merge_record_groupsHash[String, Hash[Symbol, untyped]]

: (Hash[String, Hash[Symbol, untyped]], Hash[String, Hash[Symbol, untyped]]) -> Hash[String, Hash[Symbol, untyped]]

Parameters:

  • (Hash[String, Hash[Symbol, untyped]])
  • (Hash[String, Hash[Symbol, untyped]])

Returns:

  • (Hash[String, Hash[Symbol, untyped]])


48
# File 'sig/generated/paper_trail_diff/support.rbs', line 48

def self?.merge_record_groups: (Hash[String, Hash[Symbol, untyped]], Hash[String, Hash[Symbol, untyped]]) -> Hash[String, Hash[Symbol, untyped]]

#self?.merge_record_ownersHash[String, Array[untyped]]?

: (Hash[String, Array[untyped]]?, Hash[String, Array[untyped]]?) -> Hash[String, Array[untyped]]?

Parameters:

  • (Hash[String, Array[untyped]], nil)
  • (Hash[String, Array[untyped]], nil)

Returns:

  • (Hash[String, Array[untyped]], nil)


54
# File 'sig/generated/paper_trail_diff/support.rbs', line 54

def self?.merge_record_owners: (Hash[String, Array[untyped]]?, Hash[String, Array[untyped]]?) -> Hash[String, Array[untyped]]?

#self?.paper_trail_diff_value?Boolean

: (untyped) -> bool

Parameters:

  • (Object)

Returns:

  • (Boolean)


57
# File 'sig/generated/paper_trail_diff/support.rbs', line 57

def self?.paper_trail_diff_value?: (untyped) -> bool

#self?.sequential_id?Boolean

: (untyped) -> bool

Parameters:

  • (Object)

Returns:

  • (Boolean)


36
# File 'sig/generated/paper_trail_diff/support.rbs', line 36

def self?.sequential_id?: (untyped) -> bool

#self?.serializeObject

: (untyped) -> untyped

Parameters:

  • (Object)

Returns:

  • (Object)


13
# File 'sig/generated/paper_trail_diff/support.rbs', line 13

def self?.serialize: (untyped) -> untyped

#self?.tied_timestamp_pairArray[untyped]?

Versions sharing a timestamp, whether or not their ids order them. PT-AT indexes association membership per version but resolves it by timestamp, so association state cannot be told apart across such a pair even when the scalar sequence is perfectly recoverable. : (Array) -> Array?

Parameters:

  • (Array[untyped])

Returns:

  • (Array[untyped], nil)


33
# File 'sig/generated/paper_trail_diff/support.rbs', line 33

def self?.tied_timestamp_pair: (Array[untyped]) -> Array[untyped]?