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