paper_trail-human 0.5.0 → 0.6.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: dfb8c783e819dfade94289a84366e1d04f13f711339c6c08ca471d4a2daac52c
4
- data.tar.gz: 6ff6a8161a65ee10cc25a84c77f5986a33c9a13b285854efc7e8fce30597f198
3
+ metadata.gz: 3b4f76ffb9c3e73defa2048c879473a97d861f58831bd5277dcf85ad0d8656a3
4
+ data.tar.gz: e1bddce4e11cddb78b79fded41c9d874fc5b5b680f6d90c859a3f455d10f356e
5
5
  SHA512:
6
- metadata.gz: dcc23c680e095752b9413ece751ebb82c5d4b3b8bf060811d01acfb775f4b803f2a65a318f175d40e288716b2bf6a13e35ca56a586391187900d60a1f2b0ec5c
7
- data.tar.gz: '08542e032fd13d2e8ad3a9ae4949145ca5bf82d0e5290cbc2387393df0cbf1de8ebb4724fb16ea756b96394a5a43e4add686f4f9b14612f9cd7c85b78e0fe22c'
6
+ metadata.gz: d78f8fa3b717ba035f6aa9cfc2e52ae6f5d72caae5bd581cd9a79490fc6b25ddf91d165d07a358b9ee6a3e168c0647ee37d2f6c6168e23df5124f8f5c98e12fe
7
+ data.tar.gz: 4cbfc5c62c108a1a36377e3dd75ff789b86b1ceb10374b93c846961fefe8dfb33df92168d1ee726af33ac7982d1d998bf2aa576fddb5f3be2adabc5530bea225
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.0] - 2026-06-02
4
+
5
+ ### Added
6
+ - `:relation` resolver now supports arrays of IDs, resolving each to its attribute value
7
+
3
8
  ## [0.5.0] - 2026-06-01
4
9
 
5
10
  ### Added
data/README.md CHANGED
@@ -18,6 +18,7 @@ Resolves foreign keys to names, translates enums and constants, formats dates an
18
18
  - [2b. Per-Model Fields](#2b-per-model-fields)
19
19
  - [2c. Item Name](#2c-item-name)
20
20
  - [2d. After Format Hook](#2d-after-format-hook)
21
+ - [2e. Event Callbacks](#2e-event-callbacks)
21
22
  - [3. Resolvers](#3-resolvers)
22
23
  - [3a. Relation](#3a-relation)
23
24
  - [3b. Enum](#3b-enum)
@@ -32,13 +33,16 @@ Resolves foreign keys to names, translates enums and constants, formats dates an
32
33
  - [4c. Filtering Fields](#4c-filtering-fields)
33
34
  - [4d. Output Formats](#4d-output-formats)
34
35
  - [5. Timeline](#5-timeline)
35
- - [6. I18n](#6-i18n)
36
- - [6a. Field Names](#6a-field-names)
37
- - [6b. Event Labels](#6b-event-labels)
38
- - [7. Architecture](#7-architecture)
39
- - [8. Requirements](#8-requirements)
40
- - [9. Contributing](#9-contributing)
41
- - [10. License](#10-license)
36
+ - [6. Extensibility](#6-extensibility)
37
+ - [6a. Custom Resolvers](#6a-custom-resolvers)
38
+ - [6b. Custom Formatters](#6b-custom-formatters)
39
+ - [7. I18n](#7-i18n)
40
+ - [7a. Field Names](#7a-field-names)
41
+ - [7b. Event Labels](#7b-event-labels)
42
+ - [8. Architecture](#8-architecture)
43
+ - [9. Requirements](#9-requirements)
44
+ - [10. Contributing](#10-contributing)
45
+ - [11. License](#11-license)
42
46
 
43
47
  ## 1. Introduction
44
48
 
@@ -46,11 +50,13 @@ Resolves foreign keys to names, translates enums and constants, formats dates an
46
50
 
47
51
  | paper_trail-human | ruby | activerecord | paper_trail |
48
52
  |-------------------|---------|--------------|-------------|
53
+ | 0.5.x | >= 3.1 | >= 6.1 | >= 12.0 |
54
+ | 0.4.x | >= 3.1 | >= 6.1 | >= 12.0 |
49
55
  | 0.3.x | >= 3.1 | >= 6.1 | >= 12.0 |
50
56
  | 0.2.x | >= 3.0 | >= 6.1 | >= 12.0 |
51
57
  | 0.1.x | >= 2.7 | >= 5.2 | >= 9.0 |
52
58
 
53
- **CI matrix (0.3.x):**
59
+ **CI matrix (0.5.x):**
54
60
 
55
61
  | Rails | PaperTrail | Ruby |
56
62
  |-------|-----------|-------------------|
@@ -138,7 +144,7 @@ PaperTrail::Human.configure do |config|
138
144
  m.field :role, :enum, class_name: "UserRole", method: :label
139
145
  m.field :company_id, :relation, class_name: "Company", attribute: :name
140
146
  m.field :active, :boolean, true_label: "Active", false_label: "Inactive"
141
- m.field :bio, :text, max_length: 100, show_diff_stats: true
147
+ m.field :bio, :text, max_length: 100, diff: true
142
148
  m.field :due_date, :date, format: "%d/%m/%Y"
143
149
  m.field :salary, :number, format: :currency, unit: "R$"
144
150
  m.field :score, :custom, resolve: ->(v) { "#{v} points" }
@@ -163,6 +169,8 @@ PaperTrail::Human.format(version)[:item_name]
163
169
 
164
170
  The `item_name` key is only present when the record exists and the attribute is configured.
165
171
 
172
+ In batch mode (`format_collection`), item names are preloaded to prevent N+1 queries.
173
+
166
174
  ### 2d. After Format Hook
167
175
 
168
176
  Post-process every formatted result:
@@ -176,6 +184,24 @@ config.after_format = ->(result, version) {
176
184
 
177
185
  The lambda receives the formatted hash and the original `PaperTrail::Version`, and must return the hash.
178
186
 
187
+ ### 2e. Event Callbacks
188
+
189
+ Register multiple callbacks that fire after formatting. Useful for side effects like logging, notifications, or external integrations:
190
+
191
+ ```ruby
192
+ config.on_format do |result, version|
193
+ AuditLog.push(result) if result[:event] == "destroy"
194
+ end
195
+
196
+ config.on_format do |result, version|
197
+ SlackNotifier.notify(result)
198
+ end
199
+ ```
200
+
201
+ - Multiple callbacks, executed in order of registration
202
+ - Fault-tolerant: errors in one callback don't block others or the return value
203
+ - Runs after the `after_format` hook
204
+
179
205
  ## 3. Resolvers
180
206
 
181
207
  ### 3a. Relation
@@ -234,17 +260,32 @@ m.field :score, :custom, resolve: ->(value) { "#{value} points" }
234
260
 
235
261
  ### 3e. Text
236
262
 
237
- Truncates long text fields:
263
+ Truncates long text fields with optional diff stats:
238
264
 
239
265
  ```ruby
240
266
  m.field :body, :text, max_length: 100, show_diff_stats: true
241
267
  # => "Lorem ipsum dolor sit amet..." (250 chars)
242
268
  ```
243
269
 
270
+ **Diff mode** — shows line-level additions and deletions:
271
+
272
+ ```ruby
273
+ m.field :body, :text, diff: true
274
+ # => {
275
+ # field: "Body",
276
+ # previous_value: "Old text...",
277
+ # value: "New text...",
278
+ # additions: 5,
279
+ # deletions: 2,
280
+ # summary: "+5/-2 lines"
281
+ # }
282
+ ```
283
+
244
284
  | Option | Description | Default |
245
285
  |--------|-------------|---------|
246
286
  | `max_length:` | Maximum characters before truncation | `80` |
247
287
  | `show_diff_stats:` | Append total char count | `false` |
288
+ | `diff:` | Enable line-level diff stats | `false` |
248
289
 
249
290
  ### 3f. Date
250
291
 
@@ -302,7 +343,9 @@ Event-specific behavior:
302
343
  PaperTrail::Human.format_collection(user.versions)
303
344
  ```
304
345
 
305
- Same as `format` but for multiple versions. Relations are batch-loaded to prevent N+1 queries.
346
+ Same as `format` but for multiple versions. Relations and item names are batch-loaded to prevent N+1 queries.
347
+
348
+ When using `as:`, returns a single joined string (separated by blank lines for text/markdown, newlines for HTML).
306
349
 
307
350
  ### 4c. Filtering Fields
308
351
 
@@ -326,7 +369,7 @@ PaperTrail::Human.format(version, as: :html)
326
369
  # => HTML div with table (XSS-safe, escapes entities)
327
370
  ```
328
371
 
329
- Available formats: `:text`, `:markdown`, `:html`.
372
+ Built-in formats: `:text`, `:markdown`, `:html`. Custom formats can be registered (see [6b. Custom Formatters](#6b-custom-formatters)).
330
373
 
331
374
  Works with both `format` and `format_collection`.
332
375
 
@@ -351,9 +394,61 @@ PaperTrail::Human.timeline(user.versions, group_by: :day)
351
394
 
352
395
  Supports `only:` and `except:` filters.
353
396
 
354
- ## 6. I18n
397
+ ## 6. Extensibility
398
+
399
+ ### 6a. Custom Resolvers
400
+
401
+ Register your own resolver types:
402
+
403
+ ```ruby
404
+ class MoneyResolver
405
+ include PaperTrail::Human::Ports::Resolver
406
+
407
+ def initialize(currency: "USD", **)
408
+ @currency = currency
409
+ end
410
+
411
+ def resolve(value)
412
+ "#{@currency} #{format('%.2f', value.to_f)}"
413
+ end
414
+ end
415
+
416
+ PaperTrail::Human.configure do |config|
417
+ config.register_resolver :money, MoneyResolver
418
+
419
+ config.register "Order" do |m|
420
+ m.field :total, :money, currency: "R$"
421
+ end
422
+ end
423
+ ```
424
+
425
+ The resolver class must include `PaperTrail::Human::Ports::Resolver` and implement `#resolve(value)`.
426
+
427
+ For resolvers that need both old and new values, implement `#resolve_pair?` returning `true` and `#resolve_change(previous_value, new_value)`.
428
+
429
+ ### 6b. Custom Formatters
430
+
431
+ Register your own output formats:
432
+
433
+ ```ruby
434
+ class JsonFormatter
435
+ def call(result)
436
+ result.to_json
437
+ end
438
+ end
439
+
440
+ PaperTrail::Human.configure do |config|
441
+ config.register_formatter :json, JsonFormatter
442
+ end
443
+
444
+ PaperTrail::Human.format(version, as: :json)
445
+ ```
446
+
447
+ The formatter class must implement `#call(result)` and return a string.
448
+
449
+ ## 7. I18n
355
450
 
356
- ### 6a. Field Names
451
+ ### 7a. Field Names
357
452
 
358
453
  Field names are resolved in this order:
359
454
 
@@ -363,7 +458,7 @@ Field names are resolved in this order:
363
458
 
364
459
  Example: `company_id` → looks up `activerecord.attributes.user.company_id` → falls back to `"Company"`.
365
460
 
366
- ### 6b. Event Labels
461
+ ### 7b. Event Labels
367
462
 
368
463
  Enable translated event labels:
369
464
 
@@ -393,7 +488,7 @@ pt-BR:
393
488
  destroy: "Exclusão"
394
489
  ```
395
490
 
396
- ## 7. Architecture
491
+ ## 8. Architecture
397
492
 
398
493
  Hexagonal (Ports & Adapters):
399
494
 
@@ -401,8 +496,9 @@ Hexagonal (Ports & Adapters):
401
496
  ┌─────────────────────────────────────────────┐
402
497
  │ Core │
403
498
  │ ChangeExtractor · FieldFormatter │
404
- │ EventTranslator · Presenter
405
- │ BatchPresenter · Timeline
499
+ │ EventTranslator · AfterFormat
500
+ Presenter · BatchPresenter · Timeline
501
+ │ ItemNameLoader · RelationLoader │
406
502
  ├─────────────────────────────────────────────┤
407
503
  │ Ports │
408
504
  │ Resolver (interface) │
@@ -411,25 +507,26 @@ Hexagonal (Ports & Adapters):
411
507
  │ Resolvers: Relation, Enum, Boolean, │
412
508
  │ Custom, Text, Date, Number │
413
509
  │ Formatters: Text, Markdown, Html │
510
+ │ (+ user-registered resolvers/formatters) │
414
511
  └─────────────────────────────────────────────┘
415
512
  ```
416
513
 
417
514
  - **Core** — pure formatting logic, no external dependencies
418
515
  - **Ports** — `Resolver` interface that every adapter implements
419
- - **Adapters** — concrete implementations for resolving values and formatting output
516
+ - **Adapters** — concrete implementations (lazy-loaded via `autoload`)
420
517
 
421
- The gem has zero dependencies beyond `activerecord` and `paper_trail`. The Railtie is optional — it works in non-Rails apps (Sinatra, Hanami, etc).
518
+ The gem has zero dependencies beyond `activerecord` and `paper_trail`. Adapters are loaded on demand. The Railtie is optional — it works in non-Rails apps (Sinatra, Hanami, etc).
422
519
 
423
- ## 8. Requirements
520
+ ## 9. Requirements
424
521
 
425
522
  - Ruby >= 3.1
426
523
  - Rails >= 6.1 (or standalone ActiveRecord)
427
524
  - PaperTrail >= 12.0
428
525
 
429
- ## 9. Contributing
526
+ ## 10. Contributing
430
527
 
431
528
  See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on setting up the development environment, running tests, and submitting pull requests.
432
529
 
433
- ## 10. License
530
+ ## 11. License
434
531
 
435
532
  MIT. See [LICENSE.txt](LICENSE.txt).
@@ -14,6 +14,18 @@ module PaperTrail
14
14
  end
15
15
 
16
16
  def resolve(value)
17
+ return resolve_array(value) if value.is_a?(Array)
18
+
19
+ resolve_single(value)
20
+ end
21
+
22
+ private
23
+
24
+ def resolve_array(values)
25
+ values.map { |v| resolve_single(v) }
26
+ end
27
+
28
+ def resolve_single(value)
17
29
  return @cache[value] || @cache[value.to_i] || value if @cache.any?
18
30
 
19
31
  klass = safe_const_get(@class_name)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module PaperTrail
4
4
  module Human
5
- VERSION = '0.5.0'
5
+ VERSION = '0.6.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paper_trail-human
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriel