hoardable 0.11.0 → 0.12.2
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/README.md +8 -8
- data/lib/generators/hoardable/install_generator.rb +1 -1
- data/lib/generators/hoardable/migration_generator.rb +5 -7
- data/lib/generators/hoardable/templates/install.rb.erb +52 -0
- data/lib/generators/hoardable/templates/migration.rb.erb +12 -5
- data/lib/hoardable/associations.rb +0 -1
- data/lib/hoardable/belongs_to.rb +1 -1
- data/lib/hoardable/database_client.rb +12 -13
- data/lib/hoardable/engine.rb +0 -6
- data/lib/hoardable/finder_methods.rb +4 -4
- data/lib/hoardable/has_many.rb +8 -4
- data/lib/hoardable/scopes.rb +2 -2
- data/lib/hoardable/source_model.rb +5 -12
- data/lib/hoardable/version.rb +1 -1
- data/lib/hoardable/version_model.rb +13 -13
- data/lib/hoardable.rb +0 -1
- data/sig/hoardable.rbs +38 -23
- metadata +3 -6
- data/lib/generators/hoardable/templates/functions.rb.erb +0 -16
- data/lib/generators/hoardable/templates/migration_6.rb.erb +0 -54
- data/lib/hoardable/attachment.rb +0 -16
- data/lib/hoardable/has_one_attached.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eec6cda69bb2bbb1aeec6a0c460881c33abbe1de123ef9178d2c16378374b2bd
|
4
|
+
data.tar.gz: f3f57007e08f958017483dfffd0275b5b70c0ef1522984ea78da4c595a9e5506
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04a13fec8198d4c4fda05b5364a7925755e7f2708004e15a564f243b64836577f4393cb502e7dd7d88ea123df29909ccb36a1d603cae8b2e711958250dc45763
|
7
|
+
data.tar.gz: 34a59cb91ed643784e4aa6ab88abac2d3e7a59a59945bcdce6e849aa441bc1490a6870c2661f65c31d246e4cb87bb39c4502e6cb8ce05cb3c42a6ad0e56f1140
|
data/README.md
CHANGED
@@ -84,9 +84,9 @@ of that model. As we continue our example from above:
|
|
84
84
|
```
|
85
85
|
$ irb
|
86
86
|
>> Post
|
87
|
-
=> Post(id: integer, body: text, user_id: integer, created_at: datetime)
|
87
|
+
=> Post(id: integer, body: text, user_id: integer, created_at: datetime, hoardable_id: integer)
|
88
88
|
>> PostVersion
|
89
|
-
=> PostVersion(id: integer, body: text, user_id: integer, created_at: datetime, _data: jsonb, _during: tsrange
|
89
|
+
=> PostVersion(id: integer, body: text, user_id: integer, created_at: datetime, hoardable_id: integer, _data: jsonb, _during: tsrange)
|
90
90
|
```
|
91
91
|
|
92
92
|
A `Post` now `has_many :versions`. With the default configuration, whenever an update and deletion
|
@@ -144,7 +144,7 @@ If you want to look-up the version of a record at a specific time, you can use t
|
|
144
144
|
```ruby
|
145
145
|
post.at(1.day.ago) # => #<PostVersion>
|
146
146
|
# or you can use the scope on the version model class
|
147
|
-
PostVersion.at(1.day.ago).find_by(
|
147
|
+
PostVersion.at(1.day.ago).find_by(hoardable_id: post.id) # => #<PostVersion>
|
148
148
|
```
|
149
149
|
|
150
150
|
The source model class also has an `.at` method:
|
@@ -357,7 +357,7 @@ Hoardable.at(datetime) do
|
|
357
357
|
post.comments.size # => 2
|
358
358
|
post.id # => 2
|
359
359
|
post.version? # => true
|
360
|
-
post.
|
360
|
+
post.hoardable_id # => 1
|
361
361
|
end
|
362
362
|
```
|
363
363
|
|
@@ -370,7 +370,7 @@ 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
|
-
`
|
373
|
+
`hoardable_id`.
|
374
374
|
|
375
375
|
Sometimes you’ll trash something that `has_many :children, dependent: :destroy` and want
|
376
376
|
to untrash everything in a similar dependent manner. Whenever a hoardable version is created in a
|
@@ -393,7 +393,7 @@ class Post < ActiveRecord::Base
|
|
393
393
|
end
|
394
394
|
```
|
395
395
|
|
396
|
-
##
|
396
|
+
## Action Text
|
397
397
|
|
398
398
|
Hoardable provides support for ActiveRecord models with `has_rich_text`. First, you must create a
|
399
399
|
temporal table for `ActionText::RichText`:
|
@@ -420,9 +420,9 @@ post = Post.create!(content: '<div>Hello World</div>')
|
|
420
420
|
datetime = DateTime.current
|
421
421
|
post.update!(content: '<div>Goodbye Cruel World</div>')
|
422
422
|
post.content.versions.size # => 1
|
423
|
-
|
423
|
+
post.content.to_plain_text # => 'Goodbye Cruel World'
|
424
424
|
Hoardable.at(datetime) do
|
425
|
-
|
425
|
+
post.content.to_plain_text # => 'Hello World'
|
426
426
|
end
|
427
427
|
```
|
428
428
|
|
@@ -12,7 +12,7 @@ module Hoardable
|
|
12
12
|
class_option :foreign_key_type, type: :string
|
13
13
|
|
14
14
|
def create_versions_table
|
15
|
-
migration_template
|
15
|
+
migration_template 'migration.rb.erb', "db/migrate/create_#{singularized_table_name}_versions.rb"
|
16
16
|
end
|
17
17
|
|
18
18
|
no_tasks do
|
@@ -23,12 +23,10 @@ module Hoardable
|
|
23
23
|
'bigint'
|
24
24
|
end
|
25
25
|
|
26
|
-
def
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
'migration.rb.erb'
|
31
|
-
end
|
26
|
+
def primary_key
|
27
|
+
options[:primary_key] || class_name.singularize.constantize.primary_key
|
28
|
+
rescue StandardError
|
29
|
+
'id'
|
32
30
|
end
|
33
31
|
|
34
32
|
def singularized_table_name
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class InstallHoardable < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
4
|
+
def up
|
5
|
+
execute(
|
6
|
+
<<~SQL
|
7
|
+
DO $$
|
8
|
+
BEGIN
|
9
|
+
IF NOT EXISTS (
|
10
|
+
SELECT 1 FROM pg_type t WHERE t.typname = 'hoardable_operation'
|
11
|
+
) THEN
|
12
|
+
CREATE TYPE hoardable_operation AS ENUM ('update', 'delete', 'insert');
|
13
|
+
END IF;
|
14
|
+
END
|
15
|
+
$$;
|
16
|
+
CREATE OR REPLACE FUNCTION hoardable_source_set_id() RETURNS trigger
|
17
|
+
LANGUAGE plpgsql AS
|
18
|
+
$$
|
19
|
+
DECLARE
|
20
|
+
_pk information_schema.constraint_column_usage.column_name%TYPE;
|
21
|
+
_id _pk%TYPE;
|
22
|
+
BEGIN
|
23
|
+
SELECT c.column_name
|
24
|
+
FROM information_schema.table_constraints t
|
25
|
+
JOIN information_schema.constraint_column_usage c
|
26
|
+
ON c.constraint_name = t.constraint_name
|
27
|
+
WHERE c.table_name = TG_TABLE_NAME AND t.constraint_type = 'PRIMARY KEY'
|
28
|
+
LIMIT 1
|
29
|
+
INTO _pk;
|
30
|
+
EXECUTE format('SELECT $1.%I', _pk) INTO _id USING NEW;
|
31
|
+
NEW.hoardable_id = _id;
|
32
|
+
RETURN NEW;
|
33
|
+
END;$$;
|
34
|
+
CREATE OR REPLACE FUNCTION hoardable_version_prevent_update() RETURNS trigger
|
35
|
+
LANGUAGE plpgsql AS
|
36
|
+
$$BEGIN
|
37
|
+
RAISE EXCEPTION 'updating a version is not allowed';
|
38
|
+
RETURN NEW;
|
39
|
+
END;$$;
|
40
|
+
SQL
|
41
|
+
)
|
42
|
+
end
|
43
|
+
def down
|
44
|
+
execute(
|
45
|
+
<<~SQL
|
46
|
+
DROP TYPE IF EXISTS hoardable_operation;
|
47
|
+
DROP FUNCTION IF EXISTS hoardable_version_prevent_update();
|
48
|
+
DROP FUNCTION IF EXISTS hoardable_source_set_id();
|
49
|
+
SQL
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
@@ -2,21 +2,25 @@
|
|
2
2
|
|
3
3
|
class Create<%= class_name.singularize.delete(':') %>Versions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
4
4
|
def change
|
5
|
-
|
5
|
+
add_column :<%= table_name %>, :hoardable_id, :<%= foreign_key_type %>
|
6
|
+
add_index :<%= table_name %>, :hoardable_id
|
6
7
|
create_table :<%= singularized_table_name %>_versions, id: false, options: 'INHERITS (<%= table_name %>)' do |t|
|
7
8
|
t.jsonb :_data
|
8
9
|
t.tsrange :_during, null: false
|
9
10
|
t.uuid :_event_uuid, null: false, index: true
|
10
|
-
t.
|
11
|
-
t.<%= foreign_key_type %> :hoardable_source_id, null: false, index: true
|
11
|
+
t.column :_operation, :hoardable_operation, null: false, index: true
|
12
12
|
end
|
13
13
|
reversible do |dir|
|
14
14
|
dir.up do
|
15
15
|
execute(
|
16
16
|
<<~SQL
|
17
|
+
UPDATE <%= table_name %> SET hoardable_id = <%= primary_key %>;
|
17
18
|
CREATE TRIGGER <%= singularized_table_name %>_versions_prevent_update
|
18
19
|
BEFORE UPDATE ON <%= singularized_table_name %>_versions FOR EACH ROW
|
19
20
|
EXECUTE PROCEDURE hoardable_version_prevent_update();
|
21
|
+
CREATE TRIGGER <%= table_name %>_set_hoardable_id
|
22
|
+
BEFORE INSERT ON <%= table_name %> FOR EACH ROW
|
23
|
+
EXECUTE PROCEDURE hoardable_source_set_id();
|
20
24
|
SQL
|
21
25
|
)
|
22
26
|
end
|
@@ -25,14 +29,17 @@ class Create<%= class_name.singularize.delete(':') %>Versions < ActiveRecord::Mi
|
|
25
29
|
<<~SQL
|
26
30
|
DROP TRIGGER <%= singularized_table_name %>_versions_prevent_update
|
27
31
|
ON <%= singularized_table_name %>_versions;
|
32
|
+
DROP TRIGGER <%= table_name %>_set_hoardable_id
|
33
|
+
ON <%= table_name %>;
|
28
34
|
SQL
|
29
35
|
)
|
30
36
|
end
|
31
37
|
end
|
32
|
-
|
38
|
+
change_column_null :<%= table_name %>, :hoardable_id, false
|
39
|
+
add_index(:<%= singularized_table_name %>_versions, :<%= primary_key %>, unique: true)
|
33
40
|
add_index(
|
34
41
|
:<%= singularized_table_name %>_versions,
|
35
|
-
%i[_during
|
42
|
+
%i[_during hoardable_id],
|
36
43
|
name: 'idx_<%= singularized_table_name %>_versions_temporally'
|
37
44
|
)
|
38
45
|
end
|
data/lib/hoardable/belongs_to.rb
CHANGED
@@ -21,7 +21,7 @@ module Hoardable
|
|
21
21
|
define_method("trashed_#{name}") do
|
22
22
|
source_reflection = reflections[name.to_s]
|
23
23
|
source_reflection.version_class.trashed.only_most_recent.find_by(
|
24
|
-
|
24
|
+
hoardable_id: source_reflection.foreign_key
|
25
25
|
)
|
26
26
|
end
|
27
27
|
|
@@ -13,30 +13,25 @@ module Hoardable
|
|
13
13
|
delegate :version_class, to: :source_record
|
14
14
|
|
15
15
|
def insert_hoardable_version(operation, &block)
|
16
|
-
version = version_class.insert(initialize_version_attributes(operation), returning:
|
17
|
-
version_id = version[0][
|
16
|
+
version = version_class.insert(initialize_version_attributes(operation), returning: source_primary_key.to_sym)
|
17
|
+
version_id = version[0][source_primary_key]
|
18
18
|
source_record.instance_variable_set('@hoardable_version', version_class.find(version_id))
|
19
19
|
source_record.run_callbacks(:versioned, &block)
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
def hoardable_version_source_id
|
27
|
-
@hoardable_version_source_id ||= query_hoardable_version_source_id
|
22
|
+
def source_primary_key
|
23
|
+
source_record.class.primary_key
|
28
24
|
end
|
29
25
|
|
30
|
-
def
|
31
|
-
|
32
|
-
version_class.where(primary_key => source_record.read_attribute(primary_key)).pluck('hoardable_source_id')[0]
|
26
|
+
def find_or_initialize_hoardable_event_uuid
|
27
|
+
Thread.current[:hoardable_event_uuid] ||= ActiveRecord::Base.connection.query('SELECT gen_random_uuid();')[0][0]
|
33
28
|
end
|
34
29
|
|
35
30
|
def initialize_version_attributes(operation)
|
36
|
-
|
31
|
+
source_attributes_without_primary_key.merge(
|
37
32
|
source_record.changes.transform_values { |h| h[0] },
|
38
33
|
{
|
39
|
-
'
|
34
|
+
'hoardable_id' => source_record.id,
|
40
35
|
'_event_uuid' => find_or_initialize_hoardable_event_uuid,
|
41
36
|
'_operation' => operation,
|
42
37
|
'_data' => initialize_hoardable_data.merge(changes: source_record.changes),
|
@@ -45,6 +40,10 @@ module Hoardable
|
|
45
40
|
)
|
46
41
|
end
|
47
42
|
|
43
|
+
def source_attributes_without_primary_key
|
44
|
+
source_record.attributes_before_type_cast.without(source_primary_key)
|
45
|
+
end
|
46
|
+
|
48
47
|
def initialize_temporal_range
|
49
48
|
((previous_temporal_tsrange_end || hoardable_source_epoch)..Time.now.utc)
|
50
49
|
end
|
data/lib/hoardable/engine.rb
CHANGED
@@ -105,11 +105,5 @@ module Hoardable
|
|
105
105
|
require_relative 'encrypted_rich_text' if SUPPORTS_ENCRYPTED_ACTION_TEXT
|
106
106
|
end
|
107
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
108
|
end
|
115
109
|
end
|
@@ -7,18 +7,18 @@ module Hoardable
|
|
7
7
|
# with the class method +hoardable+.
|
8
8
|
module FinderMethods
|
9
9
|
def find_one(id)
|
10
|
-
super(
|
10
|
+
super(hoardable_ids([id])[0])
|
11
11
|
end
|
12
12
|
|
13
13
|
def find_some(ids)
|
14
|
-
super(
|
14
|
+
super(hoardable_ids(ids))
|
15
15
|
end
|
16
16
|
|
17
17
|
private
|
18
18
|
|
19
|
-
def
|
19
|
+
def hoardable_ids(ids)
|
20
20
|
ids.map do |id|
|
21
|
-
version_class.where(
|
21
|
+
version_class.where(hoardable_id: id).select(primary_key).ids[0] || id
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|
data/lib/hoardable/has_many.rb
CHANGED
@@ -5,7 +5,7 @@ module Hoardable
|
|
5
5
|
module HasMany
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
|
-
# An +ActiveRecord+ extension that allows looking up {VersionModel}s by +
|
8
|
+
# An +ActiveRecord+ extension that allows looking up {VersionModel}s by +hoardable_id+ as
|
9
9
|
# if they were {SourceModel}s when using {Hoardable#at}.
|
10
10
|
module HasManyExtension
|
11
11
|
def scope
|
@@ -15,9 +15,13 @@ module Hoardable
|
|
15
15
|
private
|
16
16
|
|
17
17
|
def hoardable_scope
|
18
|
-
if Hoardable.instance_variable_get('@at') &&
|
19
|
-
|
20
|
-
|
18
|
+
if Hoardable.instance_variable_get('@at') && (hoardable_id = @association.owner.hoardable_id)
|
19
|
+
if @association.reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
|
20
|
+
@association.reflection.source_reflection.instance_variable_set(
|
21
|
+
'@active_record_primary_key', 'hoardable_id'
|
22
|
+
)
|
23
|
+
end
|
24
|
+
@association.scope.rewhere(@association.reflection.foreign_key => hoardable_id)
|
21
25
|
else
|
22
26
|
@association.scope
|
23
27
|
end
|
data/lib/hoardable/scopes.rb
CHANGED
@@ -62,10 +62,10 @@ module Hoardable
|
|
62
62
|
scope :at, lambda { |datetime|
|
63
63
|
raise(CreatedAtColumnMissingError, @klass.table_name) unless @klass.column_names.include?('created_at')
|
64
64
|
|
65
|
-
include_versions.where(id: version_class.at(datetime).select(
|
65
|
+
include_versions.where(id: version_class.at(datetime).select(@klass.primary_key)).or(
|
66
66
|
exclude_versions
|
67
67
|
.where("#{table_name}.created_at < ?", datetime)
|
68
|
-
.where.not(id: version_class.select(:
|
68
|
+
.where.not(id: version_class.select(:hoardable_id).where(DURING_QUERY, datetime))
|
69
69
|
).hoardable
|
70
70
|
}
|
71
71
|
end
|
@@ -32,8 +32,6 @@ module Hoardable
|
|
32
32
|
include Scopes
|
33
33
|
|
34
34
|
around_update(if: [HOARDABLE_CALLBACKS_ENABLED, HOARDABLE_VERSION_UPDATES]) do |_, block|
|
35
|
-
next if self.is_a?(Hoardable::Attachment)
|
36
|
-
|
37
35
|
hoardable_client.insert_hoardable_version('update', &block)
|
38
36
|
end
|
39
37
|
|
@@ -53,7 +51,7 @@ module Hoardable
|
|
53
51
|
dependent: nil,
|
54
52
|
class_name: version_class.to_s,
|
55
53
|
inverse_of: :hoardable_source,
|
56
|
-
foreign_key: :
|
54
|
+
foreign_key: :hoardable_id
|
57
55
|
)
|
58
56
|
end
|
59
57
|
|
@@ -62,7 +60,7 @@ module Hoardable
|
|
62
60
|
#
|
63
61
|
# @return [Boolean]
|
64
62
|
def trashed?
|
65
|
-
|
63
|
+
!self.class.exists?(self.class.primary_key => id)
|
66
64
|
end
|
67
65
|
|
68
66
|
# Returns a boolean of whether the record is actually a +version+ cast as an instance of the
|
@@ -70,7 +68,7 @@ module Hoardable
|
|
70
68
|
#
|
71
69
|
# @return [Boolean]
|
72
70
|
def version?
|
73
|
-
|
71
|
+
hoardable_id != id
|
74
72
|
end
|
75
73
|
|
76
74
|
# Returns the +version+ at the supplied +datetime+ or +time+, or +self+ if there is none.
|
@@ -100,13 +98,8 @@ module Hoardable
|
|
100
98
|
version.is_a?(version_class) ? version.revert! : self
|
101
99
|
end
|
102
100
|
|
103
|
-
|
104
|
-
|
105
|
-
# {SourceModel}.
|
106
|
-
#
|
107
|
-
# @return [Integer, nil]
|
108
|
-
def hoardable_source_id
|
109
|
-
hoardable_client.hoardable_version_source_id || id
|
101
|
+
def hoardable_id
|
102
|
+
read_attribute('hoardable_id')
|
110
103
|
end
|
111
104
|
|
112
105
|
delegate :version_class, to: :class
|
data/lib/hoardable/version.rb
CHANGED
@@ -7,6 +7,13 @@ module Hoardable
|
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
9
|
class_methods do
|
10
|
+
# This is needed to allow {FinderMethods} to work with the version class.
|
11
|
+
#
|
12
|
+
# @!visibility private
|
13
|
+
def version_class
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
10
17
|
# This is needed to omit the pseudo row of 'tableoid' when using +ActiveRecord+’s +insert+.
|
11
18
|
#
|
12
19
|
# @!visibility private
|
@@ -20,6 +27,7 @@ module Hoardable
|
|
20
27
|
belongs_to(
|
21
28
|
:hoardable_source,
|
22
29
|
inverse_of: :versions,
|
30
|
+
foreign_key: :hoardable_id,
|
23
31
|
class_name: superclass.model_name
|
24
32
|
)
|
25
33
|
|
@@ -37,7 +45,7 @@ module Hoardable
|
|
37
45
|
# Returns only trashed +versions+ that are currently orphans.
|
38
46
|
scope :trashed, lambda {
|
39
47
|
left_outer_joins(:hoardable_source)
|
40
|
-
.where(superclass.table_name => {
|
48
|
+
.where(superclass.table_name => { superclass.primary_key => nil })
|
41
49
|
.where(_operation: 'delete')
|
42
50
|
}
|
43
51
|
|
@@ -78,7 +86,7 @@ module Hoardable
|
|
78
86
|
|
79
87
|
transaction do
|
80
88
|
hoardable_source.tap do |reverted|
|
81
|
-
reverted.update!(hoardable_source_attributes.without(
|
89
|
+
reverted.update!(hoardable_source_attributes.without(self.class.superclass.primary_key))
|
82
90
|
reverted.instance_variable_set(:@hoardable_version, self)
|
83
91
|
reverted.run_callbacks(:reverted)
|
84
92
|
end
|
@@ -113,24 +121,16 @@ module Hoardable
|
|
113
121
|
_data&.dig('changes')
|
114
122
|
end
|
115
123
|
|
116
|
-
# Returns the ID of the {SourceModel} that created this {VersionModel}
|
117
|
-
def hoardable_source_id
|
118
|
-
read_attribute('hoardable_source_id')
|
119
|
-
end
|
120
|
-
|
121
124
|
private
|
122
125
|
|
123
126
|
def insert_untrashed_source
|
124
127
|
superscope = self.class.superclass.unscoped
|
125
|
-
superscope.insert(hoardable_source_attributes.merge(
|
126
|
-
superscope.find(
|
128
|
+
superscope.insert(hoardable_source_attributes.merge(superscope.primary_key => hoardable_id))
|
129
|
+
superscope.find(hoardable_id)
|
127
130
|
end
|
128
131
|
|
129
132
|
def hoardable_source_attributes
|
130
|
-
|
131
|
-
attributes_before_type_cast
|
132
|
-
.without('hoardable_source_id')
|
133
|
-
.reject { |k, _v| k.start_with?('_') }
|
133
|
+
attributes_before_type_cast.without(self.class.column_names - self.class.superclass.column_names)
|
134
134
|
end
|
135
135
|
end
|
136
136
|
end
|
data/lib/hoardable.rb
CHANGED
@@ -15,6 +15,5 @@ require_relative 'hoardable/has_many'
|
|
15
15
|
require_relative 'hoardable/belongs_to'
|
16
16
|
require_relative 'hoardable/has_one'
|
17
17
|
require_relative 'hoardable/has_rich_text'
|
18
|
-
require_relative 'hoardable/has_one_attached'
|
19
18
|
require_relative 'generators/hoardable/migration_generator'
|
20
19
|
require_relative 'generators/hoardable/install_generator'
|
data/sig/hoardable.rbs
CHANGED
@@ -8,6 +8,7 @@ module Hoardable
|
|
8
8
|
HOARDABLE_CALLBACKS_ENABLED: ^(untyped) -> untyped
|
9
9
|
HOARDABLE_SAVE_TRASH: ^(untyped) -> untyped
|
10
10
|
HOARDABLE_VERSION_UPDATES: ^(untyped) -> untyped
|
11
|
+
SUPPORTS_ENCRYPTED_ACTION_TEXT: untyped
|
11
12
|
self.@context: Hash[untyped, untyped]
|
12
13
|
self.@config: untyped
|
13
14
|
self.@at: nil
|
@@ -17,12 +18,15 @@ module Hoardable
|
|
17
18
|
def self.at: (untyped datetime) -> untyped
|
18
19
|
def self.logger: -> untyped
|
19
20
|
|
21
|
+
class Engine
|
22
|
+
end
|
23
|
+
|
20
24
|
module FinderMethods
|
21
25
|
def find_one: (untyped id) -> untyped
|
22
26
|
def find_some: (untyped ids) -> untyped
|
23
27
|
|
24
28
|
private
|
25
|
-
def
|
29
|
+
def hoardable_ids: ([untyped] ids) -> Array[untyped]
|
26
30
|
end
|
27
31
|
|
28
32
|
module Scopes
|
@@ -44,15 +48,13 @@ module Hoardable
|
|
44
48
|
end
|
45
49
|
|
46
50
|
class DatabaseClient
|
47
|
-
@hoardable_version_source_id: untyped
|
48
|
-
|
49
51
|
attr_reader source_record: SourceModel
|
50
52
|
def initialize: (SourceModel source_record) -> void
|
51
53
|
def insert_hoardable_version: (untyped operation) -> untyped
|
54
|
+
def source_primary_key: -> untyped
|
52
55
|
def find_or_initialize_hoardable_event_uuid: -> untyped
|
53
|
-
def hoardable_version_source_id: -> untyped
|
54
|
-
def query_hoardable_version_source_id: -> untyped
|
55
56
|
def initialize_version_attributes: (untyped operation) -> untyped
|
57
|
+
def source_attributes_without_primary_key: -> untyped
|
56
58
|
def initialize_temporal_range: -> Range
|
57
59
|
def initialize_hoardable_data: -> untyped
|
58
60
|
def assign_hoardable_context: (:event_uuid | :meta | :note | :whodunit key) -> nil
|
@@ -71,7 +73,7 @@ module Hoardable
|
|
71
73
|
def at: (untyped datetime) -> SourceModel?
|
72
74
|
def version_at: (untyped datetime) -> untyped
|
73
75
|
def revert_to!: (untyped datetime) -> SourceModel?
|
74
|
-
def
|
76
|
+
def hoardable_id: -> untyped
|
75
77
|
|
76
78
|
private
|
77
79
|
def hoardable_client: -> DatabaseClient
|
@@ -82,18 +84,16 @@ module Hoardable
|
|
82
84
|
end
|
83
85
|
|
84
86
|
module VersionModel
|
85
|
-
@hoardable_source_attributes: untyped
|
86
|
-
|
87
87
|
def revert!: -> untyped
|
88
88
|
def untrash!: -> untyped
|
89
89
|
def changes: -> untyped
|
90
|
-
def hoardable_source_id: -> untyped
|
91
90
|
|
92
91
|
private
|
93
92
|
def insert_untrashed_source: -> untyped
|
94
93
|
def hoardable_source_attributes: -> untyped
|
95
94
|
|
96
95
|
public
|
96
|
+
def version_class: -> VersionModel
|
97
97
|
def scope_attributes: -> untyped
|
98
98
|
end
|
99
99
|
|
@@ -108,15 +108,15 @@ module Hoardable
|
|
108
108
|
end
|
109
109
|
|
110
110
|
module Associations
|
111
|
-
|
111
|
+
include HasRichText
|
112
|
+
include BelongsTo
|
113
|
+
include HasOne
|
114
|
+
include HasMany
|
115
|
+
end
|
112
116
|
|
113
|
-
|
114
|
-
def has_one: (*untyped args) -> nil
|
117
|
+
module HasMany
|
115
118
|
def has_many: (*untyped args) -> untyped
|
116
119
|
|
117
|
-
private
|
118
|
-
def hoardable_association_overrider: -> Overrider
|
119
|
-
|
120
120
|
module HasManyExtension
|
121
121
|
@scope: untyped
|
122
122
|
@association: bot
|
@@ -126,14 +126,21 @@ module Hoardable
|
|
126
126
|
private
|
127
127
|
def hoardable_scope: -> untyped
|
128
128
|
end
|
129
|
+
end
|
129
130
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
131
|
+
module BelongsTo
|
132
|
+
def belongs_to: (*untyped args) -> nil
|
133
|
+
|
134
|
+
private
|
135
|
+
def hoardable_override_belongs_to: (untyped name) -> untyped
|
136
|
+
end
|
137
|
+
|
138
|
+
module HasOne
|
139
|
+
def has_one: (*untyped args) -> nil
|
140
|
+
end
|
141
|
+
|
142
|
+
module HasRichText
|
143
|
+
def has_rich_text: (untyped name, ?encrypted: false, ?hoardable: false) -> nil
|
137
144
|
end
|
138
145
|
|
139
146
|
class MigrationGenerator
|
@@ -141,7 +148,7 @@ module Hoardable
|
|
141
148
|
|
142
149
|
def create_versions_table: -> untyped
|
143
150
|
def foreign_key_type: -> String
|
144
|
-
def
|
151
|
+
def primary_key: -> String
|
145
152
|
def singularized_table_name: -> untyped
|
146
153
|
end
|
147
154
|
|
@@ -150,4 +157,12 @@ module Hoardable
|
|
150
157
|
def create_migration_file: -> untyped
|
151
158
|
def self.next_migration_number: (untyped dir) -> untyped
|
152
159
|
end
|
160
|
+
|
161
|
+
class RichText
|
162
|
+
include Model
|
163
|
+
end
|
164
|
+
|
165
|
+
class EncryptedRichText
|
166
|
+
include Model
|
167
|
+
end
|
153
168
|
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.12.2
|
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-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -106,12 +106,10 @@ files:
|
|
106
106
|
- Rakefile
|
107
107
|
- lib/generators/hoardable/install_generator.rb
|
108
108
|
- lib/generators/hoardable/migration_generator.rb
|
109
|
-
- lib/generators/hoardable/templates/
|
109
|
+
- lib/generators/hoardable/templates/install.rb.erb
|
110
110
|
- lib/generators/hoardable/templates/migration.rb.erb
|
111
|
-
- lib/generators/hoardable/templates/migration_6.rb.erb
|
112
111
|
- lib/hoardable.rb
|
113
112
|
- lib/hoardable/associations.rb
|
114
|
-
- lib/hoardable/attachment.rb
|
115
113
|
- lib/hoardable/belongs_to.rb
|
116
114
|
- lib/hoardable/database_client.rb
|
117
115
|
- lib/hoardable/encrypted_rich_text.rb
|
@@ -120,7 +118,6 @@ files:
|
|
120
118
|
- lib/hoardable/finder_methods.rb
|
121
119
|
- lib/hoardable/has_many.rb
|
122
120
|
- lib/hoardable/has_one.rb
|
123
|
-
- lib/hoardable/has_one_attached.rb
|
124
121
|
- lib/hoardable/has_rich_text.rb
|
125
122
|
- lib/hoardable/model.rb
|
126
123
|
- lib/hoardable/rich_text.rb
|
@@ -1,16 +0,0 @@
|
|
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,54 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Create<%= class_name.singularize.delete(':') %>Versions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
4
|
-
def change
|
5
|
-
reversible do |dir|
|
6
|
-
dir.up do
|
7
|
-
execute <<~SQL
|
8
|
-
DO $$
|
9
|
-
BEGIN
|
10
|
-
IF NOT EXISTS (
|
11
|
-
SELECT 1 FROM pg_type t
|
12
|
-
WHERE t.typname = 'hoardable_operation'
|
13
|
-
) THEN
|
14
|
-
CREATE TYPE hoardable_operation AS ENUM ('update', 'delete', 'insert');
|
15
|
-
END IF;
|
16
|
-
END
|
17
|
-
$$;
|
18
|
-
SQL
|
19
|
-
end
|
20
|
-
end
|
21
|
-
create_table :<%= singularized_table_name %>_versions, id: false, options: 'INHERITS (<%= table_name %>)' do |t|
|
22
|
-
t.jsonb :_data
|
23
|
-
t.tsrange :_during, null: false
|
24
|
-
t.uuid :_event_uuid, null: false, index: true
|
25
|
-
t.column :_operation, :hoardable_operation, null: false, index: true
|
26
|
-
t.<%= foreign_key_type %> :hoardable_source_id, null: false, index: true
|
27
|
-
end
|
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
|
47
|
-
add_index(:<%= singularized_table_name %>_versions, :id, unique: true)
|
48
|
-
add_index(
|
49
|
-
:<%= singularized_table_name %>_versions,
|
50
|
-
%i[_during hoardable_source_id],
|
51
|
-
name: 'idx_<%= singularized_table_name %>_versions_temporally'
|
52
|
-
)
|
53
|
-
end
|
54
|
-
end
|
data/lib/hoardable/attachment.rb
DELETED
@@ -1,16 +0,0 @@
|
|
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
|
@@ -1,33 +0,0 @@
|
|
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
|