paper_trail-actor 0.4.0 → 0.5.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 +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +138 -38
- data/lib/paper_trail-actor/actor_mixin.rb +16 -0
- data/lib/paper_trail-actor/rails/controller.rb +9 -0
- data/lib/paper_trail-actor/request.rb +5 -9
- data/lib/paper_trail-actor/version.rb +1 -1
- data/lib/paper_trail-actor/version_concern.rb +5 -13
- data/lib/paper_trail-actor.rb +7 -2
- metadata +11 -37
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8d8d84d52637e0114f5ab6c64b3caf0d10b2a94d6049d1e4c1ec0826d4dd94f4
|
|
4
|
+
data.tar.gz: fdabf9efe06ee201f706b17fc47fc75ffe590c57657c479b6b4bb1a26428eb94
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 50b13f56e781102f2c2da25054ea6530feff8d544a8320bc912157f4cb639f9a559ee6edc2166f58295aac8e7f7c716f06ea8f1abddc1fcf873a92f1e17ab3fd
|
|
7
|
+
data.tar.gz: ceec6f0626b24475afb00c413607491cb9456bab684a4ad835dc77798fbb3cf910a007c97e51ce5eb884dc176a2d03eb8828849704e5f5b3e342d5a6c054b8c2
|
data/CHANGELOG.md
CHANGED
|
@@ -52,3 +52,35 @@
|
|
|
52
52
|
### Fixed
|
|
53
53
|
|
|
54
54
|
- None
|
|
55
|
+
|
|
56
|
+
## [0.5.0] - 2025-04-29
|
|
57
|
+
|
|
58
|
+
- Small improvements.
|
|
59
|
+
|
|
60
|
+
### Breaking Changes
|
|
61
|
+
|
|
62
|
+
- None
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- [#12](https://github.com/tttffff/paper_trail-actor/pull/12) Default user for paper trail.
|
|
67
|
+
When using `#set_paper_trail_whodunnit`, the default `#user_for_paper_trail` is now the `#current_user` object, not it's `id`.
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
|
|
71
|
+
- [#10](https://github.com/tttffff/paper_trail-actor/pull/10) Allow other object types.
|
|
72
|
+
Can now use any object that implements globalid, not just ActiveRecord objects.
|
|
73
|
+
|
|
74
|
+
## [0.5.1] - 2025-11-15
|
|
75
|
+
|
|
76
|
+
### Breaking Changes
|
|
77
|
+
|
|
78
|
+
- None
|
|
79
|
+
|
|
80
|
+
### Added
|
|
81
|
+
|
|
82
|
+
- None
|
|
83
|
+
|
|
84
|
+
### Fixed
|
|
85
|
+
|
|
86
|
+
- Explicitly set required ruby version.
|
data/README.md
CHANGED
|
@@ -1,82 +1,182 @@
|
|
|
1
1
|
# PaperTrail-Actor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Track changes in your Rails application with precision. PaperTrail-Actor extends [PaperTrail](https://github.com/paper-trail-gem/paper_trail) to store full ActiveRecord objects (not just IDs) as the actor responsible for changes.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Problem it solves:** When both `Admin` and `User` models can modify records, storing only an ID makes it impossible to know which model type made the change. PaperTrail-Actor stores GlobalIDs to uniquely identify actors across different model types.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Features](#features)
|
|
10
|
+
- [Requirements](#requirements)
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Usage](#usage)
|
|
13
|
+
- [Basic Setup](#basic-setup)
|
|
14
|
+
- [Supported Object Types](#supported-object-types)
|
|
15
|
+
- [Controller Integration](#controller-integration)
|
|
16
|
+
- [Migrating Existing Data](#migrating-existing-data)
|
|
17
|
+
- [How It Works](#how-it-works)
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- **Store any ActiveRecord object** as the actor who made changes
|
|
22
|
+
- **Retrieve the full object** with a simple `#actor` method
|
|
23
|
+
- **Backward compatible** with existing PaperTrail versions
|
|
24
|
+
- **Flexible** - works with strings, unpersisted records, and non-ActiveRecord objects
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
- Ruby 2.7+
|
|
29
|
+
- Rails 6.0+
|
|
30
|
+
- PaperTrail 11.0+
|
|
10
31
|
|
|
11
32
|
## Installation
|
|
12
33
|
|
|
13
|
-
1. Add
|
|
34
|
+
1. Add to your Gemfile:
|
|
14
35
|
```ruby
|
|
15
36
|
gem "paper_trail-actor"
|
|
16
37
|
```
|
|
17
|
-
2.
|
|
38
|
+
2. Install the gem:
|
|
18
39
|
```sh
|
|
19
40
|
bundle install
|
|
20
41
|
```
|
|
21
42
|
|
|
22
|
-
##
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### Basic Setup
|
|
23
46
|
|
|
24
|
-
|
|
25
|
-
- It is designed not to hinder or break existing PaperTrail functionalities.
|
|
26
|
-
- PaperTrail versions with and without a globalid can live side by side (e.g. versions created before installing this gem.)
|
|
47
|
+
Set any ActiveRecord object as the actor responsible for changes:
|
|
27
48
|
|
|
28
49
|
```ruby
|
|
50
|
+
admin = Admin.find(1)
|
|
51
|
+
PaperTrail.request.whodunnit = admin
|
|
52
|
+
|
|
53
|
+
# Now all changes will be attributed to this admin
|
|
54
|
+
# See Supported Object Types section below for detailed examples
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Supported Object Types
|
|
58
|
+
|
|
59
|
+
The gem handles various object types gracefully:
|
|
60
|
+
|
|
61
|
+
**Persisted ActiveRecord objects:**
|
|
62
|
+
```ruby
|
|
63
|
+
admin = Admin.find(1)
|
|
29
64
|
product = Product.find(42)
|
|
30
|
-
admin = Admin.find(1) # <Admin:0x007fa2df9a5590>
|
|
31
65
|
|
|
32
66
|
PaperTrail.request.whodunnit = admin
|
|
33
|
-
PaperTrail.request.whodunnit
|
|
34
|
-
PaperTrail.request.actor
|
|
67
|
+
PaperTrail.request.whodunnit # => "gid://app/Admin/1"
|
|
68
|
+
PaperTrail.request.actor # => #<Admin id: 1>
|
|
35
69
|
|
|
70
|
+
# Make changes - PaperTrail will track the actor
|
|
36
71
|
product.update(name: "Ice cream")
|
|
37
|
-
product.versions.last.whodunnit
|
|
38
|
-
product.versions.last.actor
|
|
72
|
+
product.versions.last.whodunnit # => "gid://app/Admin/1"
|
|
73
|
+
product.versions.last.actor # => #<Admin id: 1>
|
|
39
74
|
```
|
|
40
75
|
|
|
41
|
-
|
|
76
|
+
**Strings:**
|
|
77
|
+
```ruby
|
|
78
|
+
PaperTrail.request.whodunnit = "Alex the admin"
|
|
79
|
+
PaperTrail.request.whodunnit # => "Alex the admin"
|
|
80
|
+
PaperTrail.request.actor # => "Alex the admin"
|
|
81
|
+
```
|
|
42
82
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
83
|
+
**New/unpersisted ActiveRecord objects:**
|
|
84
|
+
```ruby
|
|
85
|
+
PaperTrail.request.whodunnit = Admin.new(name: "New Admin")
|
|
86
|
+
# Cannot get a globalid for an unpersisted object. So we use the `#to_s` for the object.
|
|
87
|
+
PaperTrail.request.whodunnit # => "#<Admin:0x00007f8e8c0a0b80>"
|
|
88
|
+
PaperTrail.request.actor # => "#<Admin:0x00007f8e8c0a0b80>"
|
|
89
|
+
```
|
|
46
90
|
|
|
91
|
+
**Non-ActiveRecord objects:**
|
|
47
92
|
```ruby
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
93
|
+
class BackgroundJob
|
|
94
|
+
def to_s
|
|
95
|
+
"BackgroundJob-#{object_id}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
51
98
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
99
|
+
PaperTrail.request.whodunnit = BackgroundJob.new
|
|
100
|
+
PaperTrail.request.whodunnit # => "BackgroundJob-12345"
|
|
101
|
+
PaperTrail.request.actor # => "BackgroundJob-12345"
|
|
55
102
|
```
|
|
56
103
|
|
|
57
|
-
###
|
|
104
|
+
### Controller Integration
|
|
58
105
|
|
|
59
|
-
|
|
106
|
+
Set the actor automatically in your controllers using PaperTrail's built-in callback:
|
|
60
107
|
|
|
61
108
|
```ruby
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
most_recent_product_version.whodunnit = alex_the_admin
|
|
66
|
-
most_recent_product_version.save
|
|
67
|
-
most_recent_product_version.actor # <Admin:0x00000120fbb7d0>
|
|
109
|
+
class ApplicationController < ActionController::Base
|
|
110
|
+
before_action :set_paper_trail_whodunnit
|
|
111
|
+
end
|
|
68
112
|
```
|
|
69
113
|
|
|
70
|
-
|
|
114
|
+
If your controller has a `#current_user` method, PaperTrail-Actor will automatically use it and attribute all changes within the request cycle to that user.
|
|
71
115
|
|
|
72
|
-
|
|
116
|
+
```ruby
|
|
117
|
+
# In your CRUD actions
|
|
118
|
+
def update
|
|
119
|
+
# Nothing else required here to attribute the user as the actor.
|
|
120
|
+
@product.update(product_params)
|
|
121
|
+
@product.versions.last.actor # => #<User id: 42> (full User object)
|
|
122
|
+
end
|
|
123
|
+
```
|
|
73
124
|
|
|
74
|
-
|
|
125
|
+
**Before PaperTrail-Actor:**
|
|
126
|
+
```ruby
|
|
127
|
+
# whodunnit was just a string like "42"
|
|
128
|
+
version.whodunnit # => "42"
|
|
129
|
+
# You couldn't tell if this was User 42 or Admin 42
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**After PaperTrail-Actor:**
|
|
133
|
+
```ruby
|
|
134
|
+
# whodunnit stores the full GlobalID
|
|
135
|
+
version.whodunnit # => "gid://app/User/42"
|
|
136
|
+
version.actor # => #<User id: 42>
|
|
137
|
+
# Now you know exactly who to attribute the change to
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Override `#user_for_paper_trail` to customize the actor selection logic:
|
|
75
141
|
|
|
76
142
|
```ruby
|
|
77
143
|
class ApplicationController < ActionController::Base
|
|
144
|
+
before_action :set_paper_trail_whodunnit
|
|
145
|
+
|
|
146
|
+
private
|
|
147
|
+
|
|
78
148
|
def user_for_paper_trail
|
|
79
|
-
logged_in? ?
|
|
149
|
+
logged_in? ? current_admin : "Public user"
|
|
80
150
|
end
|
|
81
151
|
end
|
|
82
152
|
```
|
|
153
|
+
|
|
154
|
+
### Migrating Existing Data
|
|
155
|
+
|
|
156
|
+
**When to migrate:** Install PaperTrail-Actor on an existing app with PaperTrail data.
|
|
157
|
+
|
|
158
|
+
**How to migrate:** Convert existing string IDs to GlobalIDs:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
# Find a version with a plain ID
|
|
162
|
+
version = product.versions.first
|
|
163
|
+
# => #<PaperTrail::Version id: 1, whodunnit: "123">
|
|
164
|
+
|
|
165
|
+
# Convert to GlobalID
|
|
166
|
+
admin = Admin.find_by(id: version.whodunnit)
|
|
167
|
+
if admin.present?
|
|
168
|
+
version.update!(whodunnit: admin)
|
|
169
|
+
version.whodunnit # => "gid://app/Admin/123"
|
|
170
|
+
version.actor # => #<Admin id: 123>
|
|
171
|
+
end
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## How It Works
|
|
175
|
+
|
|
176
|
+
PaperTrail-Actor serializes ActiveRecord objects using [GlobalID](https://github.com/rails/globalid) and stores them in PaperTrail's `whodunnit` column. This enables:
|
|
177
|
+
|
|
178
|
+
- **Type-safe actor identification**: `gid://app/Admin/1` vs `gid://app/User/1`
|
|
179
|
+
- **Automatic deserialization**: Call `#actor` to get back the original ActiveRecord object
|
|
180
|
+
- **Graceful fallback**: Non-ActiveRecord objects are stored as strings
|
|
181
|
+
|
|
182
|
+
For example, if you have both `Admin` and `User` models that can make changes, this gem ensures you can clearly identify who made the change instead of using only an ID without knowing the model type.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module PaperTrailActor
|
|
2
|
+
module ActorMixin
|
|
3
|
+
# Used for #whodunnit= methods
|
|
4
|
+
def global_id_string_or_fallback(input_value)
|
|
5
|
+
if input_value.respond_to?(:to_global_id) && input_value.id
|
|
6
|
+
input_value.to_global_id.to_s
|
|
7
|
+
else
|
|
8
|
+
input_value.to_s
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def actor
|
|
13
|
+
::GlobalID::Locator.locate(whodunnit) || whodunnit
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -1,15 +1,11 @@
|
|
|
1
|
+
require_relative "actor_mixin"
|
|
2
|
+
|
|
1
3
|
module PaperTrailActor
|
|
2
4
|
module Request
|
|
3
|
-
|
|
4
|
-
if value.is_a? ActiveRecord::Base
|
|
5
|
-
super(value.to_gid)
|
|
6
|
-
else
|
|
7
|
-
super
|
|
8
|
-
end
|
|
9
|
-
end
|
|
5
|
+
include ActorMixin
|
|
10
6
|
|
|
11
|
-
def
|
|
12
|
-
|
|
7
|
+
def whodunnit=(input_value)
|
|
8
|
+
super(global_id_string_or_fallback(input_value))
|
|
13
9
|
end
|
|
14
10
|
end
|
|
15
11
|
end
|
|
@@ -1,20 +1,12 @@
|
|
|
1
|
+
require_relative "actor_mixin"
|
|
2
|
+
|
|
1
3
|
module PaperTrailActor
|
|
2
4
|
module VersionConcern
|
|
5
|
+
include ActorMixin
|
|
6
|
+
|
|
3
7
|
def whodunnit=(input_value)
|
|
4
|
-
whodunnit_value =
|
|
5
|
-
input_value.to_gid
|
|
6
|
-
else
|
|
7
|
-
input_value
|
|
8
|
-
end
|
|
8
|
+
whodunnit_value = global_id_string_or_fallback(input_value)
|
|
9
9
|
_write_attribute("whodunnit", whodunnit_value)
|
|
10
10
|
end
|
|
11
|
-
|
|
12
|
-
# Returns an object which was responsible for a change
|
|
13
|
-
# you need to store global_id to whodunnit field to make this method return the object(who was responsible)
|
|
14
|
-
# for example, whodunnit => "gid://app/Order/1" then
|
|
15
|
-
# whodunnit_user will return Order.find_by(id: 1) in application scope.
|
|
16
|
-
def actor
|
|
17
|
-
::GlobalID::Locator.locate(whodunnit) || whodunnit
|
|
18
|
-
end
|
|
19
11
|
end
|
|
20
12
|
end
|
data/lib/paper_trail-actor.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "paper_trail-actor/version"
|
|
2
2
|
require "paper_trail-actor/request"
|
|
3
3
|
require "paper_trail-actor/version_concern"
|
|
4
|
+
require "paper_trail-actor/rails/controller"
|
|
4
5
|
|
|
5
6
|
module ::PaperTrail
|
|
6
7
|
module Request
|
|
@@ -8,10 +9,14 @@ module ::PaperTrail
|
|
|
8
9
|
prepend ::PaperTrailActor::Request
|
|
9
10
|
end
|
|
10
11
|
end
|
|
11
|
-
end
|
|
12
12
|
|
|
13
|
-
module ::PaperTrail
|
|
14
13
|
module VersionConcern
|
|
15
14
|
include ::PaperTrailActor::VersionConcern
|
|
16
15
|
end
|
|
16
|
+
|
|
17
|
+
module Rails
|
|
18
|
+
module Controller
|
|
19
|
+
prepend ::PaperTrailActor::Rails::Controller
|
|
20
|
+
end
|
|
21
|
+
end
|
|
17
22
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: paper_trail-actor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- tttffff
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-11-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: paper_trail
|
|
@@ -53,35 +53,7 @@ dependencies:
|
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: rspec
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - ">="
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - ">="
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: activerecord
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - ">="
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: '0'
|
|
76
|
-
type: :development
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - ">="
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: '0'
|
|
83
|
-
- !ruby/object:Gem::Dependency
|
|
84
|
-
name: activesupport
|
|
56
|
+
name: rspec-rails
|
|
85
57
|
requirement: !ruby/object:Gem::Requirement
|
|
86
58
|
requirements:
|
|
87
59
|
- - ">="
|
|
@@ -136,8 +108,9 @@ dependencies:
|
|
|
136
108
|
- - ">="
|
|
137
109
|
- !ruby/object:Gem::Version
|
|
138
110
|
version: '0'
|
|
139
|
-
description: An extension to PaperTrail
|
|
140
|
-
object that was responsible for
|
|
111
|
+
description: An extension to PaperTrail that allows you to retrieve the ActiveRecord
|
|
112
|
+
object (actor) that was responsible for creating or modifying records, enabling
|
|
113
|
+
better audit trails and change attribution.
|
|
141
114
|
email:
|
|
142
115
|
- tristanfellows@icloud.com
|
|
143
116
|
executables: []
|
|
@@ -149,6 +122,8 @@ files:
|
|
|
149
122
|
- README.md
|
|
150
123
|
- Rakefile
|
|
151
124
|
- lib/paper_trail-actor.rb
|
|
125
|
+
- lib/paper_trail-actor/actor_mixin.rb
|
|
126
|
+
- lib/paper_trail-actor/rails/controller.rb
|
|
152
127
|
- lib/paper_trail-actor/request.rb
|
|
153
128
|
- lib/paper_trail-actor/version.rb
|
|
154
129
|
- lib/paper_trail-actor/version_concern.rb
|
|
@@ -158,7 +133,7 @@ licenses:
|
|
|
158
133
|
metadata:
|
|
159
134
|
homepage_uri: https://github.com/tttffff/paper_trail-actor
|
|
160
135
|
source_code_uri: https://github.com/tttffff/paper_trail-actor
|
|
161
|
-
changelog_uri: https://github.com/tttffff/paper_trail-actor/blob/
|
|
136
|
+
changelog_uri: https://github.com/tttffff/paper_trail-actor/blob/main/CHANGELOG.md
|
|
162
137
|
post_install_message:
|
|
163
138
|
rdoc_options: []
|
|
164
139
|
require_paths:
|
|
@@ -167,7 +142,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
167
142
|
requirements:
|
|
168
143
|
- - ">="
|
|
169
144
|
- !ruby/object:Gem::Version
|
|
170
|
-
version: '
|
|
145
|
+
version: '2.7'
|
|
171
146
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
172
147
|
requirements:
|
|
173
148
|
- - ">="
|
|
@@ -177,6 +152,5 @@ requirements: []
|
|
|
177
152
|
rubygems_version: 3.5.22
|
|
178
153
|
signing_key:
|
|
179
154
|
specification_version: 4
|
|
180
|
-
summary:
|
|
181
|
-
object that was responsible for changes
|
|
155
|
+
summary: Fetch the ActiveRecord object responsible for PaperTrail changes
|
|
182
156
|
test_files: []
|