hoardable 0.14.2 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
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]