paper_trail_diff 0.9.0 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ee80500dbeb48421e7b0e0766a5f5559b73aa71528360a5f1a16866f9e6ccfa
4
- data.tar.gz: 6692d3fd2d725918e16da15b4d39312b5e1a42deba3aa8e50a269abfd9fc6bd9
3
+ metadata.gz: 00ac8585544b9421fab5fa7cf677a5eddaae2867058beb2c7958ff0e8f917da5
4
+ data.tar.gz: 52e079e4f84b77ffd804fd452eaf4acc03618d2e7e685969faddcfe2f8c21eba
5
5
  SHA512:
6
- metadata.gz: f809075c65c923ca0dd828e8337477d8461dfa13b2de537a9f99548f0826f867379a33c8bf4a5061476dabd1685829adc5ab21e07052a4d40ebfff4736a44946
7
- data.tar.gz: 6212fab6601c900b2701a9401e7e9b66c5236f12da0340788f0641efef759340a29ea6f95e7b51d86c2792d847f5553db8f30d7c96e4370e37ceec0b8a227656
6
+ metadata.gz: 400361a0bd6d3d55b3637a5fdcb42febf04ff62a81b75aba3848f70c62d1865f7983016c31bb34347dc50df5b2f66ccb0e44306e2a31990c3db5c04f3806813e
7
+ data.tar.gz: 0b8497f1dcb9ecd6e12b09e28b7a69fe17363d251308abffb824e8d1d0033572c4bee71351785ed425dc5e46c656d092612c7cb4c1ff33e69ad270d8e2e7260b
data/CHANGELOG.md CHANGED
@@ -3,6 +3,28 @@
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.10.0] - 2026-08-15
7
+
8
+ ### Added
9
+
10
+ - Add `rake release:preflight`, which runs the full gate and then checks
11
+ everything that must hold before a tag: a clean working tree, a HEAD that
12
+ matches its upstream, an unused tag, a dated changelog section for the
13
+ version, and that the version is not already on RubyGems. It reports and
14
+ never tags, pushes, or publishes. An unreachable RubyGems is reported as
15
+ unknown rather than treated as "not published".
16
+ - Raise `PaperTrailDiff::UnversionedAssociationError` when a selected
17
+ association's target is not versioned and history is being reconstructed.
18
+ Such a comparison could only ever answer "nothing changed", which is a wrong
19
+ answer rather than an empty one. Live-to-live comparison reads current state
20
+ and is unaffected. `diagnose` now reports the same condition as an error
21
+ rather than a warning, so `ok?` no longer stays true for a comparison that
22
+ will raise.
23
+ - Document ActiveStorage attachments, which this reaches through:
24
+ `has_one_attached` points at models Rails owns and PaperTrail never versions,
25
+ so attachments are audited through a versioned model of your own that mirrors
26
+ the metadata onto ordinary columns.
27
+
6
28
  ## [0.9.0] - 2026-08-14
7
29
 
8
30
  ### Added
data/README.md CHANGED
@@ -152,6 +152,55 @@ that touches a record and its children in quick succession will manage it — so
152
152
  this is worth checking before trusting an association history. Recording
153
153
  versions at sub-second precision separates them.
154
154
 
155
+ ### ActiveStorage attachments
156
+
157
+ `has_one_attached` and `has_many_attached` point at `ActiveStorage::Attachment`
158
+ and `ActiveStorage::Blob`, which Rails owns and PaperTrail does not version.
159
+ There is no history behind them, so a historical comparison over those paths
160
+ raises `PaperTrailDiff::UnversionedAssociationError` rather than reporting that
161
+ nothing changed. Adding `has_paper_trail` to Rails' own models does not rescue
162
+ it either: `has_one_attached` is a scoped `has_one`, and reifying it from
163
+ history is ambiguous.
164
+
165
+ Audit the attachment through a model you own instead. Give it the facts worth
166
+ auditing as ordinary columns, and version it:
167
+
168
+ ```ruby
169
+ class DocumentRevision < ApplicationRecord
170
+ belongs_to :attachable, polymorphic: true
171
+ has_one_attached :file
172
+ has_paper_trail
173
+
174
+ # Attaching a file writes to ActiveStorage's tables, not to this record, so
175
+ # nothing would be versioned without copying the facts across.
176
+ after_save :record_file_metadata, if: -> { file.attached? }
177
+
178
+ def record_file_metadata
179
+ blob = file.blob
180
+ return if filename == blob.filename.to_s && checksum == blob.checksum
181
+
182
+ update_columns(
183
+ filename: blob.filename.to_s, content_type: blob.content_type,
184
+ byte_size: blob.byte_size, checksum: blob.checksum
185
+ )
186
+ end
187
+ end
188
+ ```
189
+
190
+ ```ruby
191
+ class Article < ApplicationRecord
192
+ has_many :document_revisions, as: :attachable
193
+ has_paper_trail
194
+ end
195
+
196
+ PaperTrailDiff.compare(before, after, associations: [:document_revisions])
197
+ ```
198
+
199
+ The bytes stay in ActiveStorage; the *auditable facts* live where PaperTrail
200
+ can see them. Nothing about the column names matters to this gem — it reports
201
+ whichever columns changed. What matters is that replacing a file writes to a
202
+ versioned record, because attaching one on its own does not.
203
+
155
204
  ## Choosing an entry point
156
205
 
157
206
  | You need | Call |
@@ -175,7 +175,9 @@ module PaperTrailDiff
175
175
  def inspect_versioned_model(model_class, path)
176
176
  return if model_versioned?(model_class)
177
177
 
178
- add_warning(
178
+ # An error rather than a warning: `ok?` must not stay true for a
179
+ # comparison that cannot work, and this one raises at runtime.
180
+ add_error(
179
181
  :unversioned_association_target,
180
182
  "#{model_class.name} does not appear to have PaperTrail enabled",
181
183
  path
@@ -35,6 +35,10 @@ module PaperTrailDiff
35
35
  # Raised when a requested ActiveRecord association does not exist.
36
36
  class UnknownAssociationError < Error; end
37
37
 
38
+ # Raised when a selected association's target is not versioned, so its history
39
+ # cannot be reconstructed at all.
40
+ class UnversionedAssociationError < Error; end
41
+
38
42
  # Raised when a requested association macro is not supported.
39
43
  class UnsupportedAssociationError < Error; end
40
44
 
@@ -17,6 +17,7 @@ module PaperTrailDiff
17
17
 
18
18
  ensure_association_tracking! if historical
19
19
  @traversal.validate!(model_class)
20
+ ensure_versioned_targets!(model_class) if historical
20
21
  end
21
22
 
22
23
  private
@@ -24,6 +25,30 @@ module PaperTrailDiff
24
25
  # @rbs @tree: AssociationTree
25
26
  # @rbs @traversal: AssociationTraversal
26
27
 
28
+ # A model PaperTrail never versioned has no history to reconstruct, so a
29
+ # comparison over it can only ever answer "nothing changed" -- which is a
30
+ # wrong answer rather than an empty one. Live-to-live comparison reads
31
+ # current state and is unaffected, so this applies to historical work only.
32
+ #: (untyped) -> void
33
+ def ensure_versioned_targets!(model_class)
34
+ @traversal.selected_reflections(model_class).each do |path, reflection|
35
+ # A polymorphic target is not known until a row names it; `diagnose`
36
+ # reports that separately rather than guessing here.
37
+ next if reflection.polymorphic?
38
+ next if versioned?(reflection.klass)
39
+
40
+ raise UnversionedAssociationError,
41
+ "association #{path} cannot be compared historically: " \
42
+ "#{reflection.klass.name} is not versioned. Add `has_paper_trail` to it, " \
43
+ 'or mirror the fields you need onto a model that has it.'
44
+ end
45
+ end
46
+
47
+ #: (untyped) -> bool
48
+ def versioned?(model_class)
49
+ model_class.respond_to?(:paper_trail_options) && !model_class.paper_trail_options.nil?
50
+ end
51
+
27
52
  #: () -> void
28
53
  def ensure_association_tracking!
29
54
  paper_trail = Object.const_get(:PaperTrail) #: untyped
@@ -2,5 +2,5 @@
2
2
  # rbs_inline: enabled
3
3
 
4
4
  module PaperTrailDiff
5
- VERSION = '0.9.0'
5
+ VERSION = '0.10.0'
6
6
  end
@@ -45,6 +45,11 @@ module PaperTrailDiff
45
45
  class UnknownAssociationError < Error
46
46
  end
47
47
 
48
+ # Raised when a selected association's target is not versioned, so its history
49
+ # cannot be reconstructed at all.
50
+ class UnversionedAssociationError < Error
51
+ end
52
+
48
53
  # Raised when a requested association macro is not supported.
49
54
  class UnsupportedAssociationError < Error
50
55
  end
@@ -16,6 +16,16 @@ module PaperTrailDiff
16
16
 
17
17
  @tree: AssociationTree
18
18
 
19
+ # A model PaperTrail never versioned has no history to reconstruct, so a
20
+ # comparison over it can only ever answer "nothing changed" -- which is a
21
+ # wrong answer rather than an empty one. Live-to-live comparison reads
22
+ # current state and is unaffected, so this applies to historical work only.
23
+ # : (untyped) -> void
24
+ def ensure_versioned_targets!: (untyped) -> void
25
+
26
+ # : (untyped) -> bool
27
+ def versioned?: (untyped) -> bool
28
+
19
29
  # : () -> void
20
30
  def ensure_association_tracking!: () -> void
21
31
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paper_trail_diff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Williams
@@ -202,11 +202,11 @@ licenses:
202
202
  metadata:
203
203
  allowed_push_host: https://rubygems.org
204
204
  bug_tracker_uri: https://github.com/aheathwilliams/paper_trail_diff/issues
205
- changelog_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.9.0/CHANGELOG.md
206
- documentation_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.9.0/README.md
205
+ changelog_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.10.0/CHANGELOG.md
206
+ documentation_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.10.0/README.md
207
207
  homepage_uri: https://github.com/aheathwilliams/paper_trail_diff
208
208
  rubygems_mfa_required: 'true'
209
- source_code_uri: https://github.com/aheathwilliams/paper_trail_diff/tree/v0.9.0
209
+ source_code_uri: https://github.com/aheathwilliams/paper_trail_diff/tree/v0.10.0
210
210
  rdoc_options: []
211
211
  require_paths:
212
212
  - lib