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 +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +120 -23
- data/lib/paper_trail/human/adapters/resolvers/relation.rb +12 -0
- data/lib/paper_trail/human/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3b4f76ffb9c3e73defa2048c879473a97d861f58831bd5277dcf85ad0d8656a3
|
|
4
|
+
data.tar.gz: e1bddce4e11cddb78b79fded41c9d874fc5b5b680f6d90c859a3f455d10f356e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d78f8fa3b717ba035f6aa9cfc2e52ae6f5d72caae5bd581cd9a79490fc6b25ddf91d165d07a358b9ee6a3e168c0647ee37d2f6c6168e23df5124f8f5c98e12fe
|
|
7
|
+
data.tar.gz: 4cbfc5c62c108a1a36377e3dd75ff789b86b1ceb10374b93c846961fefe8dfb33df92168d1ee726af33ac7982d1d998bf2aa576fddb5f3be2adabc5530bea225
|
data/CHANGELOG.md
CHANGED
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.
|
|
36
|
-
- [6a.
|
|
37
|
-
- [6b.
|
|
38
|
-
- [7.
|
|
39
|
-
- [
|
|
40
|
-
- [
|
|
41
|
-
- [
|
|
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.
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
##
|
|
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 ·
|
|
405
|
-
│ BatchPresenter
|
|
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
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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)
|