hoardable 0.1.2 → 0.1.3
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/Gemfile +1 -0
- data/README.md +4 -1
- data/lib/generators/hoardable/migration_generator.rb +2 -1
- data/lib/hoardable/error.rb +1 -1
- data/lib/hoardable/hoardable.rb +15 -1
- data/lib/hoardable/model.rb +4 -2
- data/lib/hoardable/source_model.rb +22 -1
- data/lib/hoardable/tableoid.rb +21 -1
- data/lib/hoardable/version.rb +1 -1
- data/lib/hoardable/version_model.rb +29 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96952d8928266fc5ae03199a91e85428565ff11385a74851fe61fd3a8eafc881
|
4
|
+
data.tar.gz: 1b9117cbf4ea08325d29212c16580e368a857b12f8c2d20bc346d9e8f5268d59
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e533edf9412339fcc90691fa5d10395265914f71f7f7493c9afc226902cf831db87f69661d565630602e2db815549e4209550420abc44f6a7d179ccb23ba7509
|
7
|
+
data.tar.gz: a8c690b0a3399f3853ed6c65ca7d514af9c82d10569483d7d4bed86c6e7a63d1881ecac61e13d6646facdce3370614dc6c86f88599bc6eb955feba38589c398c
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,9 @@
|
|
3
3
|
Hoardable is an ActiveRecord extension for Ruby 2.6+, Rails 6.1+, and PostgreSQL that allows for
|
4
4
|
versioning and soft-deletion of records through the use of _uni-temporal inherited tables_.
|
5
5
|
|
6
|
-
|
6
|
+
[👉 Documentation](https://www.rubydoc.info/gems/hoardable)
|
7
|
+
|
8
|
+
### huh?
|
7
9
|
|
8
10
|
[Temporal tables](https://en.wikipedia.org/wiki/Temporal_database) are a database design pattern
|
9
11
|
where each row of a table contains data along with one or more time ranges. In the case of this gem,
|
@@ -269,6 +271,7 @@ class Post < ActiveRecord::Base
|
|
269
271
|
Comment
|
270
272
|
.version_class
|
271
273
|
.trashed
|
274
|
+
.where(post_id: id)
|
272
275
|
.with_hoardable_event_uuid(hoardable_event_uuid)
|
273
276
|
.find_each(&:untrash!)
|
274
277
|
end
|
@@ -4,7 +4,8 @@ require 'rails/generators'
|
|
4
4
|
require 'rails/generators/active_record/migration/migration_generator'
|
5
5
|
|
6
6
|
module Hoardable
|
7
|
-
# Generates a migration
|
7
|
+
# Generates a migration to create an inherited uni-temporal table of a model including
|
8
|
+
# {Hoardable::Model}, for the storage of +versions+.
|
8
9
|
class MigrationGenerator < ActiveRecord::Generators::Base
|
9
10
|
source_root File.expand_path('templates', __dir__)
|
10
11
|
include Rails::Generators::Migration
|
data/lib/hoardable/error.rb
CHANGED
data/lib/hoardable/hoardable.rb
CHANGED
@@ -1,13 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# An ActiveRecord extension for keeping versions of records in temporal inherited tables
|
3
|
+
# An +ActiveRecord+ extension for keeping versions of records in uni-temporal inherited tables.
|
4
4
|
module Hoardable
|
5
|
+
# Symbols for use with setting contextual data, when creating versions. See
|
6
|
+
# {file:README.md#tracking-contextual-data README} for more.
|
5
7
|
DATA_KEYS = %i[meta whodunit note event_uuid].freeze
|
8
|
+
# Symbols for use with setting {Hoardable} configuration. See {file:README.md#configuration
|
9
|
+
# README} for more.
|
6
10
|
CONFIG_KEYS = %i[enabled save_trash].freeze
|
7
11
|
|
12
|
+
# @!visibility private
|
8
13
|
VERSION_CLASS_SUFFIX = 'Version'
|
14
|
+
|
15
|
+
# @!visibility private
|
9
16
|
VERSION_TABLE_SUFFIX = "_#{VERSION_CLASS_SUFFIX.tableize}"
|
17
|
+
|
18
|
+
# @!visibility private
|
10
19
|
SAVE_TRASH_ENABLED = -> { Hoardable.save_trash }.freeze
|
20
|
+
|
21
|
+
# @!visibility private
|
11
22
|
DURING_QUERY = '_during @> ?::timestamp'
|
12
23
|
|
13
24
|
@context = {}
|
@@ -36,6 +47,9 @@ module Hoardable
|
|
36
47
|
end
|
37
48
|
end
|
38
49
|
|
50
|
+
# This is a general use method for setting {DATA_KEYS} or {CONFIG_KEYS} around a scoped block.
|
51
|
+
#
|
52
|
+
# @param hash [Hash] Options and contextual data to set within a block
|
39
53
|
def with(hash)
|
40
54
|
current_config = @config
|
41
55
|
current_context = @context
|
data/lib/hoardable/model.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Hoardable
|
4
|
-
# This concern
|
5
|
-
# the
|
4
|
+
# This concern is the main entrypoint for using {Hoardable}. When included into an +ActiveRecord+
|
5
|
+
# class, it dynamically generates the +Version+ variant of that class (with {VersionModel}) and
|
6
|
+
# includes the {Hoardable} API methods and relationships on the source model class (through
|
7
|
+
# {SourceModel}).
|
6
8
|
module Model
|
7
9
|
extend ActiveSupport::Concern
|
8
10
|
|
@@ -1,11 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Hoardable
|
4
|
-
# This concern contains the relationships, callbacks, and API methods for
|
4
|
+
# This concern contains the {Hoardable} relationships, callbacks, and API methods for an
|
5
|
+
# +ActiveRecord+. It is included by {Hoardable::Model} after the dynamic generation of the
|
6
|
+
# +Version+ class variant.
|
5
7
|
module SourceModel
|
6
8
|
extend ActiveSupport::Concern
|
7
9
|
|
8
10
|
class_methods do
|
11
|
+
# The dynamically generated +Version+ class for this model.
|
9
12
|
def version_class
|
10
13
|
"#{name}#{VERSION_CLASS_SUFFIX}".constantize
|
11
14
|
end
|
@@ -19,10 +22,17 @@ module Hoardable
|
|
19
22
|
before_destroy :delete_hoardable_versions, if: :hoardable_callbacks_enabled, unless: SAVE_TRASH_ENABLED
|
20
23
|
after_commit :unset_hoardable_version_and_event_uuid
|
21
24
|
|
25
|
+
# This will contain the +Version+ class instance for use within +versioned+, +reverted+, and
|
26
|
+
# +untrashed+ callbacks.
|
22
27
|
attr_reader :hoardable_version
|
23
28
|
|
29
|
+
# @!attribute [r] hoardable_event_uuid
|
30
|
+
# @return [String] A postgres UUID that represents the +version+’s +ActiveRecord+ database transaction
|
31
|
+
# @!attribute [r] hoardable_operation
|
32
|
+
# @return [String] The database operation that created the +version+ - either +update+ or +delete+.
|
24
33
|
delegate :hoardable_event_uuid, :hoardable_operation, to: :hoardable_version, allow_nil: true
|
25
34
|
|
35
|
+
# Returns all +versions+ in ascending order of their temporal timeframes.
|
26
36
|
has_many(
|
27
37
|
:versions, -> { order(:_during) },
|
28
38
|
dependent: nil,
|
@@ -31,16 +41,27 @@ module Hoardable
|
|
31
41
|
)
|
32
42
|
end
|
33
43
|
|
44
|
+
# Returns a boolean of whether the record is actually a trashed +version+.
|
45
|
+
#
|
46
|
+
# @return [Boolean]
|
34
47
|
def trashed?
|
35
48
|
versions.trashed.limit(1).order(_during: :desc).first&.send(:hoardable_source_attributes) == attributes
|
36
49
|
end
|
37
50
|
|
51
|
+
# Returns the +version+ at the supplied +datetime+ or +time+. It will return +self+ if there is
|
52
|
+
# none. This will raise an error if you try to find a version in the future.
|
53
|
+
#
|
54
|
+
# @param datetime [DateTime, Time]
|
38
55
|
def at(datetime)
|
39
56
|
raise(Error, 'Future state cannot be known') if datetime.future?
|
40
57
|
|
41
58
|
versions.find_by(DURING_QUERY, datetime) || self
|
42
59
|
end
|
43
60
|
|
61
|
+
# If a version is found at the supplied datetime, it will +revert!+ to it and return it. This
|
62
|
+
# will raise an error if you try to revert to a version in the future.
|
63
|
+
#
|
64
|
+
# @param datetime [DateTime, Time]
|
44
65
|
def revert_to!(datetime)
|
45
66
|
return unless (version = at(datetime))
|
46
67
|
|
data/lib/hoardable/tableoid.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Hoardable
|
4
|
-
# This concern provides support for PostgreSQL
|
4
|
+
# This concern provides support for PostgreSQL’s tableoid system column to {SourceModel}.
|
5
5
|
module Tableoid
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
|
+
# @!visibility private
|
8
9
|
TABLEOID_AREL_CONDITIONS = lambda do |arel_table, condition|
|
9
10
|
arel_table[:tableoid].send(
|
10
11
|
condition,
|
@@ -13,13 +14,32 @@ module Hoardable
|
|
13
14
|
end.freeze
|
14
15
|
|
15
16
|
included do
|
17
|
+
# @!visibility private
|
16
18
|
attr_writer :tableoid
|
17
19
|
|
20
|
+
# By default, {Hoardable} only returns instances of the parent table, and not the +versions+
|
21
|
+
# in the inherited table.
|
18
22
|
default_scope { where(TABLEOID_AREL_CONDITIONS.call(arel_table, :eq)) }
|
23
|
+
|
24
|
+
# @!scope class
|
25
|
+
# @!method include_versions
|
26
|
+
# @return [ActiveRecord<Object>]
|
27
|
+
#
|
28
|
+
# Returns +versions+ along with instances of the source models, all cast as instances of the
|
29
|
+
# source model’s class.
|
19
30
|
scope :include_versions, -> { unscope(where: [:tableoid]) }
|
31
|
+
|
32
|
+
# @!scope class
|
33
|
+
# @!method versions
|
34
|
+
# @return [ActiveRecord<Object>]
|
35
|
+
#
|
36
|
+
# Returns only +versions+ of the parent +ActiveRecord+ class, cast as instances of the source
|
37
|
+
# model’s class.
|
20
38
|
scope :versions, -> { include_versions.where(TABLEOID_AREL_CONDITIONS.call(arel_table, :not_eq)) }
|
21
39
|
end
|
22
40
|
|
41
|
+
private
|
42
|
+
|
23
43
|
def tableoid
|
24
44
|
connection.execute("SELECT oid FROM pg_class WHERE relname = '#{table_name}'")[0]['oid']
|
25
45
|
end
|
data/lib/hoardable/version.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Hoardable
|
4
|
-
# This concern is included into the dynamically generated Version
|
4
|
+
# This concern is included into the dynamically generated +Version+ kind of the parent
|
5
|
+
# +ActiveRecord+ class.
|
5
6
|
module VersionModel
|
6
7
|
extend ActiveSupport::Concern
|
7
8
|
|
8
9
|
included do
|
9
10
|
hoardable_source_key = superclass.model_name.i18n_key
|
11
|
+
|
12
|
+
# A +version+ belongs to it’s parent +ActiveRecord+ source.
|
10
13
|
belongs_to hoardable_source_key, inverse_of: :versions
|
11
14
|
alias_method :hoardable_source, hoardable_source_key
|
12
15
|
|
@@ -19,15 +22,35 @@ module Hoardable
|
|
19
22
|
|
20
23
|
before_create :assign_temporal_tsrange
|
21
24
|
|
25
|
+
# @!scope class
|
26
|
+
# @!method trashed
|
27
|
+
# @return [ActiveRecord<Object>]
|
28
|
+
#
|
29
|
+
# Returns only trashed +versions+ that are orphans.
|
22
30
|
scope :trashed, lambda {
|
23
31
|
left_outer_joins(hoardable_source_key)
|
24
32
|
.where(superclass.table_name => { id: nil })
|
25
33
|
.where(_operation: 'delete')
|
26
34
|
}
|
35
|
+
|
36
|
+
# @!scope class
|
37
|
+
# @!method at
|
38
|
+
# @return [ActiveRecord<Object>]
|
39
|
+
#
|
40
|
+
# Returns +versions+ that were valid at the supplied +datetime+ or +time+.
|
27
41
|
scope :at, ->(datetime) { where(DURING_QUERY, datetime) }
|
42
|
+
|
43
|
+
# @!scope class
|
44
|
+
# @!method with_hoardable_event_uuid
|
45
|
+
# @return [ActiveRecord<Object>]
|
46
|
+
#
|
47
|
+
# Returns all +versions+ that were created as part of the same +ActiveRecord+ database
|
48
|
+
# transaction of the supplied +event_uuid+. Useful in +reverted+ and +untrashed+ callbacks.
|
28
49
|
scope :with_hoardable_event_uuid, ->(event_uuid) { where(_event_uuid: event_uuid) }
|
29
50
|
end
|
30
51
|
|
52
|
+
# Reverts the parent +ActiveRecord+ instance to the saved attributes of this +version+. Raises
|
53
|
+
# an error if the version is trashed.
|
31
54
|
def revert!
|
32
55
|
raise(Error, 'Version is trashed, cannot revert') unless hoardable_operation == 'update'
|
33
56
|
|
@@ -40,6 +63,8 @@ module Hoardable
|
|
40
63
|
end
|
41
64
|
end
|
42
65
|
|
66
|
+
# Inserts a trashed +version+ back into its parent +ActiveRecord+ table with its original
|
67
|
+
# primary key. Raises an error if the version is not trashed.
|
43
68
|
def untrash!
|
44
69
|
raise(Error, 'Version is not trashed, cannot untrash') unless hoardable_operation == 'delete'
|
45
70
|
|
@@ -59,6 +84,9 @@ module Hoardable
|
|
59
84
|
end
|
60
85
|
end
|
61
86
|
|
87
|
+
# Returns the +ActiveRecord+
|
88
|
+
# {https://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-changes changes} that
|
89
|
+
# were present during version creation.
|
62
90
|
def changes
|
63
91
|
_data&.dig('changes')
|
64
92
|
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.1.
|
4
|
+
version: 0.1.3
|
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-08-
|
11
|
+
date: 2022-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|