paper_trail_diff 0.1.0 → 0.3.0
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 +4 -4
- data/CHANGELOG.md +62 -0
- data/QUICKSTART.md +302 -0
- data/README.md +258 -19
- data/lib/paper_trail_diff/activity_boundary.rb +34 -3
- data/lib/paper_trail_diff/activity_child_candidate_loader.rb +73 -0
- data/lib/paper_trail_diff/activity_event_snapshot_refresher.rb +261 -45
- data/lib/paper_trail_diff/activity_history.rb +136 -0
- data/lib/paper_trail_diff/activity_range.rb +28 -12
- data/lib/paper_trail_diff/activity_snapshot_delta.rb +64 -0
- data/lib/paper_trail_diff/activity_snapshot_sequence.rb +3 -3
- data/lib/paper_trail_diff/activity_timeline_builder.rb +54 -45
- data/lib/paper_trail_diff/activity_version_collector.rb +14 -17
- data/lib/paper_trail_diff/association_diff_traversal.rb +184 -0
- data/lib/paper_trail_diff/branch_snapshot_refresher.rb +5 -3
- data/lib/paper_trail_diff/collection_comparator.rb +155 -0
- data/lib/paper_trail_diff/collection_identity_index.rb +53 -0
- data/lib/paper_trail_diff/collection_transition.rb +25 -0
- data/lib/paper_trail_diff/comparison_batch.rb +151 -0
- data/lib/paper_trail_diff/engine.rb +5 -45
- data/lib/paper_trail_diff/errors.rb +9 -0
- data/lib/paper_trail_diff/historical_snapshot_store.rb +55 -3
- data/lib/paper_trail_diff/instrumentation.rb +30 -0
- data/lib/paper_trail_diff/live_endpoint_batch_loader.rb +133 -0
- data/lib/paper_trail_diff/live_endpoint_provider.rb +48 -0
- data/lib/paper_trail_diff/live_graph_collector.rb +52 -0
- data/lib/paper_trail_diff/paper_trail_adapter.rb +61 -34
- data/lib/paper_trail_diff/preloaded_endpoint_batch_loader.rb +48 -0
- data/lib/paper_trail_diff/prepared_edge_loader.rb +54 -17
- data/lib/paper_trail_diff/prepared_history.rb +14 -4
- data/lib/paper_trail_diff/prepared_history_loader.rb +17 -9
- data/lib/paper_trail_diff/prepared_record_index.rb +138 -10
- data/lib/paper_trail_diff/snapshot.rb +66 -3
- data/lib/paper_trail_diff/snapshot_traversal.rb +97 -0
- data/lib/paper_trail_diff/step.rb +9 -0
- data/lib/paper_trail_diff/support.rb +18 -0
- data/lib/paper_trail_diff/time_activity_timeline_builder.rb +111 -0
- data/lib/paper_trail_diff/time_range.rb +85 -0
- data/lib/paper_trail_diff/time_version_range.rb +53 -0
- data/lib/paper_trail_diff/timeline_builder.rb +13 -7
- data/lib/paper_trail_diff/timeline_range.rb +72 -0
- data/lib/paper_trail_diff/traversal.rb +80 -0
- data/lib/paper_trail_diff/traversal_emitter.rb +42 -0
- data/lib/paper_trail_diff/traversal_entry.rb +94 -0
- data/lib/paper_trail_diff/version.rb +1 -1
- data/lib/paper_trail_diff/version_association_candidate_scope.rb +73 -0
- data/lib/paper_trail_diff.rb +84 -13
- data/sig/generated/paper_trail_diff/activity_boundary.rbs +17 -2
- data/sig/generated/paper_trail_diff/activity_child_candidate_loader.rbs +40 -0
- data/sig/generated/paper_trail_diff/activity_event_snapshot_refresher.rbs +49 -8
- data/sig/generated/paper_trail_diff/activity_history.rbs +79 -0
- data/sig/generated/paper_trail_diff/activity_range.rbs +14 -5
- data/sig/generated/paper_trail_diff/activity_snapshot_delta.rbs +33 -0
- data/sig/generated/paper_trail_diff/activity_snapshot_sequence.rbs +2 -2
- data/sig/generated/paper_trail_diff/activity_timeline_builder.rbs +15 -10
- data/sig/generated/paper_trail_diff/activity_version_collector.rbs +2 -5
- data/sig/generated/paper_trail_diff/association_diff_traversal.rbs +58 -0
- data/sig/generated/paper_trail_diff/branch_snapshot_refresher.rbs +2 -2
- data/sig/generated/paper_trail_diff/collection_comparator.rbs +59 -0
- data/sig/generated/paper_trail_diff/collection_identity_index.rbs +29 -0
- data/sig/generated/paper_trail_diff/collection_transition.rbs +18 -0
- data/sig/generated/paper_trail_diff/comparison_batch.rbs +65 -0
- data/sig/generated/paper_trail_diff/engine.rbs +0 -12
- data/sig/generated/paper_trail_diff/errors.rbs +12 -0
- data/sig/generated/paper_trail_diff/historical_snapshot_store.rbs +22 -2
- data/sig/generated/paper_trail_diff/instrumentation.rbs +12 -0
- data/sig/generated/paper_trail_diff/live_endpoint_batch_loader.rbs +52 -0
- data/sig/generated/paper_trail_diff/live_endpoint_provider.rbs +23 -0
- data/sig/generated/paper_trail_diff/live_graph_collector.rbs +27 -0
- data/sig/generated/paper_trail_diff/paper_trail_adapter.rbs +21 -10
- data/sig/generated/paper_trail_diff/preloaded_endpoint_batch_loader.rbs +24 -0
- data/sig/generated/paper_trail_diff/prepared_edge_loader.rbs +22 -8
- data/sig/generated/paper_trail_diff/prepared_history.rbs +6 -0
- data/sig/generated/paper_trail_diff/prepared_history_loader.rbs +4 -2
- data/sig/generated/paper_trail_diff/prepared_record_index.rbs +61 -6
- data/sig/generated/paper_trail_diff/snapshot.rbs +37 -2
- data/sig/generated/paper_trail_diff/snapshot_traversal.rbs +29 -0
- data/sig/generated/paper_trail_diff/step.rbs +7 -0
- data/sig/generated/paper_trail_diff/support.rbs +6 -0
- data/sig/generated/paper_trail_diff/time_activity_timeline_builder.rbs +46 -0
- data/sig/generated/paper_trail_diff/time_range.rbs +41 -0
- data/sig/generated/paper_trail_diff/time_version_range.rbs +27 -0
- data/sig/generated/paper_trail_diff/timeline_builder.rbs +6 -5
- data/sig/generated/paper_trail_diff/timeline_range.rbs +41 -0
- data/sig/generated/paper_trail_diff/traversal.rbs +41 -0
- data/sig/generated/paper_trail_diff/traversal_emitter.rbs +16 -0
- data/sig/generated/paper_trail_diff/traversal_entry.rbs +52 -0
- data/sig/generated/paper_trail_diff/version_association_candidate_scope.rbs +32 -0
- data/sig/generated/paper_trail_diff.rbs +24 -8
- metadata +49 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4d60569584c3d30bd35c62c1f0f699fd8859545d81f873c926dfdd8268aed3df
|
|
4
|
+
data.tar.gz: 4961a77099927fff3476641b0409cb234e1f398e9b84e9cb3cc9e11b2d580064
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2cf13bffaca3d199b2332b0e3d1c7c7c00ef7c6f82a4c9e5a75c51f376889ff57c60e1bfab1f578a7122e972698318d2720bce5f8da2755abea50aebf6e7083e
|
|
7
|
+
data.tar.gz: a071cd71f7fb766105eda1e018991ed8030885822da23382404797984b265818c0012378aea5912a507fb7bc24e9294331201b0cf1918860df3d9613476d580d
|
data/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,68 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file. The
|
|
4
4
|
project follows [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [0.3.0] - 2026-08-10
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Add `within:` time-range selection to checkpoint timelines, activity
|
|
11
|
+
timelines, and combined analysis, with inclusive/exclusive end handling and
|
|
12
|
+
explicit errors when a final mutation cannot be reconstructed.
|
|
13
|
+
- Add `compare_many` for collection reports, with batched current-root loading,
|
|
14
|
+
bounded live-association preloading, cross-root prepared history, immutable
|
|
15
|
+
identity-keyed results, and scale-invariant query regression coverage.
|
|
16
|
+
- Add opt-in reuse of fully preloaded current endpoints and namespaced
|
|
17
|
+
ActiveSupport runtime instrumentation without automatic logging, including
|
|
18
|
+
end-to-end activity-timeline duration and step counts.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Teach timeline filtering through the shared `Step#empty?` protocol in the
|
|
23
|
+
README and quickstart examples.
|
|
24
|
+
- Reuse the live graph loaded by `compare_many` while preparing historical
|
|
25
|
+
association state, and defer root version loading until an edge needs it.
|
|
26
|
+
- Reuse collection identity positions and adjacent transition hints so activity
|
|
27
|
+
steps compare only the changed member, and resolve direct nested collection
|
|
28
|
+
owners by foreign key instead of scanning every parent snapshot.
|
|
29
|
+
- Reuse prepared predecessor and successor scalar states for isolated activity
|
|
30
|
+
updates instead of reifying and deserializing each PaperTrail event again.
|
|
31
|
+
- Preserve both sides of nested collection membership moves by using the
|
|
32
|
+
general comparator when one event changes multiple parent snapshots.
|
|
33
|
+
- Preserve per-owner `limit`, `offset`, and owner-dependent association scopes
|
|
34
|
+
in `compare_many` while retaining batched preloading for safe branches.
|
|
35
|
+
- Replace lifetime association-identity materialization with an indexed start-state
|
|
36
|
+
checkpoint plus post-boundary activity and current members.
|
|
37
|
+
- Bound historical activity-child candidates at the selected end so membership
|
|
38
|
+
changes after the range cannot introduce unrelated empty timeline steps.
|
|
39
|
+
- Decode compatible prepared scalar version payloads without constructing an
|
|
40
|
+
intermediate Active Record object, with reification fallback for unsafe schemas.
|
|
41
|
+
- Reuse internally owned frozen collection arrays instead of defensively copying
|
|
42
|
+
them a second time during immutable activity reconstruction.
|
|
43
|
+
- Replace pre-existing versions-relation ordering when selecting a time range's
|
|
44
|
+
immediate trailing reconstruction boundary.
|
|
45
|
+
- Limit automatic CI to pull requests and `main` pushes, and cancel superseded
|
|
46
|
+
runs for the same pull request or ref.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- Supply TZInfo's timezone database to the test bundle so zoned-time coverage
|
|
51
|
+
runs on Windows as well as systems with a native zoneinfo database.
|
|
52
|
+
|
|
53
|
+
## [0.2.0] - 2026-08-09
|
|
54
|
+
|
|
55
|
+
### Added
|
|
56
|
+
|
|
57
|
+
- Add deterministic `Diff#each_entry` and `Diff#each_change` traversal with
|
|
58
|
+
immutable `TraversalEntry` values for renderers, counters, exports, and
|
|
59
|
+
notifications.
|
|
60
|
+
- Add immutable version metadata and record references to activity boundaries.
|
|
61
|
+
- Give checkpoint `Step` objects `from_boundary` and `to_boundary` readers and
|
|
62
|
+
give both timeline step types an `empty?` predicate.
|
|
63
|
+
- Add a Rails-focused quickstart with minimal endpoint, timeline, ignore, and
|
|
64
|
+
association examples.
|
|
65
|
+
- Execute selected README and quickstart examples in isolated core and PT-AT
|
|
66
|
+
test sessions so documented behavior cannot silently drift.
|
|
67
|
+
|
|
6
68
|
## [0.1.0] - 2026-08-09
|
|
7
69
|
|
|
8
70
|
### Added
|
data/QUICKSTART.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Quickstart
|
|
2
|
+
|
|
3
|
+
This guide gets `paper_trail_diff` running in a Rails application and shows the
|
|
4
|
+
smallest useful examples. Ruby 3.1 or newer and PaperTrail 16 or 17 are
|
|
5
|
+
supported.
|
|
6
|
+
|
|
7
|
+
## 1. Install the gem
|
|
8
|
+
|
|
9
|
+
From the Rails application directory:
|
|
10
|
+
|
|
11
|
+
```console
|
|
12
|
+
bundle add paper_trail_diff
|
|
13
|
+
bin/rails generate paper_trail:install --with-changes
|
|
14
|
+
bin/rails db:migrate
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Skip the generator if the application already has a PaperTrail `versions`
|
|
18
|
+
table. The `object_changes` column created by `--with-changes` is recommended
|
|
19
|
+
for efficient activity analysis, although endpoint comparison is based on
|
|
20
|
+
reified state rather than a PaperTrail changeset.
|
|
21
|
+
|
|
22
|
+
## 2. Version a model
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# app/models/article.rb
|
|
26
|
+
class Article < ApplicationRecord
|
|
27
|
+
has_paper_trail
|
|
28
|
+
end
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Restart the Rails console after changing the Gemfile or an initializer.
|
|
32
|
+
|
|
33
|
+
## 3. Create a little history
|
|
34
|
+
|
|
35
|
+
Run this in `bin/rails console`:
|
|
36
|
+
|
|
37
|
+
<!-- executable:quickstart-history -->
|
|
38
|
+
```ruby
|
|
39
|
+
article = Article.create!(title: "Draft")
|
|
40
|
+
|
|
41
|
+
article.update!(title: "Published")
|
|
42
|
+
draft_version = article.versions.last
|
|
43
|
+
|
|
44
|
+
article.update!(title: "Final")
|
|
45
|
+
published_version = article.versions.last
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The variable names are intentional. PaperTrail versions contain the state
|
|
49
|
+
*before* their event:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
draft_version.reify.title # => "Draft"
|
|
53
|
+
published_version.reify.title # => "Published"
|
|
54
|
+
article.title # => "Final" (current database state)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 4. Compare two states
|
|
58
|
+
|
|
59
|
+
<!-- executable:quickstart-compare -->
|
|
60
|
+
```ruby
|
|
61
|
+
diff = PaperTrailDiff.compare(draft_version, published_version)
|
|
62
|
+
|
|
63
|
+
diff.empty? # => false
|
|
64
|
+
diff.attributes["title"].from # => "Draft"
|
|
65
|
+
diff.attributes["title"].to # => "Published"
|
|
66
|
+
|
|
67
|
+
diff.to_h
|
|
68
|
+
# => {
|
|
69
|
+
# record_presence_change: nil,
|
|
70
|
+
# attributes: {
|
|
71
|
+
# "title" => { from: "Draft", to: "Published" }
|
|
72
|
+
# },
|
|
73
|
+
# associations: {}
|
|
74
|
+
# }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`compare` reports only the net difference between its endpoints. It does not
|
|
78
|
+
report intermediate changes.
|
|
79
|
+
|
|
80
|
+
### Compare a version with current state
|
|
81
|
+
|
|
82
|
+
Pass the persisted record explicitly when the second endpoint should be the
|
|
83
|
+
current database state:
|
|
84
|
+
|
|
85
|
+
<!-- executable:quickstart-current -->
|
|
86
|
+
```ruby
|
|
87
|
+
diff = PaperTrailDiff.compare(published_version, article)
|
|
88
|
+
|
|
89
|
+
diff.attributes["title"].to # => "Final"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The record must be persisted, not destroyed, and have no unsaved changes.
|
|
93
|
+
Current state is never inferred automatically.
|
|
94
|
+
|
|
95
|
+
## 5. Build a checkpoint timeline
|
|
96
|
+
|
|
97
|
+
Create one more historical endpoint:
|
|
98
|
+
|
|
99
|
+
<!-- executable:quickstart-timeline-state -->
|
|
100
|
+
```ruby
|
|
101
|
+
article.update!(title: "Archived")
|
|
102
|
+
final_version = article.versions.last # reifies to "Final"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Then compare every adjacent root version:
|
|
106
|
+
|
|
107
|
+
<!-- executable:quickstart-timeline -->
|
|
108
|
+
```ruby
|
|
109
|
+
steps = PaperTrailDiff.timeline(
|
|
110
|
+
article,
|
|
111
|
+
from: draft_version,
|
|
112
|
+
to: final_version
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
steps.map do |step|
|
|
116
|
+
change = step.diff.attributes["title"]
|
|
117
|
+
[change.from, change.to]
|
|
118
|
+
end
|
|
119
|
+
# => [["Draft", "Published"], ["Published", "Final"]]
|
|
120
|
+
|
|
121
|
+
visible_steps = steps.reject(&:empty?)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Empty steps remain in the timeline. Filter them only when the application's
|
|
125
|
+
display does not need every version boundary.
|
|
126
|
+
|
|
127
|
+
### Select mutations by time
|
|
128
|
+
|
|
129
|
+
Use `within:` when the application has a reporting window rather than saved
|
|
130
|
+
endpoint versions:
|
|
131
|
+
|
|
132
|
+
<!-- executable:quickstart-time-range -->
|
|
133
|
+
```ruby
|
|
134
|
+
window = draft_version.created_at...final_version.created_at
|
|
135
|
+
ranged_steps = PaperTrailDiff.timeline(article, within: window)
|
|
136
|
+
|
|
137
|
+
ranged_steps.map do |step|
|
|
138
|
+
change = step.diff.attributes["title"]
|
|
139
|
+
[change.from, change.to]
|
|
140
|
+
end
|
|
141
|
+
# => [["Draft", "Published"], ["Published", "Final"]]
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The half-open range selects the mutations recorded by `draft_version` and
|
|
145
|
+
`published_version`. The excluded `final_version` is still used as the trailing
|
|
146
|
+
boundary needed to reveal the last selected mutation. PaperTrail versions are
|
|
147
|
+
pre-change snapshots, so a range containing mutations must have a later root
|
|
148
|
+
version. The gem raises `PaperTrailDiff::IncompleteTimeRangeError` instead of
|
|
149
|
+
assuming that the current record is the missing endpoint.
|
|
150
|
+
|
|
151
|
+
## 6. Choose the right API
|
|
152
|
+
|
|
153
|
+
| Need | Call |
|
|
154
|
+
| --- | --- |
|
|
155
|
+
| Net difference between two endpoints | `compare` |
|
|
156
|
+
| Net differences for many current records | `compare_many` |
|
|
157
|
+
| One step per root-record version | `timeline` |
|
|
158
|
+
| Steps for root and selected child versions | `activity_timeline` |
|
|
159
|
+
| Net diff and timelines from one history pass | `analyze` |
|
|
160
|
+
|
|
161
|
+
`timeline` is a root-checkpoint timeline. A child change becomes visible at the
|
|
162
|
+
next root boundary. `activity_timeline` can make a versioned child change its
|
|
163
|
+
own boundary and can end at an explicitly supplied current record.
|
|
164
|
+
|
|
165
|
+
## 7. Ignore noise fields
|
|
166
|
+
|
|
167
|
+
`updated_at` is ignored by default. Passing `ignore:` replaces that default:
|
|
168
|
+
|
|
169
|
+
<!-- executable:quickstart-ignore -->
|
|
170
|
+
```ruby
|
|
171
|
+
PaperTrailDiff.compare(
|
|
172
|
+
draft_version,
|
|
173
|
+
published_version,
|
|
174
|
+
ignore: %i[updated_at lock_version]
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
PaperTrailDiff.compare(
|
|
178
|
+
draft_version,
|
|
179
|
+
published_version,
|
|
180
|
+
ignore: [] # compare every available scalar attribute
|
|
181
|
+
)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
See the main README for exact path-specific ignore rules.
|
|
185
|
+
|
|
186
|
+
## 8. Add association history when needed
|
|
187
|
+
|
|
188
|
+
Associations are optional. Add PT-AT only when the application needs historical
|
|
189
|
+
association reconstruction:
|
|
190
|
+
|
|
191
|
+
```console
|
|
192
|
+
bundle add paper_trail-association_tracking
|
|
193
|
+
bin/rails generate paper_trail_association_tracking:install
|
|
194
|
+
bin/rails db:migrate
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The generator creates `version_associations` and enables
|
|
198
|
+
`PaperTrail.config.track_associations`. Every model whose historical state is
|
|
199
|
+
needed must be versioned:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
class Article < ApplicationRecord
|
|
203
|
+
has_many :comments, dependent: :destroy
|
|
204
|
+
has_paper_trail synchronize_version_creation_timestamp: false
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
class Comment < ApplicationRecord
|
|
208
|
+
belongs_to :article
|
|
209
|
+
has_paper_trail
|
|
210
|
+
end
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Take an explicit root checkpoint before the example change:
|
|
214
|
+
|
|
215
|
+
<!-- executable:quickstart-association -->
|
|
216
|
+
```ruby
|
|
217
|
+
Article.transaction do
|
|
218
|
+
Article.find(article.id).paper_trail.save_with_version
|
|
219
|
+
end
|
|
220
|
+
before = article.versions.reload.last
|
|
221
|
+
|
|
222
|
+
article.comments.create!(body: "First comment")
|
|
223
|
+
current = Article.find(article.id)
|
|
224
|
+
|
|
225
|
+
diff = PaperTrailDiff.compare(
|
|
226
|
+
before,
|
|
227
|
+
current,
|
|
228
|
+
associations: [:comments]
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
diff.associations["comments"].added.first.attributes["body"]
|
|
232
|
+
# => "First comment"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Nested paths are explicit and bounded:
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
diff = PaperTrailDiff.compare(
|
|
239
|
+
before,
|
|
240
|
+
current,
|
|
241
|
+
associations: ["comments.replies.author"]
|
|
242
|
+
)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
To see versioned child events without touching the article after each change:
|
|
246
|
+
|
|
247
|
+
<!-- executable:quickstart-activity -->
|
|
248
|
+
```ruby
|
|
249
|
+
steps = PaperTrailDiff.activity_timeline(
|
|
250
|
+
article,
|
|
251
|
+
from: before,
|
|
252
|
+
to: current,
|
|
253
|
+
associations: ["comments"]
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
steps.reject(&:empty?).each do |step|
|
|
257
|
+
boundary = step.to_boundary
|
|
258
|
+
puts "#{boundary.item_type} ##{boundary.item_id}"
|
|
259
|
+
end
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Use a later transaction-backed root checkpoint instead of `to: current` when
|
|
263
|
+
the result must remain reproducible after the database changes again.
|
|
264
|
+
|
|
265
|
+
For a historical reporting window, replace `from:` and `to:` with `within:`:
|
|
266
|
+
|
|
267
|
+
```ruby
|
|
268
|
+
steps = PaperTrailDiff.activity_timeline(
|
|
269
|
+
article,
|
|
270
|
+
within: report_start...report_end,
|
|
271
|
+
associations: ["comments"]
|
|
272
|
+
)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
This selects versioned comment activity directly, even if the article had no
|
|
276
|
+
mutation during the window. Take a root checkpoint after `report_end`; it gives
|
|
277
|
+
the gem the historical context needed to expose the final child mutation.
|
|
278
|
+
|
|
279
|
+
## 9. Common surprises
|
|
280
|
+
|
|
281
|
+
- PaperTrail stores pre-change snapshots. The current record is not represented
|
|
282
|
+
by `versions.last`; pass the record explicitly when current state is wanted.
|
|
283
|
+
- A `within:` window containing mutations needs a later root checkpoint. The
|
|
284
|
+
trailing version is reconstruction context and may appear as a step's ending
|
|
285
|
+
boundary even though its own mutation is outside the window.
|
|
286
|
+
- `ignore:` replaces the default list. Include `updated_at` yourself when using
|
|
287
|
+
a custom list and you still want it ignored.
|
|
288
|
+
- Historical associations require PT-AT to be installed, loaded, migrated, and
|
|
289
|
+
enabled. Restart the console after setup.
|
|
290
|
+
- Only requested association paths are traversed. The gem never recursively
|
|
291
|
+
discovers the whole model graph.
|
|
292
|
+
- Historical output cannot contain data that PaperTrail or PT-AT did not record
|
|
293
|
+
or can no longer reconstruct.
|
|
294
|
+
|
|
295
|
+
For performance guidance, HABTM limitations, diagnostics, discovery, path-aware
|
|
296
|
+
ignore rules, and complete result shapes, continue with the
|
|
297
|
+
[README](README.md).
|
|
298
|
+
|
|
299
|
+
Upstream setup references:
|
|
300
|
+
[PaperTrail installation](https://github.com/paper-trail-gem/paper_trail#1b-installation)
|
|
301
|
+
and
|
|
302
|
+
[PT-AT installation](https://github.com/westonganger/paper_trail-association_tracking#install).
|