recorder 1.2.3 → 1.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 +113 -0
- data/README.md +170 -10
- data/lib/generators/recorder/install_generator.rb +2 -18
- data/lib/generators/recorder/templates/{add_index_by_user_id_to_recorder_revisions.rb → add_index_by_user_id_to_recorder_revisions.rb.tt} +2 -2
- data/lib/generators/recorder/templates/{create_recorder_revisions.rb → create_recorder_revisions.rb.tt} +3 -3
- data/lib/recorder/manager.rb +3 -1
- data/lib/recorder/tape/record.rb +4 -2
- data/lib/recorder/version.rb +1 -1
- data/lib/recorder.rb +1 -1
- metadata +55 -48
- data/.gitignore +0 -16
- data/.rspec +0 -2
- data/.rubocop.yml +0 -27
- data/.travis.yml +0 -4
- data/Gemfile +0 -6
- data/Rakefile +0 -9
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/lib/generators/recorder/templates/add_number_column_to_recorder_revisions.rb +0 -44
- data/recorder.gemspec +0 -37
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d2e570a4fef05c798efd2a10ded9db849f500e41fab1e13c6f9e485ebfdfdee
|
|
4
|
+
data.tar.gz: 3a3e7f23e99544e05e0e0008de975f9bea9ed2089de89d999ac601a80b3cbe11
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4202283d1af6b5da82c360a3537623516794855e56a31973ed43ff0f0bceb04cdfa412d180af0dab9dc30b096a5213966663dd0cb19af1130e76d52360c24570
|
|
7
|
+
data.tar.gz: 4b7451c330c0115755813f0736135b5323b5d1f72b0f0583e21e88dcc725f1adf6fd44c5ebc6fd6060046304c75be96e282ab590a01c0b47d65eb6d4edf8ca80
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [1.3.0] - 2026-09-16
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Support for Rails 7.0, 7.1, 7.2, 8.0, and 8.1. Rails 6.1 remains supported.
|
|
15
|
+
- Support for Ruby 3.3 and 3.4. Ruby 3.0 remains the minimum.
|
|
16
|
+
- Declared and tested the supported Ruby versions (#10).
|
|
17
|
+
- The specs and RuboCop now run in GitHub Actions (#9).
|
|
18
|
+
- Releases are published to RubyGems.org from GitHub Actions when a version tag
|
|
19
|
+
is pushed (#22).
|
|
20
|
+
- Gem metadata: `source_code_uri`, `changelog_uri`, `bug_tracker_uri`, and
|
|
21
|
+
`rubygems_mfa_required`.
|
|
22
|
+
- README coverage of the public API: configuration, `Recorder.meta=`,
|
|
23
|
+
`recorder_disabled!`, and the association tracking options (#20).
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- `activerecord` and `activesupport` requirements widen from `~> 6.1` to
|
|
28
|
+
`>= 6.1, < 9`, and the `< 3.4` Ruby ceiling is removed. Rails 6.1 and 7.0
|
|
29
|
+
cannot run on Ruby 3.4; every other combination is exercised in CI.
|
|
30
|
+
- The released gem now ships only `lib/`, the README, the LICENSE, and this
|
|
31
|
+
changelog. Previous releases also carried repository scaffolding — CI
|
|
32
|
+
configuration, `Rakefile`, `bin/`, and dotfiles — that a host app never loads.
|
|
33
|
+
- `recorder_revisions.item_id` and `recorder_revisions.user_id` are now
|
|
34
|
+
`bigint`. Rails has defaulted primary keys to `bigint` since 5.1, so the
|
|
35
|
+
`integer` columns could not hold a key from any table this gem audits once it
|
|
36
|
+
passed 2,147,483,647. No release since Rails 5.0 shipped a migration that could
|
|
37
|
+
run; installs from the 0.1.x line on Rails 4 have the narrow columns and are
|
|
38
|
+
unaffected, because the gem ships no migration that alters an existing table
|
|
39
|
+
(#18).
|
|
40
|
+
|
|
41
|
+
### Removed
|
|
42
|
+
|
|
43
|
+
- `pg` is no longer a runtime dependency. Revisions still require PostgreSQL
|
|
44
|
+
column types, but the adapter is the host application's to declare, so the
|
|
45
|
+
gem no longer forces `pg` into its bundle.
|
|
46
|
+
- The `--with_partitions` generator option. It dispatched to a template that was
|
|
47
|
+
never added to the gem, so the run wrote the first migration and then aborted,
|
|
48
|
+
leaving a partial install. It never completed once (#18).
|
|
49
|
+
- The `--with_number_column` generator option. The `number` column it added was
|
|
50
|
+
never read by the gem, and the migration it generated could not run on any
|
|
51
|
+
Rails this gem supports, so there is no working installed base. Anyone who has
|
|
52
|
+
the column keeps it — the counter lives in a trigger in their own schema,
|
|
53
|
+
which this does not touch, and the gem never writes the column. Optional
|
|
54
|
+
cleanup, including a working `DROP TRIGGER`, is in #14 — the `down` the gem
|
|
55
|
+
originally shipped is a syntax error and cannot roll it back (#14).
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- Asynchronous recording no longer enqueues job arguments Sidekiq rejects. The
|
|
60
|
+
`meta` hash supplied through `Recorder.meta=` reached the worker with its
|
|
61
|
+
original keys and values, and Sidekiq raises on arguments that are not JSON
|
|
62
|
+
native from 7.0 onwards. Because the raise happened inside an `after_create`,
|
|
63
|
+
it rolled back the host application's own write. The enqueued payload is now
|
|
64
|
+
normalised to JSON natives in full (#19).
|
|
65
|
+
- `rails generate recorder:install` now produces migrations that run. The
|
|
66
|
+
templates subclassed a bare `ActiveRecord::Migration`, which Rails has
|
|
67
|
+
rejected since 5.0, so `rails db:migrate` raised on the only documented way to
|
|
68
|
+
install the gem. The version is templated from the host app's Rails (#18).
|
|
69
|
+
- `Recorder.version` returns the version instead of raising `TypeError`. It
|
|
70
|
+
scoped into `VERSION::STRING`, but `VERSION` is a `String` (#17).
|
|
71
|
+
- `recorder_disabled!` re-enables recording when the block raises. The re-enable
|
|
72
|
+
was skipped on the exception path, so recording stayed off for the rest of the
|
|
73
|
+
request or job and every later save went silently unaudited (#17).
|
|
74
|
+
- Made the spec suite runnable and trustworthy again (#8).
|
|
75
|
+
|
|
76
|
+
## [1.2.3] - 2023-04-25
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
|
|
80
|
+
- A revision is created when an item's associations change.
|
|
81
|
+
- No revision is created for an `:update` event when nothing changed.
|
|
82
|
+
|
|
83
|
+
## [1.2.2] - 2022-11-17
|
|
84
|
+
|
|
85
|
+
### Fixed
|
|
86
|
+
|
|
87
|
+
- Refactoring for full Rails 6 compatibility.
|
|
88
|
+
|
|
89
|
+
## [1.2.1] - 2022-11-16
|
|
90
|
+
|
|
91
|
+
### Fixed
|
|
92
|
+
|
|
93
|
+
- Fixed parsing of empty associations.
|
|
94
|
+
|
|
95
|
+
## [1.2.0] - 2022-11-15
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- Rails 6 support.
|
|
100
|
+
|
|
101
|
+
## [1.1.1] - 2021-09-13
|
|
102
|
+
|
|
103
|
+
Releases at and before 1.1.1 predate this changelog. See the
|
|
104
|
+
[commit history](https://github.com/jetrockets/recorder/commits/master) for
|
|
105
|
+
details.
|
|
106
|
+
|
|
107
|
+
[Unreleased]: https://github.com/jetrockets/recorder/compare/v1.3.0...HEAD
|
|
108
|
+
[1.3.0]: https://github.com/jetrockets/recorder/compare/v1.2.3...v1.3.0
|
|
109
|
+
[1.2.3]: https://github.com/jetrockets/recorder/compare/v1.2.2...v1.2.3
|
|
110
|
+
[1.2.2]: https://github.com/jetrockets/recorder/compare/v1.2.1...v1.2.2
|
|
111
|
+
[1.2.1]: https://github.com/jetrockets/recorder/compare/v1.2.0...v1.2.1
|
|
112
|
+
[1.2.0]: https://github.com/jetrockets/recorder/compare/v1.1.1...v1.2.0
|
|
113
|
+
[1.1.1]: https://github.com/jetrockets/recorder/releases/tag/v1.1.1
|
data/README.md
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
# Recorder
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/jetrockets/recorder/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/recorder)
|
|
5
|
+
|
|
6
|
+
Recorder tracks changes of your Rails models. Each create, update, and destroy on
|
|
7
|
+
an observed model writes a `recorder_revisions` row holding an attribute snapshot,
|
|
8
|
+
the changes, and — when the controller concern is included — the user and IP
|
|
9
|
+
behind the request.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- PostgreSQL — revisions are stored in `jsonb` and `inet` columns
|
|
14
|
+
- Ruby and Rails per the table below
|
|
15
|
+
|
|
16
|
+
| Rails | Supported Ruby |
|
|
17
|
+
|-------|----------------|
|
|
18
|
+
| 6.1 | 3.0 – 3.3 |
|
|
19
|
+
| 7.0 | 3.0 – 3.3 |
|
|
20
|
+
| 7.1 | 3.0 – 3.4 |
|
|
21
|
+
| 7.2 | 3.1 – 3.4 |
|
|
22
|
+
| 8.0 | 3.2 – 3.4 |
|
|
23
|
+
| 8.1 | 3.2 – 3.4 |
|
|
24
|
+
|
|
25
|
+
Rails 6.1 and 7.0 cannot run on Ruby 3.4 — they require `mutex_m`, which left the
|
|
26
|
+
default gems in that release. Every combination in the table is exercised in CI.
|
|
4
27
|
|
|
5
28
|
## Installation
|
|
6
29
|
|
|
@@ -14,15 +37,25 @@ And then execute:
|
|
|
14
37
|
|
|
15
38
|
$ bundle
|
|
16
39
|
|
|
17
|
-
|
|
40
|
+
Generate the migration for the `recorder_revisions` table:
|
|
41
|
+
|
|
42
|
+
$ rails generate recorder:install
|
|
43
|
+
|
|
44
|
+
The generator writes the migration but does not run it. Run it yourself:
|
|
45
|
+
|
|
46
|
+
$ rails db:migrate
|
|
18
47
|
|
|
19
|
-
|
|
48
|
+
The generator accepts one option:
|
|
49
|
+
|
|
50
|
+
- `--with_index_by_user_id` — adds an index on `user_id`.
|
|
20
51
|
|
|
21
52
|
## Usage
|
|
22
53
|
|
|
23
|
-
|
|
54
|
+
### Observing a model
|
|
24
55
|
|
|
25
|
-
|
|
56
|
+
Include `Recorder::Observer` into the model and configure logging options for it:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
26
59
|
class Post < ActiveRecord::Base
|
|
27
60
|
include ::Recorder::Observer
|
|
28
61
|
|
|
@@ -41,7 +74,28 @@ Recorder supports the following options:
|
|
|
41
74
|
* `associations: {hash} (hash)` - allows to set what associations will be logged alongside with the model. For each association you can also set ignore and only options;
|
|
42
75
|
* `async: bool` - a logging strategy (true - asynchronous, false - synchronous).
|
|
43
76
|
|
|
44
|
-
|
|
77
|
+
These per-model options do not reach the recorder yet — see [Known issues](#known-issues).
|
|
78
|
+
Until they do, every observed model records a full attribute snapshot, filtered
|
|
79
|
+
only by the global `Recorder.config.ignore`.
|
|
80
|
+
|
|
81
|
+
### Global configuration
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
Recorder.config do |config|
|
|
85
|
+
config.ignore = %i[created_at updated_at]
|
|
86
|
+
config.async = false
|
|
87
|
+
config.sidekiq_options = {queue: 'recorder', retry: 10, backtrace: true}
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`ignore` defaults to `[]`, `async` to `false`, and `sidekiq_options` to the hash
|
|
92
|
+
shown above.
|
|
93
|
+
|
|
94
|
+
There are two strategies for logging: synchronous and asynchronous. When the synchronous strategy is used, a revision record is saved immediately after a model is saved, and the async strategy moves creating of revision records to [Sidekiq](https://github.com/sidekiq/sidekiq). Under the async
|
|
95
|
+
strategy the revision is enqueued to `Recorder::Sidekiq::RevisionsWorker` two
|
|
96
|
+
seconds out; the worker is loaded by the railtie when `Sidekiq` is defined.
|
|
97
|
+
|
|
98
|
+
### Recording the current user
|
|
45
99
|
|
|
46
100
|
To enable storing of such data as user_id and ip, you need to include `Recorder::Rails::ControllerConcern` to `ApplicationController`. Recorder uses [request_store](https://github.com/steveklabnik/request_store) to safely store these data on a thread level.
|
|
47
101
|
|
|
@@ -52,11 +106,118 @@ To enable storing of such data as user_id and ip, you need to include `Recorder:
|
|
|
52
106
|
end
|
|
53
107
|
```
|
|
54
108
|
|
|
109
|
+
The concern reads `current_user`; override `recorder_user_id` to name a different
|
|
110
|
+
method. Override `recorder_meta` to store a hash alongside every revision. A
|
|
111
|
+
revision without a user is valid — `user_id` stays `nil`.
|
|
112
|
+
|
|
113
|
+
### Turning recording off
|
|
114
|
+
|
|
115
|
+
`Recorder::Manager` suspends recording for the current request or thread:
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
class Importer
|
|
119
|
+
include Recorder::Manager
|
|
120
|
+
|
|
121
|
+
def call
|
|
122
|
+
recorder_disabled! do
|
|
123
|
+
# nothing recorded in here
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The block form re-enables recording on the way out, including when the block
|
|
130
|
+
raises. Called without a block, `recorder_disabled!` stays in effect until
|
|
131
|
+
`recorder_enabled!`.
|
|
132
|
+
|
|
133
|
+
### Reading revisions
|
|
134
|
+
|
|
135
|
+
Observed models get a `revisions` association:
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
revision = post.revisions.ordered_by_created_at.first
|
|
139
|
+
|
|
140
|
+
revision.event # "update"
|
|
141
|
+
revision.data # {"attributes" => {...}, "changes" => {...}, "associations" => {...}}
|
|
142
|
+
revision.user_id
|
|
143
|
+
revision.action_date
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`#item_changeset` wraps `data['changes']` in a `Recorder::Changeset`, which reads
|
|
147
|
+
the values back as the model's own types:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
changeset = revision.item_changeset
|
|
151
|
+
|
|
152
|
+
changeset.keys # attributes that changed
|
|
153
|
+
changeset.previous(:title) # value before the change
|
|
154
|
+
changeset.next(:title) # value after it
|
|
155
|
+
changeset.human_attribute_name(:title)
|
|
156
|
+
changeset.previous_version # a copy of the record with the old values
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Changed associations are reachable the same way:
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
revision.changed_associations # ["author"]
|
|
163
|
+
revision.association_changeset('author')
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
To render a changeset yourself, define `PostChangeset` — the class is looked up
|
|
167
|
+
as `"#{model}Changeset"` — or point at another one with a
|
|
168
|
+
`recorder_changeset_class` class method on the model. Otherwise
|
|
169
|
+
`Recorder::Changeset` is used.
|
|
170
|
+
|
|
171
|
+
## Known issues
|
|
172
|
+
|
|
173
|
+
The gem is under active maintenance and these defects are known as of 1.2.3:
|
|
174
|
+
|
|
175
|
+
- The per-model options above (`ignore:`, `only:`, `associations:`, `async:`)
|
|
176
|
+
are not applied. `Recorder::Tape` asks the record instance for
|
|
177
|
+
`recorder_options`, but `Recorder::Observer` defines that method on the class,
|
|
178
|
+
so the lookup always falls back to `{}` — every observed model records a full
|
|
179
|
+
attribute snapshot, synchronously. Global configuration is unaffected.
|
|
180
|
+
- `Recorder.enabled=` does not switch recording off. It writes to
|
|
181
|
+
`Recorder.config`, which nothing on the recording path reads — the gates are in
|
|
182
|
+
`Recorder.store`, which `Recorder::Manager` drives.
|
|
183
|
+
- Collection associations are never recorded. `associations:` handles only
|
|
184
|
+
singular associations; a `has_many` reflection is skipped without a warning.
|
|
185
|
+
|
|
55
186
|
## Development
|
|
56
187
|
|
|
57
188
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
58
189
|
|
|
59
|
-
|
|
190
|
+
The specs need a PostgreSQL server. Connection settings are read from the
|
|
191
|
+
environment — `RECORDER_DB_HOST`, `RECORDER_DB_PORT`, `RECORDER_DB_USERNAME`,
|
|
192
|
+
`RECORDER_DB_PASSWORD`, `RECORDER_DB_NAME` — and default to `postgres:postgres`
|
|
193
|
+
on `localhost:5432` against a `recorder_test` database, which must already exist.
|
|
194
|
+
|
|
195
|
+
`rake spec` runs against whichever Rails version the root `Gemfile` resolves to.
|
|
196
|
+
To run against a specific one, use the appraisal gemfiles:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
bundle exec appraisal install # once, to generate gemfiles/
|
|
200
|
+
bundle exec appraisal rails-7.2 rake spec
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
CI runs the suite across the whole Ruby × Rails matrix above, plus `bundle exec
|
|
204
|
+
rubocop`. Both must pass before a pull request merges.
|
|
205
|
+
|
|
206
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
207
|
+
|
|
208
|
+
To release a new version, bump `Recorder::VERSION` and move the `[Unreleased]`
|
|
209
|
+
changelog entries under it in a pull request. Once that merges, tag the merge
|
|
210
|
+
commit and push the tag:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
git fetch origin
|
|
214
|
+
git tag vX.Y.Z origin/master
|
|
215
|
+
git push origin vX.Y.Z
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The Release workflow then runs CI against the tagged commit, checks the tag
|
|
219
|
+
matches `Recorder::VERSION`, publishes the gem to [rubygems.org](https://rubygems.org)
|
|
220
|
+
through trusted publishing, and creates the GitHub release from the changelog.
|
|
60
221
|
|
|
61
222
|
## Contributing
|
|
62
223
|
|
|
@@ -64,11 +225,10 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jetroc
|
|
|
64
225
|
|
|
65
226
|
## Credits
|
|
66
227
|
|
|
67
|
-

|
|
68
229
|
|
|
69
|
-
Recorder is maintained by [JetRockets](https://www.jetrockets.
|
|
230
|
+
Recorder is maintained by [JetRockets](https://www.jetrockets.com).
|
|
70
231
|
|
|
71
232
|
## License
|
|
72
233
|
|
|
73
234
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
|
74
|
-
|
|
@@ -10,20 +10,6 @@ module Recorder
|
|
|
10
10
|
|
|
11
11
|
source_root File.expand_path('templates', __dir__)
|
|
12
12
|
|
|
13
|
-
class_option(
|
|
14
|
-
:with_partitions,
|
|
15
|
-
type: :boolean,
|
|
16
|
-
default: false,
|
|
17
|
-
desc: 'Create partitions to `recorder_revisions` table'
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
class_option(
|
|
21
|
-
:with_number_column,
|
|
22
|
-
type: :boolean,
|
|
23
|
-
default: false,
|
|
24
|
-
desc: 'Add `number` column to `recorder_revisions` table'
|
|
25
|
-
)
|
|
26
|
-
|
|
27
13
|
class_option(
|
|
28
14
|
:with_index_by_user_id,
|
|
29
15
|
type: :boolean,
|
|
@@ -35,9 +21,7 @@ module Recorder
|
|
|
35
21
|
|
|
36
22
|
def create_migration_file
|
|
37
23
|
add_or_skip_recorder_migration('create_recorder_revisions')
|
|
38
|
-
add_or_skip_recorder_migration('add_number_column_to_recorder_revisions') if options.with_number_column?
|
|
39
24
|
add_or_skip_recorder_migration('add_index_by_user_id_to_recorder_revisions') if options.with_index_by_user_id?
|
|
40
|
-
add_or_skip_recorder_migration('add_partitions_to_recorder_revisions') if options.with_partitions?
|
|
41
25
|
end
|
|
42
26
|
|
|
43
27
|
def self.next_migration_number(dirname)
|
|
@@ -47,11 +31,11 @@ module Recorder
|
|
|
47
31
|
protected
|
|
48
32
|
|
|
49
33
|
def add_or_skip_recorder_migration(template)
|
|
50
|
-
migration_dir = File.expand_path('db/migrate')
|
|
34
|
+
migration_dir = File.expand_path('db/migrate', destination_root)
|
|
51
35
|
if self.class.migration_exists?(migration_dir, template)
|
|
52
36
|
::Kernel.warn "Migration already exists: #{template}"
|
|
53
37
|
else
|
|
54
|
-
migration_template "#{template}.rb", "db/migrate/#{template}.rb"
|
|
38
|
+
migration_template "#{template}.rb.tt", "db/migrate/#{template}.rb"
|
|
55
39
|
end
|
|
56
40
|
end
|
|
57
41
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# This migration adds
|
|
4
|
-
class AddIndexByUserIdToRecorderRevisions < ActiveRecord::Migration
|
|
3
|
+
# This migration adds an index by `user_id` to the `recorder_revisions` table.
|
|
4
|
+
class AddIndexByUserIdToRecorderRevisions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
5
5
|
def change
|
|
6
6
|
add_index :recorder_revisions, :user_id
|
|
7
7
|
end
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# This migration creates the `recorder_revisions` table.
|
|
4
|
-
class CreateRecorderRevisions < ActiveRecord::Migration
|
|
4
|
+
class CreateRecorderRevisions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
5
5
|
def change
|
|
6
6
|
create_table :recorder_revisions do |t|
|
|
7
7
|
t.string :item_type, null: false
|
|
8
|
-
t.
|
|
8
|
+
t.bigint :item_id
|
|
9
9
|
t.string :event, null: false
|
|
10
10
|
t.jsonb :data, null: false
|
|
11
11
|
t.inet :ip
|
|
12
12
|
t.date :action_date, null: false
|
|
13
|
-
t.
|
|
13
|
+
t.bigint :user_id
|
|
14
14
|
t.jsonb :meta
|
|
15
15
|
t.datetime :created_at, null: false
|
|
16
16
|
end
|
data/lib/recorder/manager.rb
CHANGED
data/lib/recorder/tape/record.rb
CHANGED
|
@@ -28,13 +28,15 @@ module Recorder
|
|
|
28
28
|
options[:async].nil? ? Recorder.config.async : options[:async]
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
# Sidekiq accepts only JSON-native job arguments. `data` is serialised
|
|
32
|
+
# ahead of the round trip so it survives as a string for the worker to
|
|
33
|
+
# parse back; everything else is normalised by the round trip itself.
|
|
31
34
|
def record_async(params, options)
|
|
32
35
|
params[:data] = params[:data].to_json
|
|
33
|
-
params[:action_date] = params[:action_date].to_s
|
|
34
36
|
|
|
35
37
|
Recorder::Sidekiq::RevisionsWorker.perform_in(
|
|
36
38
|
options[:delay] || 2.seconds,
|
|
37
|
-
params.
|
|
39
|
+
JSON.parse(params.to_json)
|
|
38
40
|
)
|
|
39
41
|
end
|
|
40
42
|
end
|
data/lib/recorder/version.rb
CHANGED
data/lib/recorder.rb
CHANGED
metadata
CHANGED
|
@@ -1,71 +1,83 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: recorder
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Igor Alexandrov
|
|
8
8
|
autorequire:
|
|
9
|
-
bindir:
|
|
9
|
+
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-09-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: '6.1'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '9'
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
26
|
requirements:
|
|
24
|
-
- - "
|
|
27
|
+
- - ">="
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
29
|
version: '6.1'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '9'
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
34
|
name: activesupport
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
30
36
|
requirements:
|
|
31
|
-
- - "
|
|
37
|
+
- - ">="
|
|
32
38
|
- !ruby/object:Gem::Version
|
|
33
39
|
version: '6.1'
|
|
40
|
+
- - "<"
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '9'
|
|
34
43
|
type: :runtime
|
|
35
44
|
prerelease: false
|
|
36
45
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
46
|
requirements:
|
|
38
|
-
- - "
|
|
47
|
+
- - ">="
|
|
39
48
|
- !ruby/object:Gem::Version
|
|
40
49
|
version: '6.1'
|
|
50
|
+
- - "<"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '9'
|
|
41
53
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
54
|
+
name: request_store
|
|
43
55
|
requirement: !ruby/object:Gem::Requirement
|
|
44
56
|
requirements:
|
|
45
57
|
- - ">="
|
|
46
58
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '0'
|
|
59
|
+
version: '1.0'
|
|
48
60
|
type: :runtime
|
|
49
61
|
prerelease: false
|
|
50
62
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
63
|
requirements:
|
|
52
64
|
- - ">="
|
|
53
65
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '0'
|
|
66
|
+
version: '1.0'
|
|
55
67
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
68
|
+
name: appraisal
|
|
57
69
|
requirement: !ruby/object:Gem::Requirement
|
|
58
70
|
requirements:
|
|
59
|
-
- - "
|
|
71
|
+
- - "~>"
|
|
60
72
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
62
|
-
type: :
|
|
73
|
+
version: '2.5'
|
|
74
|
+
type: :development
|
|
63
75
|
prerelease: false
|
|
64
76
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
77
|
requirements:
|
|
66
|
-
- - "
|
|
78
|
+
- - "~>"
|
|
67
79
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
80
|
+
version: '2.5'
|
|
69
81
|
- !ruby/object:Gem::Dependency
|
|
70
82
|
name: bundler
|
|
71
83
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -84,16 +96,16 @@ dependencies:
|
|
|
84
96
|
name: generator_spec
|
|
85
97
|
requirement: !ruby/object:Gem::Requirement
|
|
86
98
|
requirements:
|
|
87
|
-
- - "
|
|
99
|
+
- - "~>"
|
|
88
100
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '0'
|
|
101
|
+
version: '0.9'
|
|
90
102
|
type: :development
|
|
91
103
|
prerelease: false
|
|
92
104
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
105
|
requirements:
|
|
94
|
-
- - "
|
|
106
|
+
- - "~>"
|
|
95
107
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '0'
|
|
108
|
+
version: '0.9'
|
|
97
109
|
- !ruby/object:Gem::Dependency
|
|
98
110
|
name: jetrockets-standard
|
|
99
111
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -109,47 +121,47 @@ dependencies:
|
|
|
109
121
|
- !ruby/object:Gem::Version
|
|
110
122
|
version: '0'
|
|
111
123
|
- !ruby/object:Gem::Dependency
|
|
112
|
-
name:
|
|
124
|
+
name: pg
|
|
113
125
|
requirement: !ruby/object:Gem::Requirement
|
|
114
126
|
requirements:
|
|
115
|
-
- - "
|
|
127
|
+
- - ">="
|
|
116
128
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: '
|
|
129
|
+
version: '1.0'
|
|
118
130
|
type: :development
|
|
119
131
|
prerelease: false
|
|
120
132
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
133
|
requirements:
|
|
122
|
-
- - "
|
|
134
|
+
- - ">="
|
|
123
135
|
- !ruby/object:Gem::Version
|
|
124
|
-
version: '
|
|
136
|
+
version: '1.0'
|
|
125
137
|
- !ruby/object:Gem::Dependency
|
|
126
|
-
name:
|
|
138
|
+
name: rake
|
|
127
139
|
requirement: !ruby/object:Gem::Requirement
|
|
128
140
|
requirements:
|
|
129
|
-
- - "
|
|
141
|
+
- - "~>"
|
|
130
142
|
- !ruby/object:Gem::Version
|
|
131
|
-
version: '0'
|
|
143
|
+
version: '13.0'
|
|
132
144
|
type: :development
|
|
133
145
|
prerelease: false
|
|
134
146
|
version_requirements: !ruby/object:Gem::Requirement
|
|
135
147
|
requirements:
|
|
136
|
-
- - "
|
|
148
|
+
- - "~>"
|
|
137
149
|
- !ruby/object:Gem::Version
|
|
138
|
-
version: '0'
|
|
150
|
+
version: '13.0'
|
|
139
151
|
- !ruby/object:Gem::Dependency
|
|
140
|
-
name: rails
|
|
152
|
+
name: rspec-rails
|
|
141
153
|
requirement: !ruby/object:Gem::Requirement
|
|
142
154
|
requirements:
|
|
143
155
|
- - ">="
|
|
144
156
|
- !ruby/object:Gem::Version
|
|
145
|
-
version: '0'
|
|
157
|
+
version: '5.0'
|
|
146
158
|
type: :development
|
|
147
159
|
prerelease: false
|
|
148
160
|
version_requirements: !ruby/object:Gem::Requirement
|
|
149
161
|
requirements:
|
|
150
162
|
- - ">="
|
|
151
163
|
- !ruby/object:Gem::Version
|
|
152
|
-
version: '0'
|
|
164
|
+
version: '5.0'
|
|
153
165
|
description: Recorder tracks changes of your Rails models
|
|
154
166
|
email:
|
|
155
167
|
- igor.alexandrov@jetrockets.com
|
|
@@ -157,21 +169,13 @@ executables: []
|
|
|
157
169
|
extensions: []
|
|
158
170
|
extra_rdoc_files: []
|
|
159
171
|
files:
|
|
160
|
-
-
|
|
161
|
-
- ".rspec"
|
|
162
|
-
- ".rubocop.yml"
|
|
163
|
-
- ".travis.yml"
|
|
164
|
-
- Gemfile
|
|
172
|
+
- CHANGELOG.md
|
|
165
173
|
- LICENSE.txt
|
|
166
174
|
- README.md
|
|
167
|
-
- Rakefile
|
|
168
|
-
- bin/console
|
|
169
|
-
- bin/setup
|
|
170
175
|
- lib/generators/recorder/USAGE
|
|
171
176
|
- lib/generators/recorder/install_generator.rb
|
|
172
|
-
- lib/generators/recorder/templates/add_index_by_user_id_to_recorder_revisions.rb
|
|
173
|
-
- lib/generators/recorder/templates/
|
|
174
|
-
- lib/generators/recorder/templates/create_recorder_revisions.rb
|
|
177
|
+
- lib/generators/recorder/templates/add_index_by_user_id_to_recorder_revisions.rb.tt
|
|
178
|
+
- lib/generators/recorder/templates/create_recorder_revisions.rb.tt
|
|
175
179
|
- lib/recorder.rb
|
|
176
180
|
- lib/recorder/changeset.rb
|
|
177
181
|
- lib/recorder/config.rb
|
|
@@ -186,11 +190,14 @@ files:
|
|
|
186
190
|
- lib/recorder/tape/data.rb
|
|
187
191
|
- lib/recorder/tape/record.rb
|
|
188
192
|
- lib/recorder/version.rb
|
|
189
|
-
- recorder.gemspec
|
|
190
193
|
homepage: https://github.com/jetrockets/recorder
|
|
191
194
|
licenses:
|
|
192
195
|
- MIT
|
|
193
|
-
metadata:
|
|
196
|
+
metadata:
|
|
197
|
+
source_code_uri: https://github.com/jetrockets/recorder
|
|
198
|
+
changelog_uri: https://github.com/jetrockets/recorder/blob/master/CHANGELOG.md
|
|
199
|
+
bug_tracker_uri: https://github.com/jetrockets/recorder/issues
|
|
200
|
+
rubygems_mfa_required: 'true'
|
|
194
201
|
post_install_message:
|
|
195
202
|
rdoc_options: []
|
|
196
203
|
require_paths:
|
|
@@ -199,14 +206,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
199
206
|
requirements:
|
|
200
207
|
- - ">="
|
|
201
208
|
- !ruby/object:Gem::Version
|
|
202
|
-
version: '0'
|
|
209
|
+
version: '3.0'
|
|
203
210
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
204
211
|
requirements:
|
|
205
212
|
- - ">="
|
|
206
213
|
- !ruby/object:Gem::Version
|
|
207
214
|
version: '0'
|
|
208
215
|
requirements: []
|
|
209
|
-
rubygems_version: 3.
|
|
216
|
+
rubygems_version: 3.5.22
|
|
210
217
|
signing_key:
|
|
211
218
|
specification_version: 4
|
|
212
219
|
summary: Rails model auditor
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/.rubocop.yml
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
inherit_gem:
|
|
2
|
-
jetrockets-standard: config/gems.yml
|
|
3
|
-
|
|
4
|
-
AllCops:
|
|
5
|
-
Exclude:
|
|
6
|
-
- 'bin/*'
|
|
7
|
-
- 'tmp/**/*'
|
|
8
|
-
- 'docs/**/*'
|
|
9
|
-
- 'Gemfile'
|
|
10
|
-
- 'vendor/**/*'
|
|
11
|
-
- 'gemfiles/**/*'
|
|
12
|
-
DisplayCopNames: true
|
|
13
|
-
TargetRubyVersion: 2.5
|
|
14
|
-
|
|
15
|
-
Style/TrailingCommaInArrayLiteral:
|
|
16
|
-
EnforcedStyleForMultiline: no_comma
|
|
17
|
-
|
|
18
|
-
Style/TrailingCommaInHashLiteral:
|
|
19
|
-
EnforcedStyleForMultiline: no_comma
|
|
20
|
-
|
|
21
|
-
Layout/ParameterAlignment:
|
|
22
|
-
EnforcedStyle: with_first_parameter
|
|
23
|
-
|
|
24
|
-
# See https://github.com/rubocop-hq/rubocop/issues/4222
|
|
25
|
-
Lint/AmbiguousBlockAssociation:
|
|
26
|
-
Exclude:
|
|
27
|
-
- 'spec/**/*'
|
data/.travis.yml
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
data/bin/console
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require 'bundler/setup'
|
|
5
|
-
require 'recorder'
|
|
6
|
-
|
|
7
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
-
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
-
|
|
10
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
-
# require "pry"
|
|
12
|
-
# Pry.start
|
|
13
|
-
|
|
14
|
-
require 'irb'
|
|
15
|
-
IRB.start
|
data/bin/setup
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# This migration adds number column to the `revisions` table.
|
|
4
|
-
class AddNumberColumnToRecorderRevisions < ActiveRecord::Migration
|
|
5
|
-
def up
|
|
6
|
-
add_column :recorder_revisions, :number, :integer, null: false, default: 0
|
|
7
|
-
|
|
8
|
-
execute <<~SQL
|
|
9
|
-
CREATE OR REPLACE FUNCTION get_recorder_revisions_number()
|
|
10
|
-
RETURNS trigger AS
|
|
11
|
-
$BODY$
|
|
12
|
-
BEGIN
|
|
13
|
-
SELECT COALESCE(MAX(recorder_revisions.number), 0) + 1
|
|
14
|
-
INTO NEW.number
|
|
15
|
-
FROM
|
|
16
|
-
recorder_revisions
|
|
17
|
-
WHERE
|
|
18
|
-
recorder_revisions.item_type = NEW.item_type
|
|
19
|
-
AND recorder_revisions.item_id = NEW.item_id;
|
|
20
|
-
#{" "}
|
|
21
|
-
RETURN NEW;
|
|
22
|
-
END;
|
|
23
|
-
$BODY$ LANGUAGE plpgsql;
|
|
24
|
-
SQL
|
|
25
|
-
|
|
26
|
-
execute <<~SQL
|
|
27
|
-
CREATE TRIGGER update_recorder_revisions_number
|
|
28
|
-
BEFORE INSERT ON recorder_revisions FOR EACH ROW
|
|
29
|
-
EXECUTE PROCEDURE get_recorder_revisions_number();
|
|
30
|
-
SQL
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def down
|
|
34
|
-
execute <<-SQL
|
|
35
|
-
DROP TRIGGER update_recorder_revisions_number;
|
|
36
|
-
SQL
|
|
37
|
-
|
|
38
|
-
execute <<-SQL
|
|
39
|
-
DROP FUNCTION IF EXISTS get_recorder_revisions_number;
|
|
40
|
-
SQL
|
|
41
|
-
|
|
42
|
-
remove_column :recorder_revisions, :number
|
|
43
|
-
end
|
|
44
|
-
end
|
data/recorder.gemspec
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
lib = File.expand_path('lib', __dir__)
|
|
4
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
-
require 'recorder/version'
|
|
6
|
-
|
|
7
|
-
Gem::Specification.new do |spec|
|
|
8
|
-
spec.name = 'recorder'
|
|
9
|
-
spec.version = Recorder::VERSION
|
|
10
|
-
spec.authors = ['Igor Alexandrov']
|
|
11
|
-
spec.email = ['igor.alexandrov@jetrockets.com']
|
|
12
|
-
|
|
13
|
-
spec.summary = 'Rails model auditor'
|
|
14
|
-
spec.description = 'Recorder tracks changes of your Rails models'
|
|
15
|
-
spec.homepage = 'https://github.com/jetrockets/recorder'
|
|
16
|
-
spec.license = 'MIT'
|
|
17
|
-
|
|
18
|
-
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
19
|
-
spec.bindir = 'exe'
|
|
20
|
-
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
21
|
-
spec.require_paths = ['lib']
|
|
22
|
-
|
|
23
|
-
spec.add_dependency 'activerecord', '~> 6.1'
|
|
24
|
-
spec.add_dependency 'activesupport', '~> 6.1'
|
|
25
|
-
spec.add_dependency 'pg'
|
|
26
|
-
spec.add_dependency 'request_store'
|
|
27
|
-
|
|
28
|
-
spec.add_development_dependency 'bundler', '>= 2.0'
|
|
29
|
-
# spec.add_development_dependency 'database_cleaner'
|
|
30
|
-
spec.add_development_dependency 'generator_spec'
|
|
31
|
-
spec.add_development_dependency 'jetrockets-standard'
|
|
32
|
-
spec.add_development_dependency 'rake', '~> 13.0'
|
|
33
|
-
spec.add_development_dependency 'rspec-rails'
|
|
34
|
-
# spec.add_development_dependency 'timecop', '~> 0.8.1'
|
|
35
|
-
# spec.add_development_dependency 'yard'
|
|
36
|
-
spec.add_development_dependency 'rails-dummy'
|
|
37
|
-
end
|