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
- .ambiguous_message(pair) ⇒ Object
- .ambiguous_pair(sorted) ⇒ Object
- .association_path(parent, name) ⇒ Object
-
.chronological_sort(versions) ⇒ Object
Ordering falls back to the id whenever timestamps tie, which recovers the real order only while ids increase with insertion.
- .chronological_version_key(version) ⇒ Object
- .compare_versions(left, right) ⇒ Object
- .immutable_copy(value) ⇒ Object
- .merge_record_groups(existing, incoming) ⇒ Object
- .sequential_id?(id) ⇒ Boolean
- .serialize(value) ⇒ Object
-
.tied_timestamp_pair(versions) ⇒ Object
Versions sharing a timestamp, whether or not their ids order them.
-
.versioned?(model_class) ⇒ Boolean
Whether a model records history at all.
Instance Method Summary collapse
-
#self?.ambiguous_message ⇒ String
: (Array) -> String.
- #self?.ambiguous_pair ⇒ Array[untyped]?
-
#self?.association_path ⇒ String
: (String, String) -> String.
-
#self?.chronological_sort ⇒ Array[untyped]
Ordering falls back to the id whenever timestamps tie, which recovers the real order only while ids increase with insertion.
-
#self?.chronological_version_key ⇒ Array[untyped]
: (untyped) -> Array.
-
#self?.compare_versions ⇒ Integer
: (untyped, untyped) -> Integer.
-
#self?.duplicate_and_freeze ⇒ Object
: (untyped) -> untyped.
-
#self?.immutable_copy ⇒ Object
: (untyped) -> untyped.
-
#self?.immutable_hash ⇒ Hash[untyped, untyped]
: (Hash[untyped, untyped]) -> Hash[untyped, untyped].
-
#self?.merge_record_groups ⇒ Hash[String, Hash[Symbol, untyped]]
: (Hash[String, Hash[Symbol, untyped]], Hash[String, Hash[Symbol, untyped]]) -> Hash[String, Hash[Symbol, untyped]].
-
#self?.merge_record_owners ⇒ Hash[String, Array[untyped]]?
: (Hash[String, Array[untyped]]?, Hash[String, Array[untyped]]?) -> Hash[String, Array[untyped]]?.
-
#self?.paper_trail_diff_value? ⇒ Boolean
: (untyped) -> bool.
-
#self?.sequential_id? ⇒ Boolean
: (untyped) -> bool.
-
#self?.serialize ⇒ Object
: (untyped) -> untyped.
-
#self?.tied_timestamp_pair ⇒ Array[untyped]?
Versions sharing a timestamp, whether or not their ids order them.
-
#self?.versioned? ⇒ Boolean
Whether a model records history at all.
Class Method Details
.ambiguous_message(pair) ⇒ Object
103 104 105 106 107 108 109 |
# File 'lib/paper_trail_diff/support.rb', line 103 def (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
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
118 119 120 |
# File 'lib/paper_trail_diff/support.rb', line 118 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.
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) end |
.chronological_version_key(version) ⇒ Object
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
112 113 114 115 |
# File 'lib/paper_trail_diff/support.rb', line 112 def compare_versions(left, right) chronological_version_key(left) <=> chronological_version_key(right) || raise(ConfigurationError, 'versions have incomparable timestamps') end |
.immutable_copy(value) ⇒ Object
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
123 124 125 126 127 128 129 |
# File 'lib/paper_trail_diff/support.rb', line 123 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
98 99 100 |
# File 'lib/paper_trail_diff/support.rb', line 98 def sequential_id?(id) id.is_a?(Integer) || id.to_s.match?(/\A\d+\z/) end |
.serialize(value) ⇒ Object
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.
80 81 82 |
# File 'lib/paper_trail_diff/support.rb', line 80 def (versions) versions.each_cons(2).find { |left, right| left.created_at == right.created_at } end |
.versioned?(model_class) ⇒ Boolean
Whether a model records history at all.
PaperTrail defines paper_trail on every ActiveRecord model, so asking
whether a class responds to it says nothing -- it is true for models that
never called has_paper_trail, and reading history from one of those fails
at the version class rather than at the question. Only configured options
distinguish the two, which is why this lives in one place: the predicate is
easy to write in a form that looks right and always answers true.
93 94 95 |
# File 'lib/paper_trail_diff/support.rb', line 93 def versioned?(model_class) model_class.respond_to?(:paper_trail_options) && !model_class..nil? end |
Instance Method Details
#self?.ambiguous_message ⇒ String
: (Array) -> String
50 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 50
def self?.ambiguous_message: (Array[untyped]) -> String
|
#self?.ambiguous_pair ⇒ Array[untyped]?
26 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 26
def self?.ambiguous_pair: (Array[untyped]) -> Array[untyped]?
|
#self?.association_path ⇒ String
: (String, String) -> String
56 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 56
def self?.association_path: (String, String) -> String
|
#self?.chronological_sort ⇒ Array[untyped]
23 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 23
def self?.chronological_sort: (Array[untyped]) -> Array[untyped]
|
#self?.chronological_version_key ⇒ Array[untyped]
: (untyped) -> Array
16 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 16
def self?.chronological_version_key: (untyped) -> Array[untyped]
|
#self?.compare_versions ⇒ Integer
: (untyped, untyped) -> Integer
53 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 53
def self?.compare_versions: (untyped, untyped) -> Integer
|
#self?.duplicate_and_freeze ⇒ Object
: (untyped) -> untyped
62 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 62
def self?.duplicate_and_freeze: (untyped) -> untyped
|
#self?.immutable_copy ⇒ Object
: (untyped) -> untyped
7 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 7
def self?.immutable_copy: (untyped) -> untyped
|
#self?.immutable_hash ⇒ Hash[untyped, untyped]
: (Hash[untyped, untyped]) -> 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_groups ⇒ Hash[String, Hash[Symbol, untyped]]
: (Hash[String, Hash[Symbol, untyped]], Hash[String, Hash[Symbol, untyped]]) -> Hash[String, Hash[Symbol, untyped]]
59 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 59
def self?.merge_record_groups: (Hash[String, Hash[Symbol, untyped]], Hash[String, Hash[Symbol, untyped]]) -> Hash[String, Hash[Symbol, untyped]]
|
#self?.merge_record_owners ⇒ Hash[String, Array[untyped]]?
: (Hash[String, Array[untyped]]?, Hash[String, Array[untyped]]?) -> Hash[String, Array[untyped]]?
65 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 65
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
68 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 68
def self?.paper_trail_diff_value?: (untyped) -> bool
|
#self?.sequential_id? ⇒ Boolean
: (untyped) -> bool
47 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 47
def self?.sequential_id?: (untyped) -> bool
|
#self?.serialize ⇒ Object
: (untyped) -> untyped
13 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 13
def self?.serialize: (untyped) -> untyped
|
#self?.tied_timestamp_pair ⇒ Array[untyped]?
33 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 33
def self?.tied_timestamp_pair: (Array[untyped]) -> Array[untyped]?
|
#self?.versioned? ⇒ Boolean
Whether a model records history at all.
PaperTrail defines paper_trail on every ActiveRecord model, so asking
whether a class responds to it says nothing -- it is true for models that
never called has_paper_trail, and reading history from one of those fails
at the version class rather than at the question. Only configured options
distinguish the two, which is why this lives in one place: the predicate is
easy to write in a form that looks right and always answers true.
: (untyped) -> bool
44 |
# File 'sig/generated/paper_trail_diff/support.rbs', line 44
def self?.versioned?: (untyped) -> bool
|