hoardable 0.16.0 → 0.18.1
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 +4 -0
- data/README.md +23 -1
- data/lib/generators/hoardable/functions/set_hoardable_id.sql +7 -0
- data/lib/generators/hoardable/install_generator.rb +1 -1
- data/lib/generators/hoardable/migration_generator.rb +15 -4
- data/lib/generators/hoardable/templates/install.rb.erb +0 -1
- data/lib/generators/hoardable/templates/migration.rb.erb +3 -2
- data/lib/generators/hoardable/triggers/set_hoardable_id.sql +1 -1
- data/lib/hoardable/database_client.rb +8 -1
- data/lib/hoardable/engine.rb +11 -1
- data/lib/hoardable/error.rb +10 -0
- data/lib/hoardable/has_many.rb +10 -5
- data/lib/hoardable/model.rb +7 -5
- data/lib/hoardable/scopes.rb +13 -6
- data/lib/hoardable/source_model.rb +14 -10
- data/lib/hoardable/version.rb +1 -1
- data/lib/hoardable/version_model.rb +3 -0
- metadata +8 -9
- data/.tool-versions +0 -2
- data/lib/generators/hoardable/functions/hoardable_source_set_id.sql +0 -18
- /data/lib/generators/hoardable/{functions → install_functions}/hoardable_prevent_update_id.sql +0 -0
- /data/lib/generators/hoardable/{functions → install_functions}/hoardable_version_prevent_update.sql +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36d1113a71b72492244fadc8172ef4a08f28bdff546b7344bb385ce38e82ecbd
|
4
|
+
data.tar.gz: a4cee0c629c4c1d59f6c0b632c0499fc5e094e099c7e0dc4bf3e0d79271f6eb8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3cb288b7dbd5938056d3fd5fae396ad6d7caca20a98902ebb228a75d4c05d5e689807ea90f4e6f96dc5e1ac20bbb028fa12717676da3f60163ca4a362bf33db1
|
7
|
+
data.tar.gz: 44bba102025a18563d29d76430d27f06299316d69b37e08fa3b216453dfe6c4890f82e6f065843c127cf26fb0da39f7ce76194d2e31dc2bd0021d9973e6c9748
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -41,7 +41,6 @@ Include `Hoardable::Model` into an ActiveRecord model you would like to hoard ve
|
|
41
41
|
```ruby
|
42
42
|
class Post < ActiveRecord::Base
|
43
43
|
include Hoardable::Model
|
44
|
-
...
|
45
44
|
end
|
46
45
|
```
|
47
46
|
|
@@ -235,6 +234,21 @@ version.changes # => { "title"=> ["Title", "New Title"] }
|
|
235
234
|
version.hoardable_operation # => "update"
|
236
235
|
```
|
237
236
|
|
237
|
+
### Overriding the temporal range
|
238
|
+
|
239
|
+
When calculating the temporal range for a given version, the default upper bound is `Time.now.utc`.
|
240
|
+
|
241
|
+
You can, however, use the `Hoardable.travel_to` class method to specify a custom upper bound for the time range. This allows
|
242
|
+
you to specify the datetime that a particular change should be recorded at by passing a block:
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
Hoardable.travel_to(2.weeks.ago) do
|
246
|
+
post.destroy!
|
247
|
+
end
|
248
|
+
```
|
249
|
+
|
250
|
+
Note: If the provided datetime pre-dates the calculated lower bound then an `InvalidTemporalUpperBoundError` will be raised.
|
251
|
+
|
238
252
|
### Model Callbacks
|
239
253
|
|
240
254
|
Sometimes you might want to do something with a version after it gets inserted to the database. You
|
@@ -313,6 +327,14 @@ end
|
|
313
327
|
|
314
328
|
Model-level configuration overrides global configuration.
|
315
329
|
|
330
|
+
### Single Table Inheritance
|
331
|
+
|
332
|
+
Hoardable works for [Single Table
|
333
|
+
Inheritance](https://guides.rubyonrails.org/association_basics.html#single-table-inheritance-sti). You
|
334
|
+
will need to include `Hoardable::Model` in each child model you'd like to version, as that is what
|
335
|
+
generates the model's version class. The migration generator only needs to be run for the parent
|
336
|
+
model, as the versions will similarly be stored in a single table.
|
337
|
+
|
316
338
|
## Relationships
|
317
339
|
|
318
340
|
### `belongs_to`
|
@@ -25,7 +25,7 @@ module Hoardable
|
|
25
25
|
|
26
26
|
def create_functions
|
27
27
|
Dir
|
28
|
-
.glob(File.join(__dir__, "
|
28
|
+
.glob(File.join(__dir__, "install_functions", "*.sql"))
|
29
29
|
.each do |file_path|
|
30
30
|
file_name = file_path.match(%r{([^/]+)\.sql})[1]
|
31
31
|
template file_path, "db/functions/#{file_name}_v01.sql"
|
@@ -36,22 +36,33 @@ module Hoardable
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
def create_function
|
40
|
+
template("../functions/set_hoardable_id.sql", "db/functions/#{function_name}_v01.sql")
|
41
|
+
end
|
42
|
+
|
39
43
|
no_tasks do
|
44
|
+
def function_name
|
45
|
+
"hoardable_set_hoardable_id_from_#{primary_key}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def klass
|
49
|
+
@klass ||= class_name.singularize.constantize
|
50
|
+
end
|
51
|
+
|
40
52
|
def table_name
|
41
|
-
|
53
|
+
klass.table_name
|
42
54
|
rescue StandardError
|
43
55
|
super
|
44
56
|
end
|
45
57
|
|
46
58
|
def foreign_key_type
|
47
|
-
options[:foreign_key_type] ||
|
48
|
-
class_name.singularize.constantize.columns.find { |col| col.name == primary_key }.sql_type
|
59
|
+
options[:foreign_key_type] || klass.columns.find { |col| col.name == primary_key }.sql_type
|
49
60
|
rescue StandardError
|
50
61
|
"bigint"
|
51
62
|
end
|
52
63
|
|
53
64
|
def primary_key
|
54
|
-
options[:primary_key] ||
|
65
|
+
options[:primary_key] || klass.primary_key
|
55
66
|
rescue StandardError
|
56
67
|
"id"
|
57
68
|
end
|
@@ -4,7 +4,6 @@ class InstallHoardable < ActiveRecord::Migration[<%= ActiveRecord::Migration.cur
|
|
4
4
|
def change
|
5
5
|
<% if postgres_version < 13 %>enable_extension :pgcrypto
|
6
6
|
<% end %>create_function :hoardable_prevent_update_id
|
7
|
-
create_function :hoardable_source_set_id
|
8
7
|
create_function :hoardable_version_prevent_update
|
9
8
|
create_enum :hoardable_operation, %w[update delete insert]
|
10
9
|
end
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class Create<%=
|
3
|
+
class Create<%= singularized_table_name.classify %>Versions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
4
4
|
def change
|
5
5
|
add_column :<%= table_name %>, :hoardable_id, :<%= foreign_key_type %>
|
6
6
|
add_index :<%= table_name %>, :hoardable_id
|
7
7
|
create_table(
|
8
8
|
:<%= singularized_table_name %>_versions,
|
9
|
-
id: false,
|
9
|
+
id: false,
|
10
10
|
options: 'INHERITS (<%= table_name %>)',
|
11
11
|
) do |t|
|
12
12
|
t.jsonb :_data
|
@@ -25,6 +25,7 @@ class Create<%= class_name.singularize.delete(':') %>Versions < ActiveRecord::Mi
|
|
25
25
|
:<%= singularized_table_name %>_versions_prevent_update,
|
26
26
|
on: :<%= singularized_table_name %>_versions
|
27
27
|
)
|
28
|
+
create_function :<%= function_name %>
|
28
29
|
create_trigger :<%= table_name %>_set_hoardable_id, on: :<%= table_name %>
|
29
30
|
create_trigger :<%= table_name %>_prevent_update_hoardable_id, on: :<%= table_name %>
|
30
31
|
change_column_null :<%= table_name %>, :hoardable_id, false
|
@@ -93,7 +93,14 @@ module Hoardable
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def initialize_temporal_range
|
96
|
-
(
|
96
|
+
upper_bound = Hoardable.instance_variable_get("@travel_to") || Time.now.utc
|
97
|
+
lower_bound = (previous_temporal_tsrange_end || hoardable_source_epoch)
|
98
|
+
|
99
|
+
if upper_bound < lower_bound
|
100
|
+
raise InvalidTemporalUpperBoundError.new(upper_bound, lower_bound)
|
101
|
+
end
|
102
|
+
|
103
|
+
(lower_bound..upper_bound)
|
97
104
|
end
|
98
105
|
|
99
106
|
def initialize_hoardable_data
|
data/lib/hoardable/engine.rb
CHANGED
@@ -81,6 +81,16 @@ module Hoardable
|
|
81
81
|
@at = nil
|
82
82
|
end
|
83
83
|
|
84
|
+
# Allows calling code to set the upper bound for the temporal range for recorded audits.
|
85
|
+
#
|
86
|
+
# @param datetime [DateTime] the datetime to temporally record versions at
|
87
|
+
def travel_to(datetime)
|
88
|
+
@travel_to = datetime
|
89
|
+
yield
|
90
|
+
ensure
|
91
|
+
@travel_to = nil
|
92
|
+
end
|
93
|
+
|
84
94
|
# @!visibility private
|
85
95
|
def logger
|
86
96
|
@logger ||= ActiveSupport::TaggedLogging.new(Logger.new($stdout))
|
@@ -101,7 +111,7 @@ module Hoardable
|
|
101
111
|
initializer "hoardable.schema_statements" do
|
102
112
|
ActiveSupport.on_load(:active_record_postgresqladapter) do
|
103
113
|
# We need to control the table dumping order of tables, so revert these to just +super+
|
104
|
-
Fx::SchemaDumper
|
114
|
+
Fx::SchemaDumper.module_eval("def tables(streams); super; end")
|
105
115
|
|
106
116
|
ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaDumper.prepend(SchemaDumper)
|
107
117
|
ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements.prepend(SchemaStatements)
|
data/lib/hoardable/error.rb
CHANGED
@@ -24,4 +24,14 @@ module Hoardable
|
|
24
24
|
LOG
|
25
25
|
end
|
26
26
|
end
|
27
|
+
|
28
|
+
# An error to be raised when the provided temporal upper bound is before the calcualated lower bound.
|
29
|
+
class InvalidTemporalUpperBoundError < Error
|
30
|
+
def initialize(upper, lower)
|
31
|
+
super(<<~LOG)
|
32
|
+
'The supplied value to `Hoardable.travel_to` (#{upper}) is before the calculated lower bound (#{lower}).
|
33
|
+
You must provide a datetime > the lower bound.
|
34
|
+
LOG
|
35
|
+
end
|
36
|
+
end
|
27
37
|
end
|
data/lib/hoardable/has_many.rb
CHANGED
@@ -26,16 +26,21 @@ module Hoardable
|
|
26
26
|
class_methods do
|
27
27
|
def has_many(*args, &block)
|
28
28
|
options = args.extract_options!
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
hoardable_option = options.delete(:hoardable)
|
30
|
+
options[:extend] = Array(options[:extend]).push(HasManyExtension) if hoardable_option
|
31
|
+
|
32
32
|
super(*args, **options, &block)
|
33
|
+
return unless hoardable_option
|
33
34
|
|
34
35
|
# This hack is needed to force Rails to not use any existing method cache so that the
|
35
|
-
# {HasManyExtension} scope is always used.
|
36
|
+
# {HasManyExtension} scope is always used when using {Hoardable.at}.
|
36
37
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
37
38
|
def #{args.first}
|
38
|
-
|
39
|
+
if Hoardable.instance_variable_get("@at")
|
40
|
+
super.extending
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
39
44
|
end
|
40
45
|
RUBY
|
41
46
|
end
|
data/lib/hoardable/model.rb
CHANGED
@@ -50,12 +50,14 @@ module Hoardable
|
|
50
50
|
define_model_callbacks :versioned, only: :after
|
51
51
|
define_model_callbacks :reverted, only: :after
|
52
52
|
define_model_callbacks :untrashed, only: :after
|
53
|
+
end
|
53
54
|
|
55
|
+
def self.included(base)
|
54
56
|
TracePoint
|
55
57
|
.new(:end) do |trace|
|
56
|
-
next unless
|
58
|
+
next unless base == trace.self
|
57
59
|
|
58
|
-
full_version_class_name = "#{name}#{VERSION_CLASS_SUFFIX}"
|
60
|
+
full_version_class_name = "#{base.name}#{VERSION_CLASS_SUFFIX}"
|
59
61
|
if (namespace_match = full_version_class_name.match(/(.*)::(.*)/))
|
60
62
|
object_namespace = namespace_match[1].constantize
|
61
63
|
version_class_name = namespace_match[2]
|
@@ -64,10 +66,10 @@ module Hoardable
|
|
64
66
|
version_class_name = full_version_class_name
|
65
67
|
end
|
66
68
|
unless Object.const_defined?(full_version_class_name)
|
67
|
-
object_namespace.const_set(version_class_name, Class.new(
|
69
|
+
object_namespace.const_set(version_class_name, Class.new(base) { include VersionModel })
|
68
70
|
end
|
69
|
-
include SourceModel
|
70
|
-
REGISTRY.add(
|
71
|
+
base.class_eval { include SourceModel }
|
72
|
+
REGISTRY.add(base)
|
71
73
|
|
72
74
|
trace.disable
|
73
75
|
end
|
data/lib/hoardable/scopes.rb
CHANGED
@@ -9,13 +9,20 @@ module Hoardable
|
|
9
9
|
included do
|
10
10
|
# By default {Hoardable} only returns instances of the parent table, and not the +versions+ in
|
11
11
|
# the inherited table. This can be bypassed by using the {.include_versions} scope or wrapping
|
12
|
-
# the code in a `Hoardable.at(datetime)` block.
|
12
|
+
# the code in a `Hoardable.at(datetime)` block. When this is a version class that is an STI
|
13
|
+
# model, also scope to them.
|
13
14
|
default_scope do
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
scope =
|
16
|
+
(
|
17
|
+
if (hoardable_at = Hoardable.instance_variable_get("@at"))
|
18
|
+
at(hoardable_at)
|
19
|
+
else
|
20
|
+
exclude_versions
|
21
|
+
end
|
22
|
+
)
|
23
|
+
next scope unless klass == version_class && "type".in?(column_names)
|
24
|
+
|
25
|
+
scope.where(type: superclass.sti_name)
|
19
26
|
end
|
20
27
|
|
21
28
|
# @!scope class
|
@@ -40,20 +40,24 @@ module Hoardable
|
|
40
40
|
end
|
41
41
|
|
42
42
|
before_destroy(if: HOARDABLE_CALLBACKS_ENABLED, unless: HOARDABLE_SAVE_TRASH) do
|
43
|
-
versions.delete_all
|
43
|
+
versions.extending.delete_all
|
44
44
|
end
|
45
45
|
|
46
46
|
after_commit { hoardable_client.unset_hoardable_version_and_event_uuid }
|
47
|
+
end
|
47
48
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
49
|
+
def self.included(base)
|
50
|
+
base.class_eval do
|
51
|
+
# Returns all +versions+ in ascending order of their temporal timeframes.
|
52
|
+
has_many(
|
53
|
+
:versions,
|
54
|
+
-> { order("UPPER(_during) ASC") },
|
55
|
+
dependent: nil,
|
56
|
+
class_name: version_class.to_s,
|
57
|
+
inverse_of: :hoardable_source,
|
58
|
+
foreign_key: :hoardable_id
|
59
|
+
)
|
60
|
+
end
|
57
61
|
end
|
58
62
|
|
59
63
|
# Returns a boolean of whether the record is actually a trashed +version+ cast as an instance of the
|
data/lib/hoardable/version.rb
CHANGED
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.18.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- justin talbott
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-01-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -58,7 +58,7 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0.
|
61
|
+
version: '0.9'
|
62
62
|
- - "<"
|
63
63
|
- !ruby/object:Gem::Version
|
64
64
|
version: '1'
|
@@ -68,7 +68,7 @@ dependencies:
|
|
68
68
|
requirements:
|
69
69
|
- - ">="
|
70
70
|
- !ruby/object:Gem::Version
|
71
|
-
version: '0.
|
71
|
+
version: '0.9'
|
72
72
|
- - "<"
|
73
73
|
- !ruby/object:Gem::Version
|
74
74
|
version: '1'
|
@@ -100,15 +100,14 @@ extensions: []
|
|
100
100
|
extra_rdoc_files: []
|
101
101
|
files:
|
102
102
|
- ".streerc"
|
103
|
-
- ".tool-versions"
|
104
103
|
- CHANGELOG.md
|
105
104
|
- Gemfile
|
106
105
|
- LICENSE.txt
|
107
106
|
- README.md
|
108
107
|
- Rakefile
|
109
|
-
- lib/generators/hoardable/functions/
|
110
|
-
- lib/generators/hoardable/
|
111
|
-
- lib/generators/hoardable/
|
108
|
+
- lib/generators/hoardable/functions/set_hoardable_id.sql
|
109
|
+
- lib/generators/hoardable/install_functions/hoardable_prevent_update_id.sql
|
110
|
+
- lib/generators/hoardable/install_functions/hoardable_version_prevent_update.sql
|
112
111
|
- lib/generators/hoardable/install_generator.rb
|
113
112
|
- lib/generators/hoardable/migration_generator.rb
|
114
113
|
- lib/generators/hoardable/templates/install.rb.erb
|
@@ -159,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
159
158
|
- !ruby/object:Gem::Version
|
160
159
|
version: '0'
|
161
160
|
requirements: []
|
162
|
-
rubygems_version: 3.5.
|
161
|
+
rubygems_version: 3.5.16
|
163
162
|
signing_key:
|
164
163
|
specification_version: 4
|
165
164
|
summary: An ActiveRecord extension for versioning and soft-deletion of records in
|
data/.tool-versions
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
CREATE OR REPLACE FUNCTION hoardable_source_set_id() RETURNS trigger
|
2
|
-
LANGUAGE plpgsql AS
|
3
|
-
$$
|
4
|
-
DECLARE
|
5
|
-
_pk information_schema.constraint_column_usage.column_name%TYPE;
|
6
|
-
_id _pk%TYPE;
|
7
|
-
BEGIN
|
8
|
-
SELECT c.column_name
|
9
|
-
FROM information_schema.table_constraints t
|
10
|
-
JOIN information_schema.constraint_column_usage c
|
11
|
-
ON c.constraint_name = t.constraint_name
|
12
|
-
WHERE c.table_name = TG_TABLE_NAME AND t.constraint_type = 'PRIMARY KEY'
|
13
|
-
LIMIT 1
|
14
|
-
INTO _pk;
|
15
|
-
EXECUTE format('SELECT $1.%I', _pk) INTO _id USING NEW;
|
16
|
-
NEW.hoardable_id = _id;
|
17
|
-
RETURN NEW;
|
18
|
-
END;$$;
|
/data/lib/generators/hoardable/{functions → install_functions}/hoardable_prevent_update_id.sql
RENAMED
File without changes
|
/data/lib/generators/hoardable/{functions → install_functions}/hoardable_version_prevent_update.sql
RENAMED
File without changes
|