hoardable 0.12.9 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +142 -143
- data/lib/hoardable/database_client.rb +1 -1
- data/lib/hoardable/engine.rb +1 -1
- data/lib/hoardable/scopes.rb +1 -1
- data/lib/hoardable/version.rb +1 -1
- data/lib/hoardable/version_model.rb +1 -1
- data/sig/hoardable.rbs +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28edc85fff69a3851a5d2c653368d732bd9e6fe8ad5544bfa7f81147c6007aab
|
4
|
+
data.tar.gz: 2d7c3abc9eedaddf3180b9f368c90fa25addaffa6c54bc76bf82880b54e7fc72
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76dfd695ad62332f876aad4d25e8d1b220970bcd8f2d2eeb69a91e4a441300fc356984dc5ef25be9dd01e745a305448a7f55b439199ef475932910bde02e8f9d
|
7
|
+
data.tar.gz: '081e30627e731ed8d9b4f42536d9e67926bb37348075efa10d2ee3f5ee3eb37476ba3b0ecf1c6ac6fb4dfa7fa700e4d02fd10c3c3f537ef7ba433e605c9d1039'
|
data/README.md
CHANGED
@@ -1,23 +1,21 @@
|
|
1
1
|
# Hoardable ![gem version](https://img.shields.io/gem/v/hoardable?style=flat-square)
|
2
2
|
|
3
|
-
Hoardable is an ActiveRecord extension for Ruby 2.6+, Rails 6.1+, and PostgreSQL that allows for
|
4
|
-
|
3
|
+
Hoardable is an ActiveRecord extension for Ruby 2.6+, Rails 6.1+, and PostgreSQL that allows for versioning
|
4
|
+
and soft-deletion of records through the use of _uni-temporal inherited tables_.
|
5
5
|
|
6
|
-
[Temporal tables](https://en.wikipedia.org/wiki/Temporal_database) are a database design pattern
|
7
|
-
|
8
|
-
|
9
|
-
"uni-temporal".
|
6
|
+
[Temporal tables](https://en.wikipedia.org/wiki/Temporal_database) are a database design pattern where each
|
7
|
+
row of a table contains data along with one or more time ranges. In the case of this gem, each database row
|
8
|
+
has a time range that represents the row’s valid time range - hence "uni-temporal".
|
10
9
|
|
11
|
-
[Table inheritance](https://www.postgresql.org/docs/14/ddl-inherit.html) is a feature of PostgreSQL
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
[Table inheritance](https://www.postgresql.org/docs/14/ddl-inherit.html) is a feature of PostgreSQL that
|
11
|
+
allows a table to inherit all columns of a parent table. The descendant table’s schema will stay in sync with
|
12
|
+
its parent. If a new column is added to or removed from the parent, the schema change is reflected on its
|
13
|
+
descendants.
|
15
14
|
|
16
|
-
With these concepts combined, `hoardable` offers a simple and effective model versioning system for
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
within Ruby on Rails.
|
15
|
+
With these concepts combined, `hoardable` offers a simple and effective model versioning system for Rails.
|
16
|
+
Versions of records are stored in separate, inherited tables along with their valid time ranges and
|
17
|
+
contextual data. Compared to other Rails-oriented versioning systems, this gem strives to be more explicit
|
18
|
+
and obvious on the lower database level, while still familiar and convenient to use within Ruby on Rails.
|
21
19
|
|
22
20
|
[👉 Documentation](https://www.rubydoc.info/gems/hoardable)
|
23
21
|
|
@@ -36,19 +34,16 @@ bin/rails g hoardable:install
|
|
36
34
|
bin/rails db:migrate
|
37
35
|
```
|
38
36
|
|
39
|
-
This will generate PostgreSQL functions, an initiailzer, and set `config.active_record.schema_format
|
40
|
-
|
37
|
+
This will generate PostgreSQL functions, an initiailzer, and set `config.active_record.schema_format = :sql`
|
38
|
+
in `application.rb`.
|
41
39
|
|
42
40
|
### Model Installation
|
43
41
|
|
44
|
-
You must include `Hoardable::Model` into an ActiveRecord model that you would like to hoard versions
|
45
|
-
of:
|
42
|
+
You must include `Hoardable::Model` into an ActiveRecord model that you would like to hoard versions of:
|
46
43
|
|
47
44
|
```ruby
|
48
45
|
class Post < ActiveRecord::Base
|
49
46
|
include Hoardable::Model
|
50
|
-
belongs_to :user
|
51
|
-
has_many :comments, dependent: :destroy
|
52
47
|
...
|
53
48
|
end
|
54
49
|
```
|
@@ -60,34 +55,31 @@ bin/rails g hoardable:migration Post
|
|
60
55
|
bin/rails db:migrate
|
61
56
|
```
|
62
57
|
|
63
|
-
By default, it will
|
64
|
-
|
65
|
-
explicitly, you can do so:
|
58
|
+
By default, it will guess the foreign key type for the `_versions` table based on the primary key of the
|
59
|
+
model specified in the migration generator above. If you want/need to specify this explicitly, you can do so:
|
66
60
|
|
67
61
|
```
|
68
62
|
bin/rails g hoardable:migration Post --foreign-key-type uuid
|
69
63
|
```
|
70
64
|
|
71
|
-
_Note:_ Creating an inherited table does not
|
72
|
-
|
65
|
+
_Note:_ Creating an inherited table does not inherit the indexes from the parent table. If you need to query
|
66
|
+
versions often, you should add appropriate indexes to the `_versions` tables.
|
73
67
|
|
74
68
|
## Usage
|
75
69
|
|
76
70
|
### Overview
|
77
71
|
|
78
|
-
Once you include `Hoardable::Model` into a model, it will dynamically generate a "Version" subclass
|
79
|
-
|
72
|
+
Once you include `Hoardable::Model` into a model, it will dynamically generate a "Version" subclass of that
|
73
|
+
model. As we continue our example from above:
|
80
74
|
|
81
|
-
```
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
>> PostVersion
|
86
|
-
=> PostVersion(id: integer, body: text, user_id: integer, created_at: datetime, hoardable_id: integer, _data: jsonb, _during: tsrange)
|
75
|
+
```ruby
|
76
|
+
Post #=> Post(id: integer, created_at: datetime, updated_at: datetime, hoardable_id: integer)
|
77
|
+
PostVersion #=> PostVersion(id: integer, created_at: datetime, updated_at: datetime, hoardable_id: integer, _data: jsonb, _during: tsrange, _event_uuid: uuid, _operation: enum)
|
78
|
+
Post.version_class #=> same as `PostVersion`
|
87
79
|
```
|
88
80
|
|
89
|
-
A `Post` now `has_many :versions`. With the default configuration, whenever an update and deletion
|
90
|
-
|
81
|
+
A `Post` now `has_many :versions`. With the default configuration, whenever an update and deletion of a
|
82
|
+
`Post` occurs, a version is created:
|
91
83
|
|
92
84
|
```ruby
|
93
85
|
post = Post.create!(title: "Title")
|
@@ -102,7 +94,11 @@ Post.find(post.id) # raises ActiveRecord::RecordNotFound
|
|
102
94
|
```
|
103
95
|
|
104
96
|
Each `PostVersion` has access to the same attributes, relationships, and other model behavior that
|
105
|
-
`Post` has, but as a read-only record
|
97
|
+
`Post` has, but as a read-only record:
|
98
|
+
|
99
|
+
``` ruby
|
100
|
+
post.versions.last.update!(title: "Rewrite history") #=> raises ActiveRecord::ReadOnlyRecord
|
101
|
+
```
|
106
102
|
|
107
103
|
If you ever need to revert to a specific version, you can call `version.revert!` on it.
|
108
104
|
|
@@ -113,8 +109,8 @@ post.reload.versions.last.revert!
|
|
113
109
|
post.title # => "Title"
|
114
110
|
```
|
115
111
|
|
116
|
-
If you would like to untrash a specific version, you can call `version.untrash!` on
|
117
|
-
re-insert the model in the parent class’s table with the original primary key.
|
112
|
+
If you would like to untrash a specific version of a record you deleted, you can call `version.untrash!` on
|
113
|
+
it. This will re-insert the model in the parent class’s table with the original primary key.
|
118
114
|
|
119
115
|
```ruby
|
120
116
|
post = Post.create!(title: "Title")
|
@@ -128,12 +124,17 @@ trashed_post.untrash!
|
|
128
124
|
Post.find(post.id) # #<Post>
|
129
125
|
```
|
130
126
|
|
127
|
+
_Note:_ You will notice above that both `posts` and `post_versions` pull from the same ID sequence. This
|
128
|
+
allows for uniquely identifying source records and versions when results are mixed together. Both a source
|
129
|
+
record and versions have an automatically managed `hoardable_id` that always represents the primary key value
|
130
|
+
of the original source record.
|
131
|
+
|
131
132
|
### Querying and Temporal Lookup
|
132
133
|
|
133
134
|
Since a `PostVersion` is an `ActiveRecord` class, you can query them like another model resource:
|
134
135
|
|
135
136
|
```ruby
|
136
|
-
post.versions.where(
|
137
|
+
post.versions.where(state: :draft)
|
137
138
|
```
|
138
139
|
|
139
140
|
If you want to look-up the version of a record at a specific time, you can use the `.at` method:
|
@@ -141,7 +142,8 @@ If you want to look-up the version of a record at a specific time, you can use t
|
|
141
142
|
```ruby
|
142
143
|
post.at(1.day.ago) # => #<PostVersion>
|
143
144
|
# or you can use the scope on the version model class
|
144
|
-
|
145
|
+
post.versions.at(1.day.ago) # => #<PostVersion>
|
146
|
+
PostVersion.at(1.day.ago).find_by(hoardable_id: post.id) # => same as above
|
145
147
|
```
|
146
148
|
|
147
149
|
The source model class also has an `.at` method:
|
@@ -150,35 +152,32 @@ The source model class also has an `.at` method:
|
|
150
152
|
Post.at(1.day.ago) # => [#<Post>, #<Post>]
|
151
153
|
```
|
152
154
|
|
153
|
-
This will return an ActiveRecord scoped query of all `Post` and `PostVersion` records that were
|
154
|
-
|
155
|
+
This will return an ActiveRecord scoped query of all `Post` and `PostVersion` records that were valid at that
|
156
|
+
time, all cast as instances of `Post`.
|
155
157
|
|
156
|
-
There is also an `at` method on `Hoardable` itself for more complex temporal resource
|
157
|
-
[Relationships](#relationships) for more.
|
158
|
+
There is also an `at` method on `Hoardable` itself for more complex and experimental temporal resource
|
159
|
+
querying. See [Relationships](#relationships) for more.
|
158
160
|
|
159
|
-
By default, `hoardable` will keep copies of records you have destroyed. You can query them
|
160
|
-
specifically with:
|
161
|
+
By default, `hoardable` will keep copies of records you have destroyed. You can query them specifically with:
|
161
162
|
|
162
163
|
```ruby
|
163
164
|
PostVersion.trashed
|
164
|
-
Post.version_class.trashed # <- same
|
165
|
+
Post.version_class.trashed # <- same as above
|
165
166
|
```
|
166
167
|
|
167
|
-
_Note:_ A `Version` is not created upon initial parent model creation. To accurately track the
|
168
|
-
|
169
|
-
|
168
|
+
_Note:_ A `Version` is not created upon initial parent model creation. To accurately track the beginning of
|
169
|
+
the first temporal period, you will need to ensure the source model table has a `created_at` timestamp
|
170
|
+
column. If this is missing, an error will be raised.
|
170
171
|
|
171
172
|
### Tracking Contextual Data
|
172
173
|
|
173
|
-
You’ll often want to track contextual data about the creation of a version. There are
|
174
|
-
|
174
|
+
You’ll often want to track contextual data about the creation of a version. There are 2 options that can be
|
175
|
+
provided for tracking contextual information:
|
175
176
|
|
176
177
|
- `:whodunit` - an identifier for who is responsible for creating the version
|
177
|
-
- `:note` - a description regarding the versioning
|
178
178
|
- `:meta` - any other contextual information you’d like to store along with the version
|
179
179
|
|
180
|
-
This information is stored in a `jsonb` column. Each key’s value can be in the format of your
|
181
|
-
choosing.
|
180
|
+
This information is stored in a `jsonb` column. Each key’s value can be in the format of your choosing.
|
182
181
|
|
183
182
|
One convenient way to assign contextual data to these is by defining a proc in an initializer, i.e.:
|
184
183
|
|
@@ -192,17 +191,17 @@ post.update!(status: 'live')
|
|
192
191
|
post.reload.versions.last.hoardable_whodunit # => 123
|
193
192
|
```
|
194
193
|
|
195
|
-
You can also set this context manually as well
|
194
|
+
You can also set this context manually as well:
|
196
195
|
|
197
196
|
```ruby
|
198
|
-
Hoardable.
|
197
|
+
Hoardable.meta = { note: "reverting due to accidental deletion" }
|
199
198
|
post.update!(title: "We’re back!")
|
200
|
-
Hoardable.
|
201
|
-
post.reload.versions.last.
|
199
|
+
Hoardable.meta = nil
|
200
|
+
post.reload.versions.last.hoardable_meta['note'] # => "reverting due to accidental deletion"
|
202
201
|
```
|
203
202
|
|
204
|
-
A more useful pattern is to use `Hoardable.with` to set the context around a block.
|
205
|
-
|
203
|
+
A more useful pattern however is to use `Hoardable.with` to set the context around a block. For example, you
|
204
|
+
could have the following in your `ApplicationController`:
|
206
205
|
|
207
206
|
```ruby
|
208
207
|
class ApplicationController < ActionController::Base
|
@@ -220,9 +219,9 @@ end
|
|
220
219
|
```
|
221
220
|
|
222
221
|
`hoardable` will also automatically capture the ActiveRecord
|
223
|
-
[changes](https://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-changes) hash, the
|
224
|
-
|
225
|
-
|
222
|
+
[changes](https://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-changes) hash, the `operation`
|
223
|
+
that cause the version (`update` or `delete`), and it will also tag all versions created in the same database
|
224
|
+
transaction with a shared and unique `event_uuid` for that transaction. These are available as:
|
226
225
|
|
227
226
|
```ruby
|
228
227
|
version.changes
|
@@ -232,12 +231,12 @@ version.hoardable_event_uuid
|
|
232
231
|
|
233
232
|
### Model Callbacks
|
234
233
|
|
235
|
-
Sometimes you might want to do something with a version after it gets inserted to the database. You
|
236
|
-
|
237
|
-
|
234
|
+
Sometimes you might want to do something with a version after it gets inserted to the database. You can
|
235
|
+
access it in `after_versioned` callbacks on the source record as `hoardable_version`. These happen within
|
236
|
+
`ActiveRecord`’s `.save`, which is enclosed in an ActiveRecord transaction.
|
238
237
|
|
239
|
-
There are also `after_reverted` and `after_untrashed` callbacks available as well, which are called
|
240
|
-
|
238
|
+
There are also `after_reverted` and `after_untrashed` callbacks available as well, which are called on the
|
239
|
+
source record after a version is reverted or untrashed.
|
241
240
|
|
242
241
|
```ruby
|
243
242
|
class User
|
@@ -272,11 +271,11 @@ Hoardable.version_updates # => default true
|
|
272
271
|
Hoardable.save_trash # => default true
|
273
272
|
```
|
274
273
|
|
275
|
-
`Hoardable.enabled` controls whether versions will be ever be created.
|
274
|
+
`Hoardable.enabled` globally controls whether versions will be ever be created.
|
276
275
|
|
277
|
-
`Hoardable.version_updates` controls whether versions get created on record updates.
|
276
|
+
`Hoardable.version_updates` globally controls whether versions get created on record updates.
|
278
277
|
|
279
|
-
`Hoardable.save_trash` controls whether to create versions upon record deletion. When this is set to
|
278
|
+
`Hoardable.save_trash` globally controls whether to create versions upon record deletion. When this is set to
|
280
279
|
`false`, all versions of a record will be deleted when the record is destroyed.
|
281
280
|
|
282
281
|
If you would like to temporarily set a config setting, you can use `Hoardable.with`:
|
@@ -287,7 +286,7 @@ Hoardable.with(enabled: false) do
|
|
287
286
|
end
|
288
287
|
```
|
289
288
|
|
290
|
-
You can also configure these
|
289
|
+
You can also configure these settings per `ActiveRecord` class using `hoardable_config`:
|
291
290
|
|
292
291
|
```ruby
|
293
292
|
class Comment < ActiveRecord::Base
|
@@ -305,18 +304,17 @@ Comment.with_hoardable_config(version_updates: true) do
|
|
305
304
|
end
|
306
305
|
```
|
307
306
|
|
308
|
-
If a model-level option exists, it will use that. Otherwise, it will fall back to the global
|
309
|
-
|
307
|
+
If a model-level option exists, it will use that. Otherwise, it will fall back to the global `Hoardable`
|
308
|
+
config.
|
310
309
|
|
311
|
-
|
310
|
+
## Relationships
|
312
311
|
|
313
|
-
|
314
|
-
with `Hoardable` considerations.
|
312
|
+
### Belongs To Trashable
|
315
313
|
|
316
|
-
Sometimes you’ll have a record that belongs to a parent record that you’ll trash. Now the child
|
317
|
-
|
318
|
-
|
319
|
-
|
314
|
+
Sometimes you’ll have a record that belongs to a parent record that you’ll trash. Now the child record’s
|
315
|
+
foreign key will point to the non-existent trashed version of the parent. If you would like to have
|
316
|
+
`belongs_to` resolve to the trashed parent model in this case, you can give it the option of `trashable:
|
317
|
+
true`:
|
320
318
|
|
321
319
|
```ruby
|
322
320
|
class Comment
|
@@ -325,10 +323,11 @@ class Comment
|
|
325
323
|
end
|
326
324
|
```
|
327
325
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
326
|
+
### Hoardable Has Many & Has One
|
327
|
+
|
328
|
+
Sometimes you'll have a Hoardable record that `has_one` or `has_many` other Hoardable records and you will
|
329
|
+
want to know the state of both the parent record and the children at a cetain point in time. You accomplish
|
330
|
+
this by adding `hoardable: true` to the `has_many` relationship and using the `Hoardable.at` method:
|
332
331
|
|
333
332
|
```ruby
|
334
333
|
class Post
|
@@ -358,22 +357,26 @@ Hoardable.at(datetime) do
|
|
358
357
|
end
|
359
358
|
```
|
360
359
|
|
361
|
-
There are some additional details to point out above. Firstly, it is important to note that the
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
360
|
+
There are some additional details to point out above. Firstly, it is important to note that the final
|
361
|
+
`post.id` yields a different value than the originally created `Post`. This is because the `post` within the
|
362
|
+
`#at` block is actually a temporal version, since it has been subsequently updated, but is reified as a
|
363
|
+
`Post` for the purposes of your business logic (serialization, rendering views, exporting, etc). Don’t fret -
|
364
|
+
you will not be able to commit any updates to the version, even though it is masquerading as a `Post` because
|
365
|
+
a database trigger won’t allow you to.
|
367
366
|
|
368
|
-
If you are ever unsure if a Hoardable record is a
|
369
|
-
|
370
|
-
`hoardable_id`.
|
367
|
+
If you are ever unsure if a Hoardable record is a source record or a version, you can be sure by calling
|
368
|
+
`version?` on it. If you want to get the true original source record ID, you can call `hoardable_id`.
|
371
369
|
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
370
|
+
_Note:_ `Hoardable.at` is still very experimental and is potentially not very performant for querying large
|
371
|
+
data sets.
|
372
|
+
|
373
|
+
### Cascading Untrashing
|
374
|
+
|
375
|
+
Sometimes you’ll trash something that `has_many :children, dependent: :destroy` and if you untrash the parent
|
376
|
+
record, you’ll want to also untrash the children. Whenever a hoardable version is created in a database
|
377
|
+
transaction, it will create or re-use a unique event UUID for the current database transaction and tag all
|
378
|
+
versions created with it. That way, when you `untrash!` a record, you could find and `untrash!` records that
|
379
|
+
were trashed with it:
|
377
380
|
|
378
381
|
```ruby
|
379
382
|
class Post < ActiveRecord::Base
|
@@ -392,18 +395,17 @@ end
|
|
392
395
|
|
393
396
|
### Action Text
|
394
397
|
|
395
|
-
Hoardable provides support for ActiveRecord models with `has_rich_text`. First, you must create a
|
396
|
-
|
398
|
+
Hoardable provides support for ActiveRecord models with `has_rich_text`. First, you must create a temporal
|
399
|
+
table for `ActionText::RichText`:
|
397
400
|
|
398
401
|
```
|
399
402
|
bin/rails g hoardable:migration ActionText::RichText
|
400
403
|
bin/rails db:migrate
|
401
404
|
```
|
402
405
|
|
403
|
-
Then in your model
|
404
|
-
`has_rich_text`:
|
406
|
+
Then in your model include `Hoardable::Model` and provide the `hoardable: true` keyword to `has_rich_text`:
|
405
407
|
|
406
|
-
```
|
408
|
+
```ruby
|
407
409
|
class Post < ActiveRecord::Base
|
408
410
|
include Hoardable::Model # or `Hoardable::Associations` if you don't need `PostVersion`
|
409
411
|
has_rich_text :content, hoardable: true
|
@@ -412,7 +414,7 @@ end
|
|
412
414
|
|
413
415
|
Now the `rich_text_content` relationship will be managed as a Hoardable `has_one` relationship:
|
414
416
|
|
415
|
-
```
|
417
|
+
```ruby
|
416
418
|
post = Post.create!(content: '<div>Hello World</div>')
|
417
419
|
datetime = DateTime.current
|
418
420
|
post.update!(content: '<div>Goodbye Cruel World</div>')
|
@@ -423,64 +425,61 @@ Hoardable.at(datetime) do
|
|
423
425
|
end
|
424
426
|
```
|
425
427
|
|
426
|
-
|
428
|
+
## Known Gotchas
|
427
429
|
|
428
|
-
|
430
|
+
### Rails Fixtures
|
429
431
|
|
430
432
|
Rails uses a method called
|
431
433
|
[`disable_referential_integrity`](https://github.com/rails/rails/blob/06e9fbd954ab113108a7982357553fdef285bff1/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb#L7)
|
432
|
-
when inserting fixtures into the database. This disables PostgreSQL triggers, which Hoardable relies
|
433
|
-
|
434
|
-
|
435
|
-
|
434
|
+
when inserting fixtures into the database. This disables PostgreSQL triggers, which Hoardable relies on for
|
435
|
+
assigning `hoardable_id` from the primary key’s value. If you would still like to use fixtures, you must
|
436
|
+
specify the primary key’s value and `hoardable_id` to the same identifier value in the fixture. This is not
|
437
|
+
an issue with fixture replacement libraries like `factory_bot` or
|
436
438
|
[`world_factory`](https://github.com/FutureProofRetail/world_factory) however.
|
437
439
|
|
438
440
|
## Gem Comparison
|
439
441
|
|
440
442
|
#### [`paper_trail`](https://github.com/paper-trail-gem/paper_trail)
|
441
443
|
|
442
|
-
`paper_trail` is maybe the most popular and fully featured gem in this space. It works for other
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
in
|
448
|
-
|
449
|
-
|
450
|
-
for
|
451
|
-
version since there is only a `created_at` timestamp.
|
444
|
+
`paper_trail` is maybe the most popular and fully featured gem in this space. It works for other database
|
445
|
+
types than PostgeSQL and (by default) stores all versions of all versioned models in a single `versions`
|
446
|
+
table. It stores changes in a `text`, `json`, or `jsonb` column. In order to efficiently query the `versions`
|
447
|
+
table, a `jsonb` column should be used, which takes up a lot of space to index. Unless you customize your
|
448
|
+
configuration, all `versions` for all models types are in the same table which is inefficient if you are only
|
449
|
+
interested in querying versions of a single model. By contrast, `hoardable` stores versions in smaller,
|
450
|
+
isolated and inherited tables with the same database columns as their parents, which are more efficient for
|
451
|
+
querying as well as auditing for truncating and dropping. The concept of a `temporal` time-frame does not
|
452
|
+
exist for a single version since there is only a `created_at` timestamp.
|
452
453
|
|
453
454
|
#### [`audited`](https://github.com/collectiveidea/audited)
|
454
455
|
|
455
|
-
`audited` works in a similar manner as `paper_trail`. It stores all versions for all model types in
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
types on the `audited` table.
|
456
|
+
`audited` works in a similar manner as `paper_trail`. It stores all versions for all model types in a single
|
457
|
+
table, you must opt into using `jsonb` as the column type to store "changes", in case you want to query them,
|
458
|
+
and there is no concept of a `temporal` time-frame for a single version. It makes opinionated decisions about
|
459
|
+
contextual data requirements and stores them as top level data types on the `audited` table.
|
460
460
|
|
461
461
|
#### [`discard`](https://github.com/jhawthorn/discard)
|
462
462
|
|
463
|
-
`discard` only covers soft-deletion. The act of "soft deleting" a record is only captured through
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
463
|
+
`discard` only covers soft-deletion. The act of "soft deleting" a record is only captured through the
|
464
|
+
time-stamping of a `discarded_at` column on the records table; there is no other capturing of the event that
|
465
|
+
caused the soft deletion unless you implement it yourself. Once the "discarded" record is restored, the
|
466
|
+
previous "discarded" awareness is lost. Since "discarded" records exist in the same table as "undiscarded"
|
467
|
+
records, you must explicitly omit the discarded records from queries across your app to keep them from
|
468
|
+
leaking in.
|
469
469
|
|
470
470
|
#### [`paranoia`](https://github.com/rubysherpas/paranoia)
|
471
471
|
|
472
|
-
`paranoia` also only covers soft-deletion. In their README, they recommend using `discard` instead
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
472
|
+
`paranoia` also only covers soft-deletion. In their README, they recommend using `discard` instead of
|
473
|
+
`paranoia` because of the fact they override ActiveRecord’s `delete` and `destroy` methods. `hoardable`
|
474
|
+
employs callbacks to create trashed versions instead of overriding methods. Otherwise, `paranoia` works
|
475
|
+
similarly to `discard` in that it keeps deleted records in the same table and tags them with a `deleted_at`
|
476
|
+
timestamp. No other information about the soft-deletion event is stored.
|
477
477
|
|
478
478
|
#### [`logidze`](https://github.com/palkan/logidze)
|
479
479
|
|
480
|
-
`logidze` is an interesting versioning alternative that leverages the power of PostgreSQL triggers.
|
481
|
-
|
482
|
-
|
483
|
-
deletion.
|
480
|
+
`logidze` is an interesting versioning alternative that leverages the power of PostgreSQL triggers. Instead
|
481
|
+
of storing the previous versions or changes in a separate table, it stores them in a proprietary JSON format
|
482
|
+
directly on the database row of the record itself. If does not support soft deletion.
|
484
483
|
|
485
484
|
## Contributing
|
486
485
|
|
@@ -55,7 +55,7 @@ module Hoardable
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def source_attributes_without_primary_key
|
58
|
-
source_record.
|
58
|
+
source_record.attributes.without(source_primary_key, *generated_column_names).merge(
|
59
59
|
source_record.class.select(refreshable_column_names).find(source_record.id).slice(refreshable_column_names)
|
60
60
|
)
|
61
61
|
end
|
data/lib/hoardable/engine.rb
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
module Hoardable
|
5
5
|
# Symbols for use with setting contextual data, when creating versions. See
|
6
6
|
# {file:README.md#tracking-contextual-data README} for more.
|
7
|
-
DATA_KEYS = %i[meta whodunit
|
7
|
+
DATA_KEYS = %i[meta whodunit event_uuid].freeze
|
8
8
|
|
9
9
|
# Symbols for use with setting {Hoardable} configuration. See {file:README.md#configuration
|
10
10
|
# README} for more.
|
data/lib/hoardable/scopes.rb
CHANGED
@@ -73,7 +73,7 @@ module Hoardable
|
|
73
73
|
private
|
74
74
|
|
75
75
|
def tableoid
|
76
|
-
connection.execute("SELECT oid FROM pg_class WHERE relname = '#{table_name}'")[0]['oid']
|
76
|
+
@tableoid ||= connection.execute("SELECT oid FROM pg_class WHERE relname = '#{table_name}'")[0]['oid']
|
77
77
|
end
|
78
78
|
end
|
79
79
|
end
|
data/lib/hoardable/version.rb
CHANGED
@@ -130,7 +130,7 @@ module Hoardable
|
|
130
130
|
end
|
131
131
|
|
132
132
|
def hoardable_source_attributes
|
133
|
-
|
133
|
+
attributes.without(
|
134
134
|
(self.class.column_names - self.class.superclass.column_names) +
|
135
135
|
(SUPPORTS_VIRTUAL_COLUMNS ? self.class.columns.select(&:virtual?).map(&:name) : [])
|
136
136
|
)
|
data/sig/hoardable.rbs
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Hoardable
|
2
2
|
VERSION: String
|
3
|
-
DATA_KEYS: [:meta, :whodunit, :
|
3
|
+
DATA_KEYS: [:meta, :whodunit, :event_uuid]
|
4
4
|
CONFIG_KEYS: [:enabled, :version_updates, :save_trash]
|
5
5
|
VERSION_CLASS_SUFFIX: String
|
6
6
|
VERSION_TABLE_SUFFIX: String
|
@@ -57,7 +57,7 @@ module Hoardable
|
|
57
57
|
def source_attributes_without_primary_key: -> untyped
|
58
58
|
def initialize_temporal_range: -> Range
|
59
59
|
def initialize_hoardable_data: -> untyped
|
60
|
-
def assign_hoardable_context: (:event_uuid | :meta | :
|
60
|
+
def assign_hoardable_context: (:event_uuid | :meta | :whodunit key) -> nil
|
61
61
|
def unset_hoardable_version_and_event_uuid: -> nil
|
62
62
|
def previous_temporal_tsrange_end: -> untyped
|
63
63
|
def hoardable_source_epoch: -> untyped
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hoardable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- justin talbott
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|