hoardable 0.9.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +0 -54
- data/Gemfile +1 -0
- data/README.md +64 -26
- data/lib/generators/hoardable/install_generator.rb +33 -0
- data/lib/generators/hoardable/templates/functions.rb.erb +16 -0
- data/lib/generators/hoardable/templates/migration.rb.erb +20 -15
- data/lib/generators/hoardable/templates/migration_6.rb.erb +20 -15
- data/lib/hoardable/associations.rb +6 -48
- data/lib/hoardable/attachment.rb +16 -0
- data/lib/hoardable/belongs_to.rb +37 -0
- data/lib/hoardable/database_client.rb +2 -18
- data/lib/hoardable/encrypted_rich_text.rb +8 -0
- data/lib/hoardable/{hoardable.rb → engine.rb} +22 -1
- data/lib/hoardable/error.rb +12 -0
- data/lib/hoardable/finder_methods.rb +25 -0
- data/lib/hoardable/has_many.rb +45 -0
- data/lib/hoardable/has_one.rb +28 -0
- data/lib/hoardable/has_one_attached.rb +33 -0
- data/lib/hoardable/has_rich_text.rb +23 -0
- data/lib/hoardable/model.rb +10 -4
- data/lib/hoardable/rich_text.rb +8 -0
- data/lib/hoardable/scopes.rb +6 -2
- data/lib/hoardable/source_model.rb +13 -14
- data/lib/hoardable/version.rb +1 -1
- data/lib/hoardable.rb +9 -2
- data/sig/hoardable.rbs +37 -12
- metadata +14 -4
- data/lib/generators/hoardable/initializer_generator.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d9f9ea3a4559d3a4515357eabc0c7ee05526ee1308a4e6eef111f2d6207aef6
|
4
|
+
data.tar.gz: be0011cb1ea9d3fcdcd5d1a4f4951d7185371cc1d05c91dfd27ea163b59620b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20ebc8d7ec2924e43e6bbd806e7aaf08350653b695dc4aef16de28359ceb95d8de16da4c19790e8b1212bc8d8d202caeb3614e05b326977bd470defe48e4b731
|
7
|
+
data.tar.gz: fc3fa92ae0ab25bee4122456b889f86eec75b0eee4efba742dced8737d7bc9ab3a99e63e63839f49089183900df8ed54a315a4561aad634c0e2ea3fa56aa8732
|
data/CHANGELOG.md
CHANGED
@@ -2,57 +2,3 @@
|
|
2
2
|
|
3
3
|
- Stability is coming.
|
4
4
|
|
5
|
-
## [0.9.0] - 2022-10-02
|
6
|
-
|
7
|
-
- **Breaking Change** - `Hoardable.return_everything` was removed in favor of the newly added
|
8
|
-
`Hoardable.at`.
|
9
|
-
|
10
|
-
## [0.8.0] - 2022-10-01
|
11
|
-
|
12
|
-
- **Breaking Change** - Due to the performance benefit of using `insert` for database injection of
|
13
|
-
versions, and a personal opinion that only an `after_versioned` hook might be needed, the
|
14
|
-
`before_versioned` and `around_versioned` ActiveRecord hooks are removed.
|
15
|
-
|
16
|
-
- **Breaking Change** - Another side effect of the performance benefit gained by using `insert` is
|
17
|
-
that a source model will need to be reloaded before a call to `versions` on it can access the
|
18
|
-
latest version after an `update` on the source record.
|
19
|
-
|
20
|
-
- **Breaking Change** - Previously the inherited `_versions` tables did not have a unique index on
|
21
|
-
the ID column, though it still pulled from the same sequence as the parent table. Prior to version
|
22
|
-
0.4.0 though, it was possible to have multiple trashed versions with the same ID. Adding unique
|
23
|
-
indexes to version tables prior to version 0.4.0 could result in issues.
|
24
|
-
|
25
|
-
## [0.7.0] - 2022-09-29
|
26
|
-
|
27
|
-
- **Breaking Change** - Continuing along with the change below, the `foreign_key` on the `_versions`
|
28
|
-
tables is now changed to `hoardable_source_id` instead of the i18n model name dervied foreign key.
|
29
|
-
The intent is to never leave room for conflict of foreign keys for existing relationships. This
|
30
|
-
can be resolved by renaming the foreign key columns from their i18n model name derived column
|
31
|
-
names to `hoardable_source_id`, i.e. `rename_column :post_versions, :post_id, :hoardable_source_id`.
|
32
|
-
|
33
|
-
## [0.6.0] - 2022-09-28
|
34
|
-
|
35
|
-
- **Breaking Change** - Previously, a source model would `has_many :versions` with an inverse
|
36
|
-
relationship based on the i18n interpreted name of the source model. Now it simply `has_many
|
37
|
-
:versions, inverse_of :hoardable_source` to not potentially conflict with previously existing
|
38
|
-
relationships.
|
39
|
-
|
40
|
-
## [0.5.0] - 2022-09-25
|
41
|
-
|
42
|
-
- **Breaking Change** - Untrashing a version will now insert a version for the untrash event with
|
43
|
-
it's own temporal timespan. This simplifies the ability to query versions temporarily for when
|
44
|
-
they were trashed or not. This changes, but corrects, temporal query results using `.at`.
|
45
|
-
|
46
|
-
- **Breaking Change** - Because of the above, a new operation enum value of "insert" was added. If
|
47
|
-
you already have the `hoardable_operation` enum in your PostgreSQL schema, you can add it by
|
48
|
-
executing the following SQL in a new migration: `ALTER TYPE hoardable_operation ADD VALUE
|
49
|
-
'insert';`.
|
50
|
-
|
51
|
-
## [0.4.0] - 2022-09-24
|
52
|
-
|
53
|
-
- **Breaking Change** - Trashed versions now pull from the same postgres sequenced used by the
|
54
|
-
source model’s table.
|
55
|
-
|
56
|
-
## [0.1.0] - 2022-07-23
|
57
|
-
|
58
|
-
- Initial release
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -29,14 +29,19 @@ Add this line to your application's Gemfile:
|
|
29
29
|
gem 'hoardable'
|
30
30
|
```
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
If you would like to generate an initializer with the global [configuration](#configuration) options:
|
32
|
+
Run `bundle install`, and then run:
|
35
33
|
|
36
34
|
```
|
37
|
-
rails g hoardable:
|
35
|
+
bin/rails g hoardable:install
|
36
|
+
bin/rails db:migrate
|
38
37
|
```
|
39
38
|
|
39
|
+
This will generate a PostgreSQL function and an initiailzer.
|
40
|
+
|
41
|
+
_Note:_ It is recommended to set `config.active_record.schema_format = :sql` in `application.rb`, so
|
42
|
+
that the function and triggers in the migrations that prevent updates to the versions table get
|
43
|
+
captured in your schema.
|
44
|
+
|
40
45
|
### Model Installation
|
41
46
|
|
42
47
|
You must include `Hoardable::Model` into an ActiveRecord model that you would like to hoard versions
|
@@ -66,10 +71,6 @@ explicitly, you can do so:
|
|
66
71
|
bin/rails g hoardable:migration Post --foreign-key-type uuid
|
67
72
|
```
|
68
73
|
|
69
|
-
_Note:_ If you are on Rails 6.1, you might want to set `config.active_record.schema_format = :sql`
|
70
|
-
in `application.rb`, so that the enum type is captured in your schema dump. This is not required in
|
71
|
-
Rails 7.
|
72
|
-
|
73
74
|
_Note:_ Creating an inherited table does not copy over the indexes from the parent table. If you
|
74
75
|
need to query versions often, you should add appropriate indexes to the `_versions` tables.
|
75
76
|
|
@@ -164,12 +165,11 @@ specifically with:
|
|
164
165
|
```ruby
|
165
166
|
PostVersion.trashed
|
166
167
|
Post.version_class.trashed # <- same thing as above
|
167
|
-
PostVersion.trashed.first.trashed? # <- true
|
168
168
|
```
|
169
169
|
|
170
170
|
_Note:_ A `Version` is not created upon initial parent model creation. To accurately track the
|
171
171
|
beginning of the first temporal period, you will need to ensure the source model table has a
|
172
|
-
`created_at` timestamp column.
|
172
|
+
`created_at` timestamp column. If this is missing, an error will be raised.
|
173
173
|
|
174
174
|
### Tracking Contextual Data
|
175
175
|
|
@@ -318,25 +318,25 @@ with `Hoardable` considerations.
|
|
318
318
|
|
319
319
|
Sometimes you’ll have a record that belongs to a parent record that you’ll trash. Now the child
|
320
320
|
record’s foreign key will point to the non-existent trashed version of the parent. If you would like
|
321
|
-
to have `belongs_to` resolve to the trashed parent model in this case, you can
|
322
|
-
`
|
321
|
+
to have `belongs_to` resolve to the trashed parent model in this case, you can give it the option of
|
322
|
+
`trashable: true`:
|
323
323
|
|
324
324
|
```ruby
|
325
325
|
class Comment
|
326
326
|
include Hoardable::Associations # <- This includes is not required if this model already includes `Hoardable::Model`
|
327
|
-
|
327
|
+
belongs_to :post, trashable: true
|
328
328
|
end
|
329
329
|
```
|
330
330
|
|
331
331
|
Sometimes you'll have a Hoardable record that `has_many` other Hoardable records and you will want
|
332
332
|
to know the state of both the parent record and the children at a cetain point in time. You
|
333
|
-
accomplish this by
|
334
|
-
method:
|
333
|
+
accomplish this by adding `hoardable: true` to the `has_many` relationship and using the
|
334
|
+
`Hoardable.at` method:
|
335
335
|
|
336
336
|
```ruby
|
337
337
|
class Post
|
338
338
|
include Hoardable::Model
|
339
|
-
|
339
|
+
has_many :comments, hoardable: true
|
340
340
|
end
|
341
341
|
|
342
342
|
def Comment
|
@@ -352,7 +352,7 @@ post.update!(title: 'New Title')
|
|
352
352
|
post_id = post.id # 1
|
353
353
|
|
354
354
|
Hoardable.at(datetime) do
|
355
|
-
post = Post.
|
355
|
+
post = Post.find(post_id)
|
356
356
|
post.title # => 'Title'
|
357
357
|
post.comments.size # => 2
|
358
358
|
post.id # => 2
|
@@ -370,11 +370,9 @@ version, even though it is masquerading as a `Post`.
|
|
370
370
|
|
371
371
|
If you are ever unsure if a Hoardable record is a "source" or a "version", you can be sure by
|
372
372
|
calling `version?` on it. If you want to get the true original source record ID, you can call
|
373
|
-
`hoardable_source_id`.
|
374
|
-
class, you can always find the relevant source or temporal version record using just the original
|
375
|
-
source record’s id.
|
373
|
+
`hoardable_source_id`.
|
376
374
|
|
377
|
-
Sometimes you’ll trash something that `
|
375
|
+
Sometimes you’ll trash something that `has_many :children, dependent: :destroy` and want
|
378
376
|
to untrash everything in a similar dependent manner. Whenever a hoardable version is created in a
|
379
377
|
database transaction, it will create or re-use a unique event UUID for that transaction and tag all
|
380
378
|
versions created with it. That way, when you `untrash!` a record, you can find and `untrash!`
|
@@ -383,7 +381,7 @@ records that were trashed with it:
|
|
383
381
|
```ruby
|
384
382
|
class Post < ActiveRecord::Base
|
385
383
|
include Hoardable::Model
|
386
|
-
|
384
|
+
has_many :comments, hoardable: true, dependent: :destroy # `Comment` also includes `Hoardable::Model`
|
387
385
|
|
388
386
|
after_untrashed do
|
389
387
|
Comment
|
@@ -395,9 +393,42 @@ class Post < ActiveRecord::Base
|
|
395
393
|
end
|
396
394
|
```
|
397
395
|
|
396
|
+
## ActionText
|
397
|
+
|
398
|
+
Hoardable provides support for ActiveRecord models with `has_rich_text`. First, you must create a
|
399
|
+
temporal table for `ActionText::RichText`:
|
400
|
+
|
401
|
+
```
|
402
|
+
bin/rails g hoardable:migration ActionText::RichText
|
403
|
+
bin/rails db:migrate
|
404
|
+
```
|
405
|
+
|
406
|
+
Then in your model, include `Hoardable::Model` and provide the `hoardable: true` keyword to
|
407
|
+
`has_rich_text`:
|
408
|
+
|
409
|
+
``` ruby
|
410
|
+
class Post < ActiveRecord::Base
|
411
|
+
include Hoardable::Model # or `Hoardable::Associations` if you don't need `PostVersion`
|
412
|
+
has_rich_text :content, hoardable: true
|
413
|
+
end
|
414
|
+
```
|
415
|
+
|
416
|
+
Now the `rich_text_content` relationship will be managed as a Hoardable `has_one` relationship:
|
417
|
+
|
418
|
+
``` ruby
|
419
|
+
post = Post.create!(content: '<div>Hello World</div>')
|
420
|
+
datetime = DateTime.current
|
421
|
+
post.update!(content: '<div>Goodbye Cruel World</div>')
|
422
|
+
post.content.versions.size # => 1
|
423
|
+
assert_equal post.content.to_plain_text, 'Goodbye Cruel World'
|
424
|
+
Hoardable.at(datetime) do
|
425
|
+
assert_equal post.content.to_plain_text, 'Hello World'
|
426
|
+
end
|
427
|
+
```
|
428
|
+
|
398
429
|
## Gem Comparison
|
399
430
|
|
400
|
-
|
431
|
+
#### [`paper_trail`](https://github.com/paper-trail-gem/paper_trail)
|
401
432
|
|
402
433
|
`paper_trail` is maybe the most popular and fully featured gem in this space. It works for other
|
403
434
|
database types than PostgeSQL and (by default) stores all versions of all versioned models in a
|
@@ -410,7 +441,7 @@ same database columns as their parents, which are more efficient for querying as
|
|
410
441
|
for truncating and dropping. The concept of a `temporal` time-frame does not exist for a single
|
411
442
|
version since there is only a `created_at` timestamp.
|
412
443
|
|
413
|
-
|
444
|
+
#### [`audited`](https://github.com/collectiveidea/audited)
|
414
445
|
|
415
446
|
`audited` works in a similar manner as `paper_trail`. It stores all versions for all model types in
|
416
447
|
a single table, you must opt into using `jsonb` as the column type to store "changes", in case you
|
@@ -418,7 +449,7 @@ want to query them, and there is no concept of a `temporal` time-frame for a sin
|
|
418
449
|
makes opinionated decisions about contextual data requirements and stores them as top level data
|
419
450
|
types on the `audited` table.
|
420
451
|
|
421
|
-
|
452
|
+
#### [`discard`](https://github.com/jhawthorn/discard)
|
422
453
|
|
423
454
|
`discard` only covers soft-deletion. The act of "soft deleting" a record is only captured through
|
424
455
|
the time-stamping of a `discarded_at` column on the records table; there is no other capturing of
|
@@ -427,7 +458,7 @@ record is restored, the previous "discarded" awareness is lost. Since "discarded
|
|
427
458
|
the same table as "undiscarded" records, you must explicitly omit the discarded records from queries
|
428
459
|
across your app to keep them from leaking in.
|
429
460
|
|
430
|
-
|
461
|
+
#### [`paranoia`](https://github.com/rubysherpas/paranoia)
|
431
462
|
|
432
463
|
`paranoia` also only covers soft-deletion. In their README, they recommend using `discard` instead
|
433
464
|
of `paranoia` because of the fact they override ActiveRecord’s `delete` and `destroy` methods.
|
@@ -435,6 +466,13 @@ of `paranoia` because of the fact they override ActiveRecord’s `delete` and `d
|
|
435
466
|
`paranoia` works similarly to `discard` in that it keeps deleted records in the same table and tags
|
436
467
|
them with a `deleted_at` timestamp. No other information about the soft-deletion event is stored.
|
437
468
|
|
469
|
+
#### [`logidze`](https://github.com/palkan/logidze)
|
470
|
+
|
471
|
+
`logidze` is an interesting versioning alternative that leverages the power of PostgreSQL triggers.
|
472
|
+
Instead of storing the previous versions or changes in a separate table, it stores them in a
|
473
|
+
proprietary JSON format directly on the database row of the record itself. If does not support soft
|
474
|
+
deletion.
|
475
|
+
|
438
476
|
## Contributing
|
439
477
|
|
440
478
|
This gem still quite new and very open to feedback.
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/generators'
|
4
|
+
|
5
|
+
module Hoardable
|
6
|
+
# Generates an initializer file for {Hoardable} configuration and a migration with a PostgreSQL
|
7
|
+
# function.
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
10
|
+
include Rails::Generators::Migration
|
11
|
+
|
12
|
+
def create_initializer_file
|
13
|
+
create_file(
|
14
|
+
'config/initializers/hoardable.rb',
|
15
|
+
<<~TEXT
|
16
|
+
# Hoardable configuration defaults are below. Learn more at https://github.com/waymondo/hoardable#configuration
|
17
|
+
#
|
18
|
+
# Hoardable.enabled = true
|
19
|
+
# Hoardable.version_updates = true
|
20
|
+
# Hoardable.save_trash = true
|
21
|
+
TEXT
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_migration_file
|
26
|
+
migration_template 'functions.rb.erb', 'db/migrate/install_hoardable.rb'
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.next_migration_number(dir)
|
30
|
+
::ActiveRecord::Generators::Base.next_migration_number(dir)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class InstallHoardable < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
4
|
+
def up
|
5
|
+
execute(
|
6
|
+
<<~SQL
|
7
|
+
CREATE OR REPLACE FUNCTION hoardable_version_prevent_update() RETURNS trigger
|
8
|
+
LANGUAGE plpgsql AS
|
9
|
+
$$BEGIN
|
10
|
+
RAISE EXCEPTION 'updating a version is not allowed';
|
11
|
+
RETURN NEW;
|
12
|
+
END;$$;
|
13
|
+
SQL
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class Create<%= class_name.singularize %>Versions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
3
|
+
class Create<%= class_name.singularize.delete(':') %>Versions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
4
4
|
def change
|
5
5
|
create_enum :hoardable_operation, %w[update delete insert]
|
6
6
|
create_table :<%= singularized_table_name %>_versions, id: false, options: 'INHERITS (<%= table_name %>)' do |t|
|
@@ -10,20 +10,25 @@ class Create<%= class_name.singularize %>Versions < ActiveRecord::Migration[<%=
|
|
10
10
|
t.enum :_operation, enum_type: 'hoardable_operation', null: false, index: true
|
11
11
|
t.<%= foreign_key_type %> :hoardable_source_id, null: false, index: true
|
12
12
|
end
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
13
|
+
reversible do |dir|
|
14
|
+
dir.up do
|
15
|
+
execute(
|
16
|
+
<<~SQL
|
17
|
+
CREATE TRIGGER <%= singularized_table_name %>_versions_prevent_update
|
18
|
+
BEFORE UPDATE ON <%= singularized_table_name %>_versions FOR EACH ROW
|
19
|
+
EXECUTE PROCEDURE hoardable_version_prevent_update();
|
20
|
+
SQL
|
21
|
+
)
|
22
|
+
end
|
23
|
+
dir.down do
|
24
|
+
execute(
|
25
|
+
<<~SQL
|
26
|
+
DROP TRIGGER <%= singularized_table_name %>_versions_prevent_update
|
27
|
+
ON <%= singularized_table_name %>_versions;
|
28
|
+
SQL
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
27
32
|
add_index(:<%= singularized_table_name %>_versions, :id, unique: true)
|
28
33
|
add_index(
|
29
34
|
:<%= singularized_table_name %>_versions,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class Create<%= class_name.singularize %>Versions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
3
|
+
class Create<%= class_name.singularize.delete(':') %>Versions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
4
4
|
def change
|
5
5
|
reversible do |dir|
|
6
6
|
dir.up do
|
@@ -25,20 +25,25 @@ class Create<%= class_name.singularize %>Versions < ActiveRecord::Migration[<%=
|
|
25
25
|
t.column :_operation, :hoardable_operation, null: false, index: true
|
26
26
|
t.<%= foreign_key_type %> :hoardable_source_id, null: false, index: true
|
27
27
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
28
|
+
reversible do |dir|
|
29
|
+
dir.up do
|
30
|
+
execute(
|
31
|
+
<<~SQL
|
32
|
+
CREATE TRIGGER <%= singularized_table_name %>_versions_prevent_update
|
33
|
+
BEFORE UPDATE ON <%= singularized_table_name %>_versions FOR EACH ROW
|
34
|
+
EXECUTE PROCEDURE hoardable_version_prevent_update();
|
35
|
+
SQL
|
36
|
+
)
|
37
|
+
end
|
38
|
+
dir.down do
|
39
|
+
execute(
|
40
|
+
<<~SQL
|
41
|
+
DROP TRIGGER <%= singularized_table_name %>_versions_prevent_update
|
42
|
+
ON <%= singularized_table_name %>_versions;
|
43
|
+
SQL
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
42
47
|
add_index(:<%= singularized_table_name %>_versions, :id, unique: true)
|
43
48
|
add_index(
|
44
49
|
:<%= singularized_table_name %>_versions,
|
@@ -7,54 +7,12 @@ module Hoardable
|
|
7
7
|
module Associations
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def hoardable_scope
|
20
|
-
if Hoardable.instance_variable_get('@at') &&
|
21
|
-
(hoardable_source_id = @association.owner.hoardable_source_id)
|
22
|
-
@association.scope.rewhere(@association.reflection.foreign_key => hoardable_source_id)
|
23
|
-
else
|
24
|
-
@association.scope
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
private_constant :HasManyScope
|
29
|
-
|
30
|
-
class_methods do
|
31
|
-
# A wrapper for +ActiveRecord+’s +belongs_to+ that allows for falling back to the most recent
|
32
|
-
# trashed +version+, in the case that the related source has been trashed.
|
33
|
-
def belongs_to_trashable(name, scope = nil, **options)
|
34
|
-
belongs_to(name, scope, **options)
|
35
|
-
|
36
|
-
trashable_relationship_name = "trashable_#{name}"
|
37
|
-
|
38
|
-
define_method(trashable_relationship_name) do
|
39
|
-
source_reflection = self.class.reflections[name.to_s]
|
40
|
-
version_class = source_reflection.version_class
|
41
|
-
version_class.trashed.only_most_recent.find_by(
|
42
|
-
hoardable_source_id: source_reflection.foreign_key
|
43
|
-
)
|
44
|
-
end
|
45
|
-
|
46
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
47
|
-
def #{name}
|
48
|
-
super || #{trashable_relationship_name}
|
49
|
-
end
|
50
|
-
RUBY
|
51
|
-
end
|
52
|
-
|
53
|
-
# A wrapper for +ActiveRecord+’s +has_many+ that allows for finding temporal versions of a
|
54
|
-
# record cast as instances of the {SourceModel}, when doing a {Hoardable#at} query.
|
55
|
-
def has_many_hoardable(name, scope = nil, **options)
|
56
|
-
has_many(name, scope, **options) { include HasManyScope }
|
57
|
-
end
|
10
|
+
included do
|
11
|
+
include HasMany
|
12
|
+
include HasOne
|
13
|
+
include BelongsTo
|
14
|
+
include HasRichText
|
15
|
+
include HasOneAttached
|
58
16
|
end
|
59
17
|
end
|
60
18
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hoardable
|
4
|
+
# A {Hoardable} subclass of {ActiveStorage::Attachment}
|
5
|
+
class Attachment < ActiveStorage::Attachment
|
6
|
+
include Model
|
7
|
+
|
8
|
+
class CreateOne < ActiveStorage::Attached::Changes::CreateOne
|
9
|
+
private
|
10
|
+
|
11
|
+
def build_attachment
|
12
|
+
Attachment.new(record: record, name: name, blob: blob)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hoardable
|
4
|
+
# Provides awareness of trashed source records to +belongs_to+ relationships.
|
5
|
+
module BelongsTo
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
def belongs_to(*args)
|
10
|
+
options = args.extract_options!
|
11
|
+
trashable = options.delete(:trashable)
|
12
|
+
super(*args, **options)
|
13
|
+
return unless trashable
|
14
|
+
|
15
|
+
hoardable_override_belongs_to(args.first)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def hoardable_override_belongs_to(name)
|
21
|
+
define_method("trashed_#{name}") do
|
22
|
+
source_reflection = reflections[name.to_s]
|
23
|
+
source_reflection.version_class.trashed.only_most_recent.find_by(
|
24
|
+
hoardable_source_id: source_reflection.foreign_key
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
29
|
+
def #{name}
|
30
|
+
super || trashed_#{name}
|
31
|
+
end
|
32
|
+
RUBY
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
private_constant :BelongsTo
|
37
|
+
end
|
@@ -73,25 +73,9 @@ module Hoardable
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def hoardable_source_epoch
|
76
|
-
if source_record.class.column_names.include?('created_at')
|
77
|
-
source_record.created_at
|
78
|
-
else
|
79
|
-
maybe_warn_about_missing_created_at_column
|
80
|
-
Time.at(0).utc
|
81
|
-
end
|
82
|
-
end
|
76
|
+
return source_record.created_at if source_record.class.column_names.include?('created_at')
|
83
77
|
|
84
|
-
|
85
|
-
return unless source_record.class.hoardable_config[:warn_on_missing_created_at_column]
|
86
|
-
|
87
|
-
source_table_name = source_record.class.table_name
|
88
|
-
Hoardable.logger.info(
|
89
|
-
<<~LOG
|
90
|
-
'#{source_table_name}' does not have a 'created_at' column, so the first version’s temporal period
|
91
|
-
will begin at the unix epoch instead. Add a 'created_at' column to '#{source_table_name}'
|
92
|
-
or set 'Hoardable.warn_on_missing_created_at_column = false' to disable this message.
|
93
|
-
LOG
|
94
|
-
)
|
78
|
+
raise CreatedAtColumnMissingError, source_record.class.table_name
|
95
79
|
end
|
96
80
|
end
|
97
81
|
private_constant :DatabaseClient
|
@@ -8,7 +8,7 @@ module Hoardable
|
|
8
8
|
|
9
9
|
# Symbols for use with setting {Hoardable} configuration. See {file:README.md#configuration
|
10
10
|
# README} for more.
|
11
|
-
CONFIG_KEYS = %i[enabled version_updates save_trash
|
11
|
+
CONFIG_KEYS = %i[enabled version_updates save_trash].freeze
|
12
12
|
|
13
13
|
VERSION_CLASS_SUFFIX = 'Version'
|
14
14
|
private_constant :VERSION_CLASS_SUFFIX
|
@@ -34,6 +34,9 @@ module Hoardable
|
|
34
34
|
end.freeze
|
35
35
|
private_constant :HOARDABLE_VERSION_UPDATES
|
36
36
|
|
37
|
+
SUPPORTS_ENCRYPTED_ACTION_TEXT = ActiveRecord.version >= ::Gem::Version.new('7.0')
|
38
|
+
private_constant :SUPPORTS_ENCRYPTED_ACTION_TEXT
|
39
|
+
|
37
40
|
@context = {}
|
38
41
|
@config = CONFIG_KEYS.to_h do |key|
|
39
42
|
[key, true]
|
@@ -91,4 +94,22 @@ module Hoardable
|
|
91
94
|
@logger ||= ActiveSupport::TaggedLogging.new(Logger.new($stdout))
|
92
95
|
end
|
93
96
|
end
|
97
|
+
|
98
|
+
# A +Rails+ engine for providing support for +ActionText+
|
99
|
+
class Engine < ::Rails::Engine
|
100
|
+
isolate_namespace Hoardable
|
101
|
+
|
102
|
+
initializer 'hoardable.action_text' do
|
103
|
+
ActiveSupport.on_load(:action_text_rich_text) do
|
104
|
+
require_relative 'rich_text'
|
105
|
+
require_relative 'encrypted_rich_text' if SUPPORTS_ENCRYPTED_ACTION_TEXT
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
initializer 'hoardable.active_storage' do
|
110
|
+
ActiveSupport.on_load(:active_storage_attachment) do
|
111
|
+
require_relative 'attachment'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
94
115
|
end
|
data/lib/hoardable/error.rb
CHANGED
@@ -3,4 +3,16 @@
|
|
3
3
|
module Hoardable
|
4
4
|
# A subclass of +StandardError+ for general use within {Hoardable}.
|
5
5
|
class Error < StandardError; end
|
6
|
+
|
7
|
+
# An error to be raised when 'created_at' columns are missing for {Hoardable::Model}s.
|
8
|
+
class CreatedAtColumnMissingError < Error
|
9
|
+
def initialize(source_table_name)
|
10
|
+
super(
|
11
|
+
<<~LOG
|
12
|
+
'#{source_table_name}' does not have a 'created_at' column, so the start of the first
|
13
|
+
version’s temporal period cannot be known. Add a 'created_at' column to '#{source_table_name}'.
|
14
|
+
LOG
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
6
18
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hoardable
|
4
|
+
# A module for overriding +ActiveRecord#find_one+ and +ActiveRecord#find_some+ in the case you are
|
5
|
+
# doing a temporal query and the current {SourceModel} record may in fact be a {VersionModel}
|
6
|
+
# record. This is extended into the current scope with {Hoardable#at} but can also be opt-ed into
|
7
|
+
# with the class method +hoardable+.
|
8
|
+
module FinderMethods
|
9
|
+
def find_one(id)
|
10
|
+
super(hoardable_source_ids([id])[0])
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_some(ids)
|
14
|
+
super(hoardable_source_ids(ids))
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def hoardable_source_ids(ids)
|
20
|
+
ids.map do |id|
|
21
|
+
version_class.where(hoardable_source_id: id).select(primary_key).ids[0] || id
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hoardable
|
4
|
+
# Provides temporal version awareness to +has_many+ relationships.
|
5
|
+
module HasMany
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
# An +ActiveRecord+ extension that allows looking up {VersionModel}s by +hoardable_source_id+ as
|
9
|
+
# if they were {SourceModel}s when using {Hoardable#at}.
|
10
|
+
module HasManyExtension
|
11
|
+
def scope
|
12
|
+
@scope ||= hoardable_scope
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def hoardable_scope
|
18
|
+
if Hoardable.instance_variable_get('@at') &&
|
19
|
+
(hoardable_source_id = @association.owner.hoardable_source_id)
|
20
|
+
@association.scope.rewhere(@association.reflection.foreign_key => hoardable_source_id)
|
21
|
+
else
|
22
|
+
@association.scope
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
private_constant :HasManyExtension
|
27
|
+
|
28
|
+
class_methods do
|
29
|
+
def has_many(*args, &block)
|
30
|
+
options = args.extract_options!
|
31
|
+
options[:extend] = Array(options[:extend]).push(HasManyExtension) if options.delete(:hoardable)
|
32
|
+
super(*args, **options, &block)
|
33
|
+
|
34
|
+
# This hack is needed to force Rails to not use any existing method cache so that the
|
35
|
+
# {HasManyExtension} scope is always used.
|
36
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
37
|
+
def #{args.first}
|
38
|
+
super.extending
|
39
|
+
end
|
40
|
+
RUBY
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
private_constant :HasMany
|
45
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hoardable
|
4
|
+
# Provides temporal version awareness to +has_one+ relationships.
|
5
|
+
module HasOne
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
def has_one(*args)
|
10
|
+
options = args.extract_options!
|
11
|
+
hoardable = options.delete(:hoardable)
|
12
|
+
association = super(*args, **options)
|
13
|
+
name = args.first
|
14
|
+
return unless hoardable || association[name.to_s].options[:class_name].match?(/RichText$/)
|
15
|
+
|
16
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
17
|
+
def #{name}
|
18
|
+
return super unless (at = Hoardable.instance_variable_get('@at'))
|
19
|
+
|
20
|
+
super&.version_at(at) ||
|
21
|
+
_reflections['profile'].klass.where(_reflections['profile'].foreign_key => id).first
|
22
|
+
end
|
23
|
+
RUBY
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
private_constant :HasOne
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hoardable
|
4
|
+
# Provides temporal version awareness for +ActionText+.
|
5
|
+
module HasOneAttached
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
def has_one_attached(name, **options, &block)
|
10
|
+
hoardable = options.delete(:hoardable)
|
11
|
+
super(name, **options, &block)
|
12
|
+
return unless hoardable
|
13
|
+
|
14
|
+
'ActiveStorage::Attachment'.constantize
|
15
|
+
reflection_options = reflections["#{name}_attachment"].options
|
16
|
+
reflection_options[:class_name] = reflection_options[:class_name].sub(/ActiveStorage/, 'Hoardable')
|
17
|
+
|
18
|
+
generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
19
|
+
# frozen_string_literal: true
|
20
|
+
def #{name}=(attachable)
|
21
|
+
attachment_changes["#{name}"] =
|
22
|
+
if attachable.nil?
|
23
|
+
ActiveStorage::Attached::Changes::DeleteOne.new("#{name}", self)
|
24
|
+
else
|
25
|
+
Hoardable::Attachment::CreateOne.new("#{name}", self, attachable)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
CODE
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
private_constant :HasOneAttached
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hoardable
|
4
|
+
# Provides temporal version awareness for +ActionText+.
|
5
|
+
module HasRichText
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
def has_rich_text(name, encrypted: false, hoardable: false)
|
10
|
+
if SUPPORTS_ENCRYPTED_ACTION_TEXT
|
11
|
+
super(name, encrypted: encrypted)
|
12
|
+
else
|
13
|
+
super(name)
|
14
|
+
end
|
15
|
+
return unless hoardable
|
16
|
+
|
17
|
+
reflection_options = reflections["rich_text_#{name}"].options
|
18
|
+
reflection_options[:class_name] = reflection_options[:class_name].sub(/ActionText/, 'Hoardable')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
private_constant :HasRichText
|
23
|
+
end
|
data/lib/hoardable/model.rb
CHANGED
@@ -53,11 +53,17 @@ module Hoardable
|
|
53
53
|
TracePoint.new(:end) do |trace|
|
54
54
|
next unless self == trace.self
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
full_version_class_name = "#{name}#{VERSION_CLASS_SUFFIX}"
|
57
|
+
if (namespace_match = full_version_class_name.match(/(.*)::(.*)/))
|
58
|
+
object_namespace = namespace_match[1].constantize
|
59
|
+
version_class_name = namespace_match[2]
|
60
|
+
else
|
61
|
+
object_namespace = Object
|
62
|
+
version_class_name = full_version_class_name
|
63
|
+
end
|
64
|
+
unless Object.const_defined?(full_version_class_name)
|
65
|
+
object_namespace.const_set(version_class_name, Class.new(self) { include VersionModel })
|
59
66
|
end
|
60
|
-
|
61
67
|
include SourceModel
|
62
68
|
|
63
69
|
trace.disable
|
data/lib/hoardable/scopes.rb
CHANGED
@@ -60,9 +60,13 @@ module Hoardable
|
|
60
60
|
# Returns instances of the source model and versions that were valid at the supplied
|
61
61
|
# +datetime+ or +time+, all cast as instances of the source model.
|
62
62
|
scope :at, lambda { |datetime|
|
63
|
+
raise(CreatedAtColumnMissingError, @klass.table_name) unless @klass.column_names.include?('created_at')
|
64
|
+
|
63
65
|
include_versions.where(id: version_class.at(datetime).select('id')).or(
|
64
|
-
|
65
|
-
|
66
|
+
exclude_versions
|
67
|
+
.where("#{table_name}.created_at < ?", datetime)
|
68
|
+
.where.not(id: version_class.select(:hoardable_source_id).where(DURING_QUERY, datetime))
|
69
|
+
).hoardable
|
66
70
|
}
|
67
71
|
end
|
68
72
|
|
@@ -16,16 +16,6 @@ module Hoardable
|
|
16
16
|
# @return [String] The database operation that created the +version+ - either +update+ or +delete+.
|
17
17
|
delegate :hoardable_event_uuid, :hoardable_operation, to: :hoardable_version, allow_nil: true
|
18
18
|
|
19
|
-
# A module for overriding +ActiveRecord#find_one+’ in the case you are doing a temporal query
|
20
|
-
# and the current {SourceModel} record may in fact be a {VersionModel} record.
|
21
|
-
module FinderMethods
|
22
|
-
def find_one(id)
|
23
|
-
conditions = { primary_key => [id, *version_class.where(hoardable_source_id: id).select(primary_key).ids] }
|
24
|
-
find_by(conditions) || where(conditions).raise_record_not_found_exception!
|
25
|
-
end
|
26
|
-
end
|
27
|
-
private_constant :FinderMethods
|
28
|
-
|
29
19
|
class_methods do
|
30
20
|
# The dynamically generated +Version+ class for this model.
|
31
21
|
def version_class
|
@@ -42,6 +32,8 @@ module Hoardable
|
|
42
32
|
include Scopes
|
43
33
|
|
44
34
|
around_update(if: [HOARDABLE_CALLBACKS_ENABLED, HOARDABLE_VERSION_UPDATES]) do |_, block|
|
35
|
+
next if self.is_a?(Hoardable::Attachment)
|
36
|
+
|
45
37
|
hoardable_client.insert_hoardable_version('update', &block)
|
46
38
|
end
|
47
39
|
|
@@ -50,7 +42,7 @@ module Hoardable
|
|
50
42
|
end
|
51
43
|
|
52
44
|
before_destroy(if: HOARDABLE_CALLBACKS_ENABLED, unless: HOARDABLE_SAVE_TRASH) do
|
53
|
-
versions.delete_all
|
45
|
+
versions.delete_all
|
54
46
|
end
|
55
47
|
|
56
48
|
after_commit { hoardable_client.unset_hoardable_version_and_event_uuid }
|
@@ -81,14 +73,21 @@ module Hoardable
|
|
81
73
|
!!hoardable_client.hoardable_version_source_id
|
82
74
|
end
|
83
75
|
|
84
|
-
# Returns the +version+ at the supplied +datetime+ or +time
|
85
|
-
# none. This will raise an error if you try to find a version in the future.
|
76
|
+
# Returns the +version+ at the supplied +datetime+ or +time+, or +self+ if there is none.
|
86
77
|
#
|
87
78
|
# @param datetime [DateTime, Time]
|
88
79
|
def at(datetime)
|
80
|
+
version_at(datetime) || (self if created_at < datetime)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Returns the +version+ at the supplied +datetime+ or +time+. This will raise an error if you
|
84
|
+
# try to find a version in the future.
|
85
|
+
#
|
86
|
+
# @param datetime [DateTime, Time]
|
87
|
+
def version_at(datetime)
|
89
88
|
raise(Error, 'Future state cannot be known') if datetime.future?
|
90
89
|
|
91
|
-
versions.at(datetime).first
|
90
|
+
versions.at(datetime).limit(1).first
|
92
91
|
end
|
93
92
|
|
94
93
|
# If a version is found at the supplied datetime, it will +revert!+ to it and return it. This
|
data/lib/hoardable/version.rb
CHANGED
data/lib/hoardable.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'active_record'
|
3
4
|
require_relative 'hoardable/version'
|
4
|
-
require_relative 'hoardable/
|
5
|
+
require_relative 'hoardable/engine'
|
6
|
+
require_relative 'hoardable/finder_methods'
|
5
7
|
require_relative 'hoardable/scopes'
|
6
8
|
require_relative 'hoardable/error'
|
7
9
|
require_relative 'hoardable/database_client'
|
@@ -9,5 +11,10 @@ require_relative 'hoardable/source_model'
|
|
9
11
|
require_relative 'hoardable/version_model'
|
10
12
|
require_relative 'hoardable/model'
|
11
13
|
require_relative 'hoardable/associations'
|
14
|
+
require_relative 'hoardable/has_many'
|
15
|
+
require_relative 'hoardable/belongs_to'
|
16
|
+
require_relative 'hoardable/has_one'
|
17
|
+
require_relative 'hoardable/has_rich_text'
|
18
|
+
require_relative 'hoardable/has_one_attached'
|
12
19
|
require_relative 'generators/hoardable/migration_generator'
|
13
|
-
require_relative 'generators/hoardable/
|
20
|
+
require_relative 'generators/hoardable/install_generator'
|
data/sig/hoardable.rbs
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Hoardable
|
2
2
|
VERSION: String
|
3
3
|
DATA_KEYS: [:meta, :whodunit, :note, :event_uuid]
|
4
|
-
CONFIG_KEYS: [:enabled, :version_updates, :save_trash
|
4
|
+
CONFIG_KEYS: [:enabled, :version_updates, :save_trash]
|
5
5
|
VERSION_CLASS_SUFFIX: String
|
6
6
|
VERSION_TABLE_SUFFIX: String
|
7
7
|
DURING_QUERY: String
|
@@ -17,8 +17,17 @@ module Hoardable
|
|
17
17
|
def self.at: (untyped datetime) -> untyped
|
18
18
|
def self.logger: -> untyped
|
19
19
|
|
20
|
+
module FinderMethods
|
21
|
+
def find_one: (untyped id) -> untyped
|
22
|
+
def find_some: (untyped ids) -> untyped
|
23
|
+
|
24
|
+
private
|
25
|
+
def hoardable_source_ids: ([untyped] ids) -> Array[untyped]
|
26
|
+
end
|
27
|
+
|
20
28
|
module Scopes
|
21
29
|
TABLEOID_AREL_CONDITIONS: Proc
|
30
|
+
self.@klass: bot
|
22
31
|
|
23
32
|
private
|
24
33
|
def tableoid: -> untyped
|
@@ -30,6 +39,10 @@ module Hoardable
|
|
30
39
|
class Error < StandardError
|
31
40
|
end
|
32
41
|
|
42
|
+
class CreatedAtColumnMissingError < Error
|
43
|
+
def initialize: (untyped source_table_name) -> void
|
44
|
+
end
|
45
|
+
|
33
46
|
class DatabaseClient
|
34
47
|
@hoardable_version_source_id: untyped
|
35
48
|
|
@@ -45,8 +58,7 @@ module Hoardable
|
|
45
58
|
def assign_hoardable_context: (:event_uuid | :meta | :note | :whodunit key) -> nil
|
46
59
|
def unset_hoardable_version_and_event_uuid: -> nil
|
47
60
|
def previous_temporal_tsrange_end: -> untyped
|
48
|
-
def hoardable_source_epoch: ->
|
49
|
-
def maybe_warn_about_missing_created_at_column: -> nil
|
61
|
+
def hoardable_source_epoch: -> untyped
|
50
62
|
end
|
51
63
|
|
52
64
|
module SourceModel
|
@@ -56,7 +68,8 @@ module Hoardable
|
|
56
68
|
attr_reader hoardable_version: untyped
|
57
69
|
def trashed?: -> untyped
|
58
70
|
def version?: -> untyped
|
59
|
-
def at: (untyped datetime) -> SourceModel
|
71
|
+
def at: (untyped datetime) -> SourceModel?
|
72
|
+
def version_at: (untyped datetime) -> untyped
|
60
73
|
def revert_to!: (untyped datetime) -> SourceModel?
|
61
74
|
def hoardable_source_id: -> untyped
|
62
75
|
|
@@ -66,10 +79,6 @@ module Hoardable
|
|
66
79
|
public
|
67
80
|
def version_class: -> untyped
|
68
81
|
def hoardable: -> untyped
|
69
|
-
|
70
|
-
module FinderMethods
|
71
|
-
def find_one: (untyped id) -> untyped
|
72
|
-
end
|
73
82
|
end
|
74
83
|
|
75
84
|
module VersionModel
|
@@ -99,10 +108,16 @@ module Hoardable
|
|
99
108
|
end
|
100
109
|
|
101
110
|
module Associations
|
102
|
-
|
103
|
-
|
111
|
+
@hoardable_association_overrider: Overrider
|
112
|
+
|
113
|
+
def belongs_to: (*untyped args) -> nil
|
114
|
+
def has_one: (*untyped args) -> nil
|
115
|
+
def has_many: (*untyped args) -> untyped
|
116
|
+
|
117
|
+
private
|
118
|
+
def hoardable_association_overrider: -> Overrider
|
104
119
|
|
105
|
-
module
|
120
|
+
module HasManyExtension
|
106
121
|
@scope: untyped
|
107
122
|
@association: bot
|
108
123
|
|
@@ -111,6 +126,14 @@ module Hoardable
|
|
111
126
|
private
|
112
127
|
def hoardable_scope: -> untyped
|
113
128
|
end
|
129
|
+
|
130
|
+
class Overrider
|
131
|
+
attr_reader klass: Associations
|
132
|
+
def initialize: (Associations klass) -> void
|
133
|
+
def override_belongs_to: (untyped name) -> untyped
|
134
|
+
def override_has_one: (untyped name) -> untyped
|
135
|
+
def override_has_many: (untyped name) -> untyped
|
136
|
+
end
|
114
137
|
end
|
115
138
|
|
116
139
|
class MigrationGenerator
|
@@ -122,7 +145,9 @@ module Hoardable
|
|
122
145
|
def singularized_table_name: -> untyped
|
123
146
|
end
|
124
147
|
|
125
|
-
class
|
148
|
+
class InstallGenerator
|
126
149
|
def create_initializer_file: -> untyped
|
150
|
+
def create_migration_file: -> untyped
|
151
|
+
def self.next_migration_number: (untyped dir) -> untyped
|
127
152
|
end
|
128
153
|
end
|
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.11.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-10-
|
11
|
+
date: 2022-10-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -104,16 +104,26 @@ files:
|
|
104
104
|
- LICENSE.txt
|
105
105
|
- README.md
|
106
106
|
- Rakefile
|
107
|
-
- lib/generators/hoardable/
|
107
|
+
- lib/generators/hoardable/install_generator.rb
|
108
108
|
- lib/generators/hoardable/migration_generator.rb
|
109
|
+
- lib/generators/hoardable/templates/functions.rb.erb
|
109
110
|
- lib/generators/hoardable/templates/migration.rb.erb
|
110
111
|
- lib/generators/hoardable/templates/migration_6.rb.erb
|
111
112
|
- lib/hoardable.rb
|
112
113
|
- lib/hoardable/associations.rb
|
114
|
+
- lib/hoardable/attachment.rb
|
115
|
+
- lib/hoardable/belongs_to.rb
|
113
116
|
- lib/hoardable/database_client.rb
|
117
|
+
- lib/hoardable/encrypted_rich_text.rb
|
118
|
+
- lib/hoardable/engine.rb
|
114
119
|
- lib/hoardable/error.rb
|
115
|
-
- lib/hoardable/
|
120
|
+
- lib/hoardable/finder_methods.rb
|
121
|
+
- lib/hoardable/has_many.rb
|
122
|
+
- lib/hoardable/has_one.rb
|
123
|
+
- lib/hoardable/has_one_attached.rb
|
124
|
+
- lib/hoardable/has_rich_text.rb
|
116
125
|
- lib/hoardable/model.rb
|
126
|
+
- lib/hoardable/rich_text.rb
|
117
127
|
- lib/hoardable/scopes.rb
|
118
128
|
- lib/hoardable/source_model.rb
|
119
129
|
- lib/hoardable/version.rb
|
@@ -1,21 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'rails/generators'
|
4
|
-
|
5
|
-
module Hoardable
|
6
|
-
# Generates an initializer file for {Hoardable} configuration.
|
7
|
-
class InitializerGenerator < Rails::Generators::Base
|
8
|
-
def create_initializer_file
|
9
|
-
create_file(
|
10
|
-
'config/initializers/hoardable.rb',
|
11
|
-
<<~TEXT
|
12
|
-
# Hoardable configuration defaults are below. Learn more at https://github.com/waymondo/hoardable#configuration
|
13
|
-
#
|
14
|
-
# Hoardable.enabled = true
|
15
|
-
# Hoardable.version_updates = true
|
16
|
-
# Hoardable.save_trash = true
|
17
|
-
TEXT
|
18
|
-
)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|