dionysus-rb 1.7.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a103f61431f824a5d8769b0d2ed6d3ab6b9493690b3503726ed87d8790da7dd9
4
- data.tar.gz: 9d87121dd840665b4bba91771b5f3e7f2a27e753d2dd374b5e461f1db172aa8d
3
+ metadata.gz: 4b87f084174020b048f571661e800ea514567cce186ed7701c8db43ada5f35be
4
+ data.tar.gz: 62f4126b20a85405bb3337eb9b0bd71d540e39943d2a2e1b5a87b86c1eabbe5a
5
5
  SHA512:
6
- metadata.gz: 784b7e7894cfabc80c1146a9ac93ce7d466a872cb3d86caec664fd1f2c1fa453d125ca2528b03e1a6d55b71fc15f87fdd54eb05cebb32639365c144a7303777d
7
- data.tar.gz: b22a989af8d7a2cfe867f6ad1485ebf88f312ba3eb7e0ea0df52663cd60a552cf1576c1c7fc85ee4caffb576397c2684d06f596e8bf993c6d6b3cdf610dcf837
6
+ metadata.gz: e0bb5902c1a0a9aaeec93ffbd16b0408fbe0288252ef36e31cca95a67ee2eb15bdb41cf120af4f6a6aec60735a2c2d9f6b571d4aad3a506ef2e214f7731cc7d7
7
+ data.tar.gz: ef7a256c9ca43d3987055df6c49abfe5fcdbf53bd855b7af00ba39f0f4a394dd3923559d69d1b8abc4e8639bc0f7a6c0398b55c2b86c40188a9ee5512ee053e3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.7.1]
4
+ - Parse an ISO8601 string timestamp in the payload instead of ignoring it. 1.7.0 required every timestamp to answer `acts_like_time?`, so a serializer that renders the parent's `updated_at` as a string made the whole class a silent no-op: the parent never looked time-like, no payload was ever examined, no row was ever corrected and `dionysus.publish.timestamp_repair` never fired.
5
+
3
6
  ## [1.7.0]
4
7
  - Correct a row whose own `updated_at` predates a record embedded in its payload, then serialize again, behind `config.touch_records_behind_their_embedded_records` (default `false`). A serializer reads the parent's columns before the associations, so a child committed in between arrives under a timestamp older than itself, and the consumer discards the payload together with that child.
5
8
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dionysus-rb (1.7.0)
4
+ dionysus-rb (1.7.1)
5
5
  activerecord (>= 5)
6
6
  activesupport (>= 3.2)
7
7
  concurrent-ruby
@@ -3,6 +3,7 @@
3
3
  class Dionysus::Producer::EmbeddedTimestampRepair
4
4
  TIMESTAMP_ATTRIBUTE = "updated_at"
5
5
  PRIMARY_KEY_ATTRIBUTE = "id"
6
+ ISO8601_WITH_OFFSET = %r{\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})\z}
6
7
 
7
8
  # Takes the block that serializes, so a repaired record can be serialized again in one place.
8
9
  def self.call(records, config, &)
@@ -43,8 +44,9 @@ class Dionysus::Producer::EmbeddedTimestampRepair
43
44
  def repaired?(record, record_payload)
44
45
  return false unless repairable?(record, record_payload)
45
46
 
47
+ published_at = coerce_time(record_payload[TIMESTAMP_ATTRIBUTE])
46
48
  latest = embedded_timestamps(record_payload).max
47
- return false unless latest && latest > record_payload[TIMESTAMP_ATTRIBUTE]
49
+ return false unless published_at && latest && latest > published_at
48
50
 
49
51
  # WHERE guards the write: a stale read must never move the row backwards. When the row is already
50
52
  # ahead nothing is written, and the reload alone repairs the payload.
@@ -61,8 +63,7 @@ class Dionysus::Producer::EmbeddedTimestampRepair
61
63
 
62
64
  def repairable?(record, record_payload)
63
65
  record.is_a?(ActiveRecord::Base) && record.persisted? &&
64
- record_payload.is_a?(Hash) && record_payload[PRIMARY_KEY_ATTRIBUTE] == record.id &&
65
- time_like?(record_payload[TIMESTAMP_ATTRIBUTE])
66
+ record_payload.is_a?(Hash) && record_payload[PRIMARY_KEY_ATTRIBUTE] == record.id
66
67
  end
67
68
 
68
69
  def embedded_timestamps(record_payload)
@@ -70,8 +71,7 @@ class Dionysus::Producer::EmbeddedTimestampRepair
70
71
  Array.wrap(value).filter_map do |embedded|
71
72
  next unless embedded.is_a?(Hash)
72
73
 
73
- timestamp = embedded[TIMESTAMP_ATTRIBUTE]
74
- timestamp if time_like?(timestamp)
74
+ coerce_time(embedded[TIMESTAMP_ATTRIBUTE])
75
75
  end
76
76
  end
77
77
  end
@@ -81,7 +81,23 @@ class Dionysus::Producer::EmbeddedTimestampRepair
81
81
  tags: ["model:#{record.class}", "outcome:#{updated.zero? ? "stale_read" : "row_behind"}"])
82
82
  end
83
83
 
84
- def time_like?(value)
85
- value.respond_to?(:acts_like_time?) && value.acts_like_time?
84
+ # A serializer may render the timestamp it is about to publish, so one payload carries both shapes.
85
+ def coerce_time(value)
86
+ case value
87
+ when ActiveSupport::TimeWithZone, Time then value
88
+ when String then parse_iso8601(value)
89
+ end
90
+ end
91
+
92
+ def parse_iso8601(value)
93
+ return nil unless ISO8601_WITH_OFFSET.match?(value)
94
+
95
+ time = Time.iso8601(value)
96
+ # Time.iso8601 normalizes an impossible date: 2026-09-31 becomes October 1.
97
+ return nil unless value.start_with?(time.strftime("%Y-%m-%d"))
98
+
99
+ time.in_time_zone
100
+ rescue ArgumentError
101
+ nil
86
102
  end
87
103
  end
@@ -3,5 +3,5 @@
3
3
  module Dionysus
4
4
  module Version
5
5
  end
6
- VERSION = "1.7.0"
6
+ VERSION = "1.7.1"
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dionysus-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.0
4
+ version: 1.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karol Galanciak
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-07 00:00:00.000000000 Z
11
+ date: 2026-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord