hoardable 0.14.2 → 0.15.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: befa1333fe9f96fa12949d58483241b3ea576cd21503cfb47586b22dbaf36fe4
4
- data.tar.gz: '02390d38824b7fe05d5f8ffd9c8e0714d27da5b35d5fe4c2fee9e3f7775125fd'
3
+ metadata.gz: b2b34416224e686978b85cd78c0a80eae6e09f727a87c58060355671c6af8334
4
+ data.tar.gz: 401049a8d781e695fd691f1a9cf0c0ea67560c535efd773b738c40041ca80869
5
5
  SHA512:
6
- metadata.gz: 83d0cb32b0363b0a1387e041301c6ddf9d55e40a8aa3d1c1d6c40416bdfb3c90115a0a37af25238474b96c0a54d9816f06ad5747b8f5d279d6af5aec09602e49
7
- data.tar.gz: 2c8ca8c336cca19bc9f4e7199d6da3474df932415e6a481c1f6868df8b440e60820a68837bda8e137bd55922401950b646b0a8be94e4b0208854022befafff6e
6
+ metadata.gz: 3b39f34db9a87e2403b6a7529a035e0ccea464fbc6cda100b7069a2cc271e63112f0299dcae283e8b7c1d8f17e14892d58b9089e9e4b7fcc178cd94e808014a1
7
+ data.tar.gz: e2a8b1e9c9e5362711b2a5170a74d6316a115c290bf285e0ff60f3eed6937a4e4838000b2042a19bc79a97f3d7970882894bb519f244f6bac1eee1d1e529acac
data/.streerc ADDED
@@ -0,0 +1 @@
1
+ --print-width=100
data/.tool-versions CHANGED
@@ -1,2 +1,2 @@
1
- ruby 3.2.1
2
- postgres 14.4
1
+ ruby 3.3.0
2
+ postgres 16.1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## 0.15.0
2
+
3
+ - *Breaking Change* - Support for Ruby 2.7 and Rails 6.1 is dropped
4
+ - *Breaking Change* - The default scoping clause that controls the inherited table SQL construction
5
+ changes from a where clause using `tableoid`s to using `FROM ONLY`
6
+ - Fixes an issue for Rails 7.1 regarding accessing version table columns through aliased attributes
7
+ - Fixes an issue where `Hoardable::RichText` couldn’t be loaded if `ActionText::RichText` wasn’t yet
8
+ loaded
9
+ - Supports dumping `INHERITS (table_name)` options to `schema.rb` and ensures the inherited tables
10
+ are dumped after their parents
11
+
12
+ ## 0.14.3
13
+
14
+ - The migration template is updated to make the primary key on the versions table its actual primary key
15
+
1
16
  ## 0.14.2
2
17
 
3
18
  - Fixes an eager loading issue regarding `ActionText::EncryptedRichText`
data/Gemfile CHANGED
@@ -1,15 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source 'https://rubygems.org'
3
+ source "https://rubygems.org"
4
4
 
5
- gem 'benchmark-ips', '~> 2.10'
6
- gem 'debug', '~> 1.6'
7
- gem 'minitest', '~> 5.0'
8
- gem 'rails', '>= 6.1'
9
- gem 'rake', '~> 13.0'
10
- gem 'rubocop', '~> 1.21'
11
- gem 'rubocop-minitest', '~> 0.20'
12
- gem 'rubocop-rake', '~> 0.6'
13
- gem 'yard', '~> 0.9'
5
+ gem "debug"
6
+ if (rails_version = ENV["RAILS_VERSION"])
7
+ gem "rails", "~> #{rails_version}.0"
8
+ else
9
+ gem "rails"
10
+ end
11
+ gem "syntax_tree"
12
+ gem "typeprof"
14
13
 
15
14
  gemspec
data/README.md CHANGED
@@ -1,21 +1,23 @@
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.7+, Rails 6.1+, and PostgreSQL that allows for versioning
4
- and soft-deletion of records through the use of _uni-temporal inherited tables_.
3
+ Hoardable is an ActiveRecord extension for Ruby 3+, Rails 7+, and PostgreSQL that allows for
4
+ versioning 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 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".
6
+ [Temporal tables](https://en.wikipedia.org/wiki/Temporal_database) are a database design pattern
7
+ where each row of a table contains data along with one or more time ranges. In the case of this gem,
8
+ each database row has a time range that represents the row’s valid time range - hence
9
+ "uni-temporal".
9
10
 
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.
11
+ [Table inheritance](https://www.postgresql.org/docs/current/ddl-inherit.html) is a feature of
12
+ PostgreSQL that allows a table to inherit all columns of a parent table. The descendant table’s
13
+ schema will stay in sync with its parent. If a new column is added to or removed from the parent,
14
+ the schema change is reflected on its descendants.
14
15
 
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.
16
+ With these concepts combined, `hoardable` offers a model versioning and soft deletion system for
17
+ Rails. Versions of records are stored in separate, inherited tables along with their valid time
18
+ ranges and contextual data. Compared to other Rails-oriented versioning systems, this gem strives to
19
+ be more explicit and obvious on the lower database level, while still familiar and convenient to use
20
+ within Ruby on Rails.
19
21
 
20
22
  [👉 Documentation](https://www.rubydoc.info/gems/hoardable)
21
23
 
@@ -34,12 +36,10 @@ bin/rails g hoardable:install
34
36
  bin/rails db:migrate
35
37
  ```
36
38
 
37
- This will generate PostgreSQL functions, an enum and an initiailzer. It will also set
38
- `config.active_record.schema_format = :sql` in `application.rb` if you are using Rails < 7.
39
-
40
39
  ### Model Installation
41
40
 
42
- You must include `Hoardable::Model` into an ActiveRecord model that you would like to hoard versions of:
41
+ You must include `Hoardable::Model` into an ActiveRecord model that you would like to hoard versions
42
+ of:
43
43
 
44
44
  ```ruby
45
45
  class Post < ActiveRecord::Base
@@ -55,31 +55,33 @@ bin/rails g hoardable:migration Post
55
55
  bin/rails db:migrate
56
56
  ```
57
57
 
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:
58
+ By default, it will guess the foreign key type for the `_versions` table based on the primary key of
59
+ the model specified in the migration generator above. If you want/need to specify this explicitly,
60
+ you can do so:
60
61
 
61
62
  ```
62
63
  bin/rails g hoardable:migration Post --foreign-key-type uuid
63
64
  ```
64
65
 
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.
66
+ _*Note*:_ Creating an inherited table does not inherit the indexes from the parent table. If you
67
+ need to query versions often, you should add appropriate indexes to the `_versions` tables. See
68
+ [here](https://github.com/waymondo/hoardable/issues/30) for more info.
67
69
 
68
70
  ## Usage
69
71
 
70
72
  ### Overview
71
73
 
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:
74
+ Once you include `Hoardable::Model` into a model, it will dynamically generate a "Version" subclass
75
+ of that model. As we continue our example from above:
74
76
 
75
77
  ```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 #=> Post(id: integer, ..., hoardable_id: integer)
79
+ PostVersion #=> PostVersion(id: integer, ..., hoardable_id: integer, _data: jsonb, _during: tsrange, _event_uuid: uuid, _operation: enum)
78
80
  Post.version_class #=> same as `PostVersion`
79
81
  ```
80
82
 
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:
83
+ A `Post` now `has_many :versions`. With the default configuration, whenever an update or deletion of
84
+ a `post` occurs, a version is created:
83
85
 
84
86
  ```ruby
85
87
  post = Post.create!(title: "Title")
@@ -96,7 +98,7 @@ Post.find(post.id) # raises ActiveRecord::RecordNotFound
96
98
  Each `PostVersion` has access to the same attributes, relationships, and other model behavior that
97
99
  `Post` has, but as a read-only record:
98
100
 
99
- ``` ruby
101
+ ```ruby
100
102
  post.versions.last.update!(title: "Rewrite history") #=> raises ActiveRecord::ReadOnlyRecord
101
103
  ```
102
104
 
@@ -105,12 +107,15 @@ If you ever need to revert to a specific version, you can call `version.revert!`
105
107
  ```ruby
106
108
  post = Post.create!(title: "Title")
107
109
  post.update!(title: "Whoops")
108
- post.reload.versions.last.revert!
110
+ version = post.reload.versions.last
111
+ version.title # -> "Title"
112
+ version.revert!
109
113
  post.title # => "Title"
110
114
  ```
111
115
 
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.
116
+ If you would like to untrash a specific version of a record you deleted, you can call
117
+ `version.untrash!` on it. This will re-insert the model in the parent class’s table with the
118
+ original primary key.
114
119
 
115
120
  ```ruby
116
121
  post = Post.create!(title: "Title")
@@ -124,13 +129,24 @@ trashed_post.untrash!
124
129
  Post.find(post.id) # #<Post>
125
130
  ```
126
131
 
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.
132
+ _*Note*:_ You will notice above that both `posts` and `post_versions` pull from the same ID
133
+ sequence. This allows for uniquely identifying source records and versions when results are mixed
134
+ together. Both a source record and versions have an automatically managed `hoardable_id` that always
135
+ represents the primary key value of the original source record.
131
136
 
132
137
  ### Querying and Temporal Lookup
133
138
 
139
+ Including `Hoardable::Model` into your source model modifies its default scope to make sure you only
140
+ query the parent table:
141
+
142
+ ```ruby
143
+ Post.where(state: :draft).to_sql # => SELECT posts.* FROM ONLY posts WHERE posts.status = 'draft'
144
+ ```
145
+
146
+ _*Note*:_ If you are executing raw SQL, you will need to include the `ONLY` keyword you see above to
147
+ the select statement if you do not wish to return versions in the results. Learn more about table
148
+ inheritance in [the PostgreSQL documentation](https://www.postgresql.org/docs/current/ddl-inherit.html).
149
+
134
150
  Since a `PostVersion` is an `ActiveRecord` class, you can query them like another model resource:
135
151
 
136
152
  ```ruby
@@ -152,32 +168,34 @@ The source model class also has an `.at` method:
152
168
  Post.at(1.day.ago) # => [#<Post>, #<Post>]
153
169
  ```
154
170
 
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`.
171
+ This will return an ActiveRecord scoped query of all `Post` and `PostVersion` records that were
172
+ valid at that time, all cast as instances of `Post`.
157
173
 
158
- There is also an `at` method on `Hoardable` itself for more complex and experimental temporal resource
159
- querying. See [Relationships](#relationships) for more.
174
+ There is also an `at` method on `Hoardable` itself for more complex and experimental temporal
175
+ resource querying. See [Relationships](#relationships) for more.
160
176
 
161
- By default, `hoardable` will keep copies of records you have destroyed. You can query them specifically with:
177
+ By default, `hoardable` will keep copies of records you have destroyed. You can query them
178
+ specifically with:
162
179
 
163
180
  ```ruby
164
- PostVersion.trashed
165
- Post.version_class.trashed # <- same as above
181
+ PostVersion.trashed.where(user_id: user.id)
182
+ Post.version_class.trashed.where(user_id: user.id) # <- same as above
166
183
  ```
167
184
 
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.
185
+ _*Note*:_ A `Version` is not created upon initial source model creation. To accurately track the
186
+ beginning of the first temporal period, you will need to ensure the source model table has a
187
+ `created_at` timestamp column. If this is missing, an error will be raised.
171
188
 
172
189
  ### Tracking Contextual Data
173
190
 
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:
191
+ You’ll often want to track contextual data about the creation of a version. There are 2 options that
192
+ can be provided for tracking this:
176
193
 
177
- - `:whodunit` - an identifier for who is responsible for creating the version
194
+ - `:whodunit` - an identifier for who/what is responsible for creating the version
178
195
  - `:meta` - any other contextual information you’d like to store along with the version
179
196
 
180
- This information is stored in a `jsonb` column. Each key’s value can be in the format of your choosing.
197
+ This information is stored in a `jsonb` column. Each key’s value can be in the format of your
198
+ choosing.
181
199
 
182
200
  One convenient way to assign contextual data to these is by defining a proc in an initializer, i.e.:
183
201
 
@@ -186,22 +204,23 @@ One convenient way to assign contextual data to these is by defining a proc in a
186
204
  Hoardable.whodunit = -> { Current.user&.id }
187
205
 
188
206
  # somewhere in your app code
189
- Current.user = User.find(123)
190
- post.update!(status: 'live')
191
- post.reload.versions.last.hoardable_whodunit # => 123
207
+ Current.set(user: User.find(123)) do
208
+ post.update!(status: :live)
209
+ post.reload.versions.last.hoardable_whodunit # => 123
210
+ end
192
211
  ```
193
212
 
194
- You can also set this context manually as well:
213
+ You can also set these context values manually as well:
195
214
 
196
215
  ```ruby
197
- Hoardable.meta = { note: "reverting due to accidental deletion" }
216
+ Hoardable.meta = {note: "reverting due to accidental deletion"}
198
217
  post.update!(title: "We’re back!")
199
218
  Hoardable.meta = nil
200
219
  post.reload.versions.last.hoardable_meta['note'] # => "reverting due to accidental deletion"
201
220
  ```
202
221
 
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`:
222
+ A more useful pattern would be to use `Hoardable.with` to set the context around a block. For
223
+ example, you could have the following in your `ApplicationController`:
205
224
 
206
225
  ```ruby
207
226
  class ApplicationController < ActionController::Base
@@ -210,7 +229,7 @@ class ApplicationController < ActionController::Base
210
229
  private
211
230
 
212
231
  def use_hoardable_context
213
- Hoardable.with(whodunit: current_user.id, meta: { request_uuid: request.uuid }) do
232
+ Hoardable.with(whodunit: current_user.id, meta: {request_uuid: request.uuid}) do
214
233
  yield
215
234
  end
216
235
  # `Hoardable.whodunit` and `Hoardable.meta` are back to nil or their previously set values
@@ -219,9 +238,10 @@ end
219
238
  ```
220
239
 
221
240
  `hoardable` will also automatically capture the ActiveRecord
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:
241
+ [changes](https://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-changes) hash, the
242
+ `operation` that cause the version (`update` or `delete`), and it will also tag all versions created
243
+ in the same database transaction with a shared and unique `event_uuid` for that transaction. These
244
+ values are available as:
225
245
 
226
246
  ```ruby
227
247
  version.changes
@@ -231,12 +251,12 @@ version.hoardable_event_uuid
231
251
 
232
252
  ### Model Callbacks
233
253
 
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.
254
+ Sometimes you might want to do something with a version after it gets inserted to the database. You
255
+ can access it in `after_versioned` callbacks on the source record as `hoardable_version`. These
256
+ happen within `ActiveRecord`’s `.save`, which is enclosed in an ActiveRecord transaction.
237
257
 
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.
258
+ There are also `after_reverted` and `after_untrashed` callbacks available as well, which are called
259
+ on the source record after a version is reverted or untrashed.
240
260
 
241
261
  ```ruby
242
262
  class User
@@ -275,14 +295,15 @@ Hoardable.save_trash # => default true
275
295
 
276
296
  `Hoardable.version_updates` globally controls whether versions get created on record updates.
277
297
 
278
- `Hoardable.save_trash` globally controls whether to create versions upon record deletion. When this is set to
279
- `false`, all versions of a record will be deleted when the record is destroyed.
298
+ `Hoardable.save_trash` globally controls whether to create versions upon source record deletion.
299
+ When this is set to `false`, all versions of a source record will be deleted when the record is
300
+ destroyed.
280
301
 
281
302
  If you would like to temporarily set a config setting, you can use `Hoardable.with`:
282
303
 
283
304
  ```ruby
284
305
  Hoardable.with(enabled: false) do
285
- post.update!(title: 'unimportant change to create version for')
306
+ post.update!(title: "replace title without creating a version")
286
307
  end
287
308
  ```
288
309
 
@@ -304,17 +325,17 @@ Comment.with_hoardable_config(version_updates: true) do
304
325
  end
305
326
  ```
306
327
 
307
- If a model-level option exists, it will use that. Otherwise, it will fall back to the global `Hoardable`
308
- config.
328
+ If a model-level option exists, it will use that. Otherwise, it will fall back to the global
329
+ `Hoardable` config.
309
330
 
310
331
  ## Relationships
311
332
 
312
333
  ### Belongs To Trashable
313
334
 
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`:
335
+ Sometimes you’ll have a record that belongs to a parent record that you’ll trash. Now the child
336
+ record’s foreign key will point to the non-existent trashed version of the parent. If you would like
337
+ to have `belongs_to` resolve to the trashed parent model in this case, you can give it the option of
338
+ `trashable: true`:
318
339
 
319
340
  ```ruby
320
341
  class Comment
@@ -335,21 +356,21 @@ class Post
335
356
  has_many :comments, hoardable: true
336
357
  end
337
358
 
338
- def Comment
359
+ class Comment
339
360
  include Hoardable::Model
340
361
  end
341
362
 
342
- post = Post.create!(title: 'Title')
343
- comment1 = post.comments.create!(body: 'Comment')
344
- comment2 = post.comments.create!(body: 'Comment')
363
+ post = Post.create!(title: "Title")
364
+ comment1 = post.comments.create!(body: "Comment")
365
+ comment2 = post.comments.create!(body: "Comment")
345
366
  datetime = DateTime.current
346
367
  comment2.destroy!
347
- post.update!(title: 'New Title')
368
+ post.update!(title: "New Title")
348
369
  post_id = post.id # 1
349
370
 
350
371
  Hoardable.at(datetime) do
351
372
  post = Post.find(post_id)
352
- post.title # => 'Title'
373
+ post.title # => "Title"
353
374
  post.comments.size # => 2
354
375
  post.id # => 2
355
376
  post.version? # => true
@@ -357,26 +378,26 @@ Hoardable.at(datetime) do
357
378
  end
358
379
  ```
359
380
 
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.
381
+ You’ll notice above that the `post` within the `#at` block is actually a temporal `post_version`,
382
+ since it has been subsequently updated and has a different id - it is reified as a `post` for the
383
+ purposes of your business logic (serialization, rendering views, exporting, etc). Don’t fret - you
384
+ will not be able to commit any updates to the version, even though it is masquerading as a `Post`
385
+ because a database trigger won’t allow it.
366
386
 
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`.
387
+ If you are ever unsure if a Hoardable record is a source record or a version, you can be sure by
388
+ calling `version?` on it. If you want to get the true original source record ID, you can call
389
+ `hoardable_id`.
369
390
 
370
- _Note:_ `Hoardable.at` is still very experimental and is potentially not very performant for querying large
371
- data sets.
391
+ _*Note*:_ `Hoardable.at` is still very experimental and is potentially not performant for querying
392
+ large data sets.
372
393
 
373
394
  ### Cascading Untrashing
374
395
 
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:
396
+ Sometimes you’ll trash something that `has_many :children, dependent: :destroy` and if you untrash
397
+ the parent record, you’ll want to also untrash the children. Whenever a hoardable version is created
398
+ in a database transaction, it will create or re-use a unique event UUID for the current database
399
+ transaction and tag all versions created with it. That way, when you `untrash!` a record, you could
400
+ find and `untrash!` records that were trashed with it:
380
401
 
381
402
  ```ruby
382
403
  class Post < ActiveRecord::Base
@@ -395,20 +416,22 @@ end
395
416
 
396
417
  ### Action Text
397
418
 
398
- Hoardable provides support for ActiveRecord models with `has_rich_text`. First, you must create a temporal
399
- table for `ActionText::RichText`:
419
+ Hoardable provides support for ActiveRecord models with `has_rich_text`. First, you must create a
420
+ temporal table for `ActionText::RichText`:
400
421
 
401
422
  ```
402
423
  bin/rails g hoardable:migration ActionText::RichText
403
424
  bin/rails db:migrate
404
425
  ```
405
426
 
406
- Then in your model include `Hoardable::Model` and provide the `hoardable: true` keyword to `has_rich_text`:
427
+ Then in your model include `Hoardable::Model` and provide the `hoardable: true` keyword to
428
+ `has_rich_text`:
407
429
 
408
430
  ```ruby
409
431
  class Post < ActiveRecord::Base
410
432
  include Hoardable::Model # or `Hoardable::Associations` if you don't need `PostVersion`
411
433
  has_rich_text :content, hoardable: true
434
+ # alternately, this could be `has_hoardable_rich_text :content`
412
435
  end
413
436
  ```
414
437
 
@@ -431,62 +454,64 @@ end
431
454
 
432
455
  Rails uses a method called
433
456
  [`disable_referential_integrity`](https://github.com/rails/rails/blob/06e9fbd954ab113108a7982357553fdef285bff1/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb#L7)
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
457
+ when inserting fixtures into the database. This disables PostgreSQL triggers, which Hoardable relies
458
+ on for assigning `hoardable_id` from the primary key’s value. If you would still like to use
459
+ fixtures, you must specify the primary key’s value and `hoardable_id` to the same identifier value
460
+ in the fixture. This is not an issue with fixture replacement libraries like `factory_bot` or
438
461
  [`world_factory`](https://github.com/FutureProofRetail/world_factory) however.
439
462
 
440
463
  ## Gem Comparison
441
464
 
442
465
  #### [`paper_trail`](https://github.com/paper-trail-gem/paper_trail)
443
466
 
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.
467
+ `paper_trail` is maybe the most popular and fully featured gem in this space. It works for other
468
+ database types than PostgeSQL. Bby default it stores all versions of all versioned models in a
469
+ single `versions` table. It stores changes in a `text`, `json`, or `jsonb` column. In order to
470
+ efficiently query the `versions` table, a `jsonb` column should be used, which can take up a lot of
471
+ space to index. Unless you customize your configuration, all `versions` for all models types are in
472
+ the same table which is inefficient if you are only interested in querying versions of a single
473
+ model. By contrast, `hoardable` stores versions in smaller, isolated, inherited tables with the same
474
+ database columns as their parents, which are more efficient for querying as well as auditing for
475
+ truncating and dropping. The concept of a temporal timeframe does not exist for a single version
476
+ since there is only a `created_at` timestamp.
453
477
 
454
478
  #### [`audited`](https://github.com/collectiveidea/audited)
455
479
 
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.
480
+ `audited` works in a similar manner as `paper_trail`. It stores all versions for all model types in
481
+ a single table, you must opt into using `jsonb` as the column type to store "changes", in case you
482
+ want to query them, and there is no concept of a temporal timeframe for a single version. It makes
483
+ opinionated decisions about contextual data requirements and stores them as top level data types on
484
+ the `audited` table.
460
485
 
461
486
  #### [`discard`](https://github.com/jhawthorn/discard)
462
487
 
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.
488
+ `discard` only covers soft-deletion. The act of "soft deleting" a record is only captured through
489
+ the time-stamping of a `discarded_at` column on the records table. There is no other capturing of
490
+ the event that caused the soft deletion unless you implement it yourself. Once the "discarded"
491
+ record is restored, the previous "discarded" awareness is lost. Since "discarded" records exist in
492
+ the same table as "undiscarded" records, you must explicitly omit the discarded records from queries
493
+ across your app to keep them from leaking in.
469
494
 
470
495
  #### [`paranoia`](https://github.com/rubysherpas/paranoia)
471
496
 
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.
497
+ `paranoia` also only covers soft-deletion. In their README, they recommend using `discard` instead
498
+ of `paranoia` because of the fact they override ActiveRecord’s `delete` and `destroy` methods.
499
+ `hoardable` employs callbacks to create trashed versions instead of overriding methods. Otherwise,
500
+ `paranoia` works similarly to `discard` in that it keeps deleted records in the same table and tags
501
+ them with a `deleted_at` timestamp. No other information about the soft-deletion event is stored.
477
502
 
478
503
  #### [`logidze`](https://github.com/palkan/logidze)
479
504
 
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.
505
+ `logidze` is an interesting versioning alternative that leverages the power of PostgreSQL triggers.
506
+ Instead of storing the previous versions or changes in a separate table, it stores them in a
507
+ proprietary JSON format directly on the database row of the record itself. If does not support soft
508
+ deletion.
483
509
 
484
510
  ## Contributing
485
511
 
486
- This gem still quite new and very open to feedback.
487
-
488
512
  Bug reports and pull requests are welcome on GitHub at https://github.com/waymondo/hoardable.
489
513
 
490
514
  ## License
491
515
 
492
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
516
+ The gem is available as open source under the terms of the [MIT
517
+ License](https://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,16 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler/gem_tasks'
4
- require 'rake/testtask'
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+ require "syntax_tree/rake_tasks"
5
6
 
6
7
  Rake::TestTask.new(:test) do |t|
7
- t.libs << 'test'
8
- t.libs << 'lib'
9
- t.test_files = FileList['test/**/test_*.rb']
8
+ t.libs << "test"
9
+ t.libs << "lib"
10
+ t.test_files = FileList["test/**/test_*.rb"]
10
11
  end
11
12
 
12
- require 'rubocop/rake_task'
13
+ SOURCE_FILES = %w[test/**/*.rb lib/**/*.rb Rakefile Gemfile bin/console hoardable.gemspec]
13
14
 
14
- RuboCop::RakeTask.new
15
+ SyntaxTree::Rake::CheckTask.new(:check) do |t|
16
+ t.source_files = SOURCE_FILES
17
+ t.print_width = 100
18
+ end
19
+
20
+ SyntaxTree::Rake::WriteTask.new(:write) do |t|
21
+ t.source_files = SOURCE_FILES
22
+ t.print_width = 100
23
+ end
24
+
25
+ task :typeprof do
26
+ `typeprof lib/hoardable.rb`
27
+ end
15
28
 
16
- task default: %i[test rubocop]
29
+ task default: %i[check test]
30
+ task pre_commit: %i[write typeprof]