recorder 1.2.3 → 1.4.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 +146 -0
- data/README.md +220 -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/changeset.rb +8 -2
- data/lib/recorder/manager.rb +3 -1
- data/lib/recorder/observer.rb +2 -0
- data/lib/recorder/revision.rb +2 -2
- data/lib/recorder/tape/data.rb +18 -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: 064c2afab28615f3f268bfacee354fdf6fceff7063190e084783dfe1640fe054
|
|
4
|
+
data.tar.gz: 6062f268b4a47ea089cf90fac14ac124fc2c5e07f65be6d37228a663ec4cfc5e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ed30006c3d6f960d017a8b97853d5737624c9690f31ffc60ee3d0dc2dbbb21507f3e52e8203dd103192ba8648ca6dc643e969a232fd40a7ee65d0dcc7f11a87b
|
|
7
|
+
data.tar.gz: 689acc4c8867035e627413e5586280e2b05327efa7a002713903300016f19a99caa1194e172de45633f6b4fb8771cdf3a380905784f327b3df01bca39ed93129
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
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.4.0]
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- README coverage of what a revision's `data` holds: the complete attribute
|
|
15
|
+
snapshot on every event, when `changes` and `associations` are present, and
|
|
16
|
+
which events record a revision at all. No behaviour changes — the snapshot is
|
|
17
|
+
what the gem has always written, and it is now written down.
|
|
18
|
+
- A known issue for the `changes` key on `destroy` revisions, which describes
|
|
19
|
+
the record's last update rather than the deletion.
|
|
20
|
+
- A `changes:` option on `recorder` that merges extra entries into a revision's
|
|
21
|
+
`changes`. It takes a Proc evaluated on the record or the name of a method on it;
|
|
22
|
+
both receive the event.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- `Recorder::Revision` declares both `belongs_to :user` and `belongs_to :item`
|
|
27
|
+
with `optional: true`. The associations have always been optional in practice,
|
|
28
|
+
but only because the gem is required before the Active Record railtie applies
|
|
29
|
+
`belongs_to_required_by_default`, so the reflections are built while the
|
|
30
|
+
default is still off. Had that ordering ever shifted, every revision without a
|
|
31
|
+
user would have failed validation, and so would every `destroy` revision,
|
|
32
|
+
which is written after its item's row is deleted. `Tape::Record#record` calls
|
|
33
|
+
`create`, not `create!`, so those revisions would have been dropped with
|
|
34
|
+
nothing raised anywhere. Recorded revisions are unchanged.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- `Recorder::Changeset#previous` and `#next` no longer raise `NoMethodError` when
|
|
39
|
+
the changes carry a key that is not an attribute of the model, and skip values
|
|
40
|
+
that are not a two-element `[old, new]` pair rather than indexing into them.
|
|
41
|
+
|
|
42
|
+
## [1.3.0] - 2026-09-16
|
|
43
|
+
|
|
44
|
+
### Added
|
|
45
|
+
|
|
46
|
+
- Support for Rails 7.0, 7.1, 7.2, 8.0, and 8.1. Rails 6.1 remains supported.
|
|
47
|
+
- Support for Ruby 3.3 and 3.4. Ruby 3.0 remains the minimum.
|
|
48
|
+
- Declared and tested the supported Ruby versions (#10).
|
|
49
|
+
- The specs and RuboCop now run in GitHub Actions (#9).
|
|
50
|
+
- Releases are published to RubyGems.org from GitHub Actions when a version tag
|
|
51
|
+
is pushed (#22).
|
|
52
|
+
- Gem metadata: `source_code_uri`, `changelog_uri`, `bug_tracker_uri`, and
|
|
53
|
+
`rubygems_mfa_required`.
|
|
54
|
+
- README coverage of the public API: configuration, `Recorder.meta=`,
|
|
55
|
+
`recorder_disabled!`, and the association tracking options (#20).
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- `activerecord` and `activesupport` requirements widen from `~> 6.1` to
|
|
60
|
+
`>= 6.1, < 9`, and the `< 3.4` Ruby ceiling is removed. Rails 6.1 and 7.0
|
|
61
|
+
cannot run on Ruby 3.4; every other combination is exercised in CI.
|
|
62
|
+
- The released gem now ships only `lib/`, the README, the LICENSE, and this
|
|
63
|
+
changelog. Previous releases also carried repository scaffolding — CI
|
|
64
|
+
configuration, `Rakefile`, `bin/`, and dotfiles — that a host app never loads.
|
|
65
|
+
- `recorder_revisions.item_id` and `recorder_revisions.user_id` are now
|
|
66
|
+
`bigint`. Rails has defaulted primary keys to `bigint` since 5.1, so the
|
|
67
|
+
`integer` columns could not hold a key from any table this gem audits once it
|
|
68
|
+
passed 2,147,483,647. No release since Rails 5.0 shipped a migration that could
|
|
69
|
+
run; installs from the 0.1.x line on Rails 4 have the narrow columns and are
|
|
70
|
+
unaffected, because the gem ships no migration that alters an existing table
|
|
71
|
+
(#18).
|
|
72
|
+
|
|
73
|
+
### Removed
|
|
74
|
+
|
|
75
|
+
- `pg` is no longer a runtime dependency. Revisions still require PostgreSQL
|
|
76
|
+
column types, but the adapter is the host application's to declare, so the
|
|
77
|
+
gem no longer forces `pg` into its bundle.
|
|
78
|
+
- The `--with_partitions` generator option. It dispatched to a template that was
|
|
79
|
+
never added to the gem, so the run wrote the first migration and then aborted,
|
|
80
|
+
leaving a partial install. It never completed once (#18).
|
|
81
|
+
- The `--with_number_column` generator option. The `number` column it added was
|
|
82
|
+
never read by the gem, and the migration it generated could not run on any
|
|
83
|
+
Rails this gem supports, so there is no working installed base. Anyone who has
|
|
84
|
+
the column keeps it — the counter lives in a trigger in their own schema,
|
|
85
|
+
which this does not touch, and the gem never writes the column. Optional
|
|
86
|
+
cleanup, including a working `DROP TRIGGER`, is in #14 — the `down` the gem
|
|
87
|
+
originally shipped is a syntax error and cannot roll it back (#14).
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- Asynchronous recording no longer enqueues job arguments Sidekiq rejects. The
|
|
92
|
+
`meta` hash supplied through `Recorder.meta=` reached the worker with its
|
|
93
|
+
original keys and values, and Sidekiq raises on arguments that are not JSON
|
|
94
|
+
native from 7.0 onwards. Because the raise happened inside an `after_create`,
|
|
95
|
+
it rolled back the host application's own write. The enqueued payload is now
|
|
96
|
+
normalised to JSON natives in full (#19).
|
|
97
|
+
- `rails generate recorder:install` now produces migrations that run. The
|
|
98
|
+
templates subclassed a bare `ActiveRecord::Migration`, which Rails has
|
|
99
|
+
rejected since 5.0, so `rails db:migrate` raised on the only documented way to
|
|
100
|
+
install the gem. The version is templated from the host app's Rails (#18).
|
|
101
|
+
- `Recorder.version` returns the version instead of raising `TypeError`. It
|
|
102
|
+
scoped into `VERSION::STRING`, but `VERSION` is a `String` (#17).
|
|
103
|
+
- `recorder_disabled!` re-enables recording when the block raises. The re-enable
|
|
104
|
+
was skipped on the exception path, so recording stayed off for the rest of the
|
|
105
|
+
request or job and every later save went silently unaudited (#17).
|
|
106
|
+
- Made the spec suite runnable and trustworthy again (#8).
|
|
107
|
+
|
|
108
|
+
## [1.2.3] - 2023-04-25
|
|
109
|
+
|
|
110
|
+
### Fixed
|
|
111
|
+
|
|
112
|
+
- A revision is created when an item's associations change.
|
|
113
|
+
- No revision is created for an `:update` event when nothing changed.
|
|
114
|
+
|
|
115
|
+
## [1.2.2] - 2022-11-17
|
|
116
|
+
|
|
117
|
+
### Fixed
|
|
118
|
+
|
|
119
|
+
- Refactoring for full Rails 6 compatibility.
|
|
120
|
+
|
|
121
|
+
## [1.2.1] - 2022-11-16
|
|
122
|
+
|
|
123
|
+
### Fixed
|
|
124
|
+
|
|
125
|
+
- Fixed parsing of empty associations.
|
|
126
|
+
|
|
127
|
+
## [1.2.0] - 2022-11-15
|
|
128
|
+
|
|
129
|
+
### Added
|
|
130
|
+
|
|
131
|
+
- Rails 6 support.
|
|
132
|
+
|
|
133
|
+
## [1.1.1] - 2021-09-13
|
|
134
|
+
|
|
135
|
+
Releases at and before 1.1.1 predate this changelog. See the
|
|
136
|
+
[commit history](https://github.com/jetrockets/recorder/commits/master) for
|
|
137
|
+
details.
|
|
138
|
+
|
|
139
|
+
[Unreleased]: https://github.com/jetrockets/recorder/compare/v1.4.0...HEAD
|
|
140
|
+
[1.4.0]: https://github.com/jetrockets/recorder/compare/v1.3.0...v1.4.0
|
|
141
|
+
[1.3.0]: https://github.com/jetrockets/recorder/compare/v1.2.3...v1.3.0
|
|
142
|
+
[1.2.3]: https://github.com/jetrockets/recorder/compare/v1.2.2...v1.2.3
|
|
143
|
+
[1.2.2]: https://github.com/jetrockets/recorder/compare/v1.2.1...v1.2.2
|
|
144
|
+
[1.2.1]: https://github.com/jetrockets/recorder/compare/v1.2.0...v1.2.1
|
|
145
|
+
[1.2.0]: https://github.com/jetrockets/recorder/compare/v1.1.1...v1.2.0
|
|
146
|
+
[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
|
|
47
|
+
|
|
48
|
+
The generator accepts one option:
|
|
18
49
|
|
|
19
|
-
|
|
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
|
|
|
@@ -40,8 +73,55 @@ Recorder supports the following options:
|
|
|
40
73
|
* `only: [array]` - only these attributes are logged, other attributes are ingored;
|
|
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).
|
|
76
|
+
* `changes: Proc | Symbol` - extra entries to merge into a revision's `changes`. A Proc
|
|
77
|
+
is evaluated on the record, a Symbol names a method on it; both receive the event
|
|
78
|
+
(`:create`, `:update` or `:destroy`) and return a hash of `name => [old, new]`, or
|
|
79
|
+
`nil` for nothing. Anything else raises `ArgumentError` where `recorder` is called.
|
|
80
|
+
|
|
81
|
+
The entries are merged after `only:` and `ignore:` have been applied, so those
|
|
82
|
+
filters never drop them, and a key that matches an attribute replaces that
|
|
83
|
+
attribute's entry. An update that reports only custom entries still records a
|
|
84
|
+
revision.
|
|
85
|
+
|
|
86
|
+
Inside the callback, read `saved_changes` and `saved_change_to_<attribute>?`, not
|
|
87
|
+
`<attribute>_changed?`: the callback runs from `after_create`/`after_update`, where
|
|
88
|
+
the dirty state has already been reset.
|
|
43
89
|
|
|
44
|
-
|
|
90
|
+
`Recorder::Changeset` rebuilds the previous and next versions by assigning each
|
|
91
|
+
change onto a copy of the record, and skips what it cannot assign: a key that is
|
|
92
|
+
not an attribute, and any value that is not a two-element `[old, new]` pair. To display such
|
|
93
|
+
a key, define `previous_<key>`/`next_<key>` on the model's changeset class, and
|
|
94
|
+
pick a name that `Recorder::Changeset` does not already answer to.
|
|
95
|
+
|
|
96
|
+
These per-model options, `changes:` included, do not reach the recorder yet — see [Known issues](#known-issues).
|
|
97
|
+
Until they do, every observed model records a full attribute snapshot, filtered
|
|
98
|
+
only by the global `Recorder.config.ignore`. Options declared as an instance
|
|
99
|
+
method are read, so that is the way to opt into any of them today:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
def recorder_options
|
|
103
|
+
{ignore: %i[identifier], changes: :extra_changes}
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Global configuration
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
Recorder.config do |config|
|
|
111
|
+
config.ignore = %i[created_at updated_at]
|
|
112
|
+
config.async = false
|
|
113
|
+
config.sidekiq_options = {queue: 'recorder', retry: 10, backtrace: true}
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`ignore` defaults to `[]`, `async` to `false`, and `sidekiq_options` to the hash
|
|
118
|
+
shown above.
|
|
119
|
+
|
|
120
|
+
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
|
|
121
|
+
strategy the revision is enqueued to `Recorder::Sidekiq::RevisionsWorker` two
|
|
122
|
+
seconds out; the worker is loaded by the railtie when `Sidekiq` is defined.
|
|
123
|
+
|
|
124
|
+
### Recording the current user
|
|
45
125
|
|
|
46
126
|
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
127
|
|
|
@@ -52,11 +132,142 @@ To enable storing of such data as user_id and ip, you need to include `Recorder:
|
|
|
52
132
|
end
|
|
53
133
|
```
|
|
54
134
|
|
|
135
|
+
The concern reads `current_user`; override `recorder_user_id` to name a different
|
|
136
|
+
method. Override `recorder_meta` to store a hash alongside every revision. A
|
|
137
|
+
revision without a user is valid — `user_id` stays `nil`.
|
|
138
|
+
|
|
139
|
+
### Turning recording off
|
|
140
|
+
|
|
141
|
+
`Recorder::Manager` suspends recording for the current request or thread:
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
class Importer
|
|
145
|
+
include Recorder::Manager
|
|
146
|
+
|
|
147
|
+
def call
|
|
148
|
+
recorder_disabled! do
|
|
149
|
+
# nothing recorded in here
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The block form re-enables recording on the way out, including when the block
|
|
156
|
+
raises. Called without a block, `recorder_disabled!` stays in effect until
|
|
157
|
+
`recorder_enabled!`.
|
|
158
|
+
|
|
159
|
+
### Reading revisions
|
|
160
|
+
|
|
161
|
+
Observed models get a `revisions` association:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
revision = post.revisions.ordered_by_created_at.first
|
|
165
|
+
|
|
166
|
+
revision.event # "update"
|
|
167
|
+
revision.data # {"attributes" => {...}, "changes" => {...}, "associations" => {...}}
|
|
168
|
+
revision.user_id
|
|
169
|
+
revision.action_date
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`data` carries a complete attribute snapshot on every event, not only what
|
|
173
|
+
changed:
|
|
174
|
+
|
|
175
|
+
- `attributes` — every attribute of the record as it stood when the callback
|
|
176
|
+
ran, filtered by the model's `only:` or `ignore:` and, failing those, by
|
|
177
|
+
`Recorder.config.ignore`. Always present.
|
|
178
|
+
- `changes` — the same filter applied to `saved_changes`, as
|
|
179
|
+
`name => [old, new]`, plus any entries from `changes:`. Omitted when that
|
|
180
|
+
leaves nothing.
|
|
181
|
+
- `associations` — those two keys again, one entry per association named in
|
|
182
|
+
`associations:`. Omitted when no association reports anything.
|
|
183
|
+
|
|
184
|
+
The snapshot is the contract, not an accident of the implementation: a revision
|
|
185
|
+
is self-contained, so reconstructing a record at a point in time does not mean
|
|
186
|
+
replaying every prior diff. It is also what keeps a `destroy` revision useful,
|
|
187
|
+
since the row it describes is gone.
|
|
188
|
+
|
|
189
|
+
An `update` records a revision only when the record or one of its recorded
|
|
190
|
+
associations reports a change; `create` and `destroy` always record one.
|
|
191
|
+
|
|
192
|
+
`#item_changeset` wraps `data['changes']` in a `Recorder::Changeset`, which reads
|
|
193
|
+
the values back as the model's own types:
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
changeset = revision.item_changeset
|
|
197
|
+
|
|
198
|
+
changeset.keys # attributes that changed
|
|
199
|
+
changeset.previous(:title) # value before the change
|
|
200
|
+
changeset.next(:title) # value after it
|
|
201
|
+
changeset.human_attribute_name(:title)
|
|
202
|
+
changeset.previous_version # a copy of the record with the old values
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Changed associations are reachable the same way:
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
revision.changed_associations # ["author"]
|
|
209
|
+
revision.association_changeset('author')
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
To render a changeset yourself, define `PostChangeset` — the class is looked up
|
|
213
|
+
as `"#{model}Changeset"` — or point at another one with a
|
|
214
|
+
`recorder_changeset_class` class method on the model. Otherwise
|
|
215
|
+
`Recorder::Changeset` is used.
|
|
216
|
+
|
|
217
|
+
## Known issues
|
|
218
|
+
|
|
219
|
+
The gem is under active maintenance and these defects are known as of 1.3.0:
|
|
220
|
+
|
|
221
|
+
- The per-model options above (`ignore:`, `only:`, `associations:`, `async:`)
|
|
222
|
+
are not applied. `Recorder::Tape` asks the record instance for
|
|
223
|
+
`recorder_options`, but `Recorder::Observer` defines that method on the class,
|
|
224
|
+
so the lookup always falls back to `{}` — every observed model records a full
|
|
225
|
+
attribute snapshot, synchronously. Global configuration is unaffected.
|
|
226
|
+
- `Recorder.enabled=` does not switch recording off. It writes to
|
|
227
|
+
`Recorder.config`, which nothing on the recording path reads — the gates are in
|
|
228
|
+
`Recorder.store`, which `Recorder::Manager` drives.
|
|
229
|
+
- Collection associations are never recorded. `associations:` handles only
|
|
230
|
+
singular associations; a `has_many` reflection is skipped without a warning.
|
|
231
|
+
- A `destroy` revision carries a `changes` key describing the record's last
|
|
232
|
+
*update*. `destroy` does not clear `saved_changes`, and `data` is built the
|
|
233
|
+
same way for every event. The `attributes` snapshot is the accurate record of
|
|
234
|
+
what was deleted.
|
|
235
|
+
|
|
55
236
|
## Development
|
|
56
237
|
|
|
57
238
|
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
239
|
|
|
59
|
-
|
|
240
|
+
The specs need a PostgreSQL server. Connection settings are read from the
|
|
241
|
+
environment — `RECORDER_DB_HOST`, `RECORDER_DB_PORT`, `RECORDER_DB_USERNAME`,
|
|
242
|
+
`RECORDER_DB_PASSWORD`, `RECORDER_DB_NAME` — and default to `postgres:postgres`
|
|
243
|
+
on `localhost:5432` against a `recorder_test` database, which must already exist.
|
|
244
|
+
|
|
245
|
+
`rake spec` runs against whichever Rails version the root `Gemfile` resolves to.
|
|
246
|
+
To run against a specific one, use the appraisal gemfiles:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
bundle exec appraisal install # once, to generate gemfiles/
|
|
250
|
+
bundle exec appraisal rails-7.2 rake spec
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
CI runs the suite across the whole Ruby × Rails matrix above, plus `bundle exec
|
|
254
|
+
rubocop`. Both must pass before a pull request merges.
|
|
255
|
+
|
|
256
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
|
257
|
+
|
|
258
|
+
To release a new version, bump `Recorder::VERSION` and move the `[Unreleased]`
|
|
259
|
+
changelog entries under it in a pull request. Once that merges, tag the merge
|
|
260
|
+
commit and push the tag:
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
git fetch origin
|
|
264
|
+
git tag vX.Y.Z origin/master
|
|
265
|
+
git push origin vX.Y.Z
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The Release workflow then runs CI against the tagged commit, checks the tag
|
|
269
|
+
matches `Recorder::VERSION`, publishes the gem to [rubygems.org](https://rubygems.org)
|
|
270
|
+
through trusted publishing, and creates the GitHub release from the changelog.
|
|
60
271
|
|
|
61
272
|
## Contributing
|
|
62
273
|
|
|
@@ -64,11 +275,10 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jetroc
|
|
|
64
275
|
|
|
65
276
|
## Credits
|
|
66
277
|
|
|
67
|
-

|
|
68
279
|
|
|
69
|
-
Recorder is maintained by [JetRockets](https://www.jetrockets.
|
|
280
|
+
Recorder is maintained by [JetRockets](https://www.jetrockets.com).
|
|
70
281
|
|
|
71
282
|
## License
|
|
72
283
|
|
|
73
284
|
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/changeset.rb
CHANGED
|
@@ -33,7 +33,7 @@ module Recorder
|
|
|
33
33
|
@previous_version = item.dup
|
|
34
34
|
|
|
35
35
|
changes.each do |key, change|
|
|
36
|
-
@previous_version.
|
|
36
|
+
@previous_version.try("#{key}=", change[0]) if pair?(change)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
@previous_version
|
|
@@ -49,10 +49,16 @@ module Recorder
|
|
|
49
49
|
@next_version = item.dup
|
|
50
50
|
|
|
51
51
|
changes.each do |key, change|
|
|
52
|
-
@next_version.
|
|
52
|
+
@next_version.try("#{key}=", change[1]) if pair?(change)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
@next_version
|
|
56
56
|
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def pair?(change)
|
|
61
|
+
change.is_a?(Array) && change.size == 2
|
|
62
|
+
end
|
|
57
63
|
end
|
|
58
64
|
end
|
data/lib/recorder/manager.rb
CHANGED
data/lib/recorder/observer.rb
CHANGED
data/lib/recorder/revision.rb
CHANGED
|
@@ -18,8 +18,8 @@ module Recorder
|
|
|
18
18
|
)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
belongs_to :item, polymorphic: true, inverse_of: :revisions
|
|
22
|
-
belongs_to :user
|
|
21
|
+
belongs_to :item, polymorphic: true, inverse_of: :revisions, optional: true
|
|
22
|
+
belongs_to :user, optional: true
|
|
23
23
|
|
|
24
24
|
validates :item_type, presence: true
|
|
25
25
|
validates :event, presence: true
|
data/lib/recorder/tape/data.rb
CHANGED
|
@@ -23,8 +23,14 @@ module Recorder
|
|
|
23
23
|
{attributes: sanitize_attributes(item.attributes, options)}
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
def self.validate_changes_option!(callback)
|
|
27
|
+
return if callback.nil? || callback.is_a?(Proc) || callback.is_a?(Symbol) || callback.is_a?(String)
|
|
28
|
+
|
|
29
|
+
raise ArgumentError, "`changes:` expects a Proc or a method name, got #{callback.inspect}"
|
|
30
|
+
end
|
|
31
|
+
|
|
26
32
|
def changes_for(event, options)
|
|
27
|
-
changes = sanitize_attributes(item.saved_changes, options)
|
|
33
|
+
changes = sanitize_attributes(item.saved_changes, options).merge(custom_changes_for(event, options))
|
|
28
34
|
|
|
29
35
|
changes.present? ? {changes: changes} : {}
|
|
30
36
|
end
|
|
@@ -37,6 +43,17 @@ module Recorder
|
|
|
37
43
|
|
|
38
44
|
private
|
|
39
45
|
|
|
46
|
+
def custom_changes_for(event, options)
|
|
47
|
+
callback = options[:changes]
|
|
48
|
+
return {} unless callback
|
|
49
|
+
|
|
50
|
+
self.class.validate_changes_option!(callback)
|
|
51
|
+
|
|
52
|
+
changes = callback.is_a?(Proc) ? item.instance_exec(event, &callback) : item.send(callback, event)
|
|
53
|
+
|
|
54
|
+
changes ? Hash(changes).symbolize_keys : {}
|
|
55
|
+
end
|
|
56
|
+
|
|
40
57
|
def sanitize_attributes(attributes, options)
|
|
41
58
|
if options[:only].present?
|
|
42
59
|
only = wrap_options(options[:only])
|
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.4.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-22 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
|