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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c73d162f69d7ff2984571b7e0aefa256357a835c2ecb22d1288a362dce38d73
4
- data.tar.gz: 4b250ebb2bf536f94d3cd8e65f0bd884579bd6fc5d8fd89f5c9846554c4d0e7a
3
+ metadata.gz: 96952d8928266fc5ae03199a91e85428565ff11385a74851fe61fd3a8eafc881
4
+ data.tar.gz: 1b9117cbf4ea08325d29212c16580e368a857b12f8c2d20bc346d9e8f5268d59
5
5
  SHA512:
6
- metadata.gz: 4503897e596e1694a49009aa3e964a86a8e317169c590a1079b075345b9116fb4af50abb68b17a4ea73c7bf1570f56906c11f5e57dbaf84044ab8ac11ca942f2
7
- data.tar.gz: a8e2780685b3d0a00757c44ca6a38f58571ef617b1543b7ee8b8f3dcef18c9fed5f7c3e10f7d31677afe0fccafeffca4348d70b5afe3706ead0438488bf57166
6
+ metadata.gz: e533edf9412339fcc90691fa5d10395265914f71f7f7493c9afc226902cf831db87f69661d565630602e2db815549e4209550420abc44f6a7d179ccb23ba7509
7
+ data.tar.gz: a8c690b0a3399f3853ed6c65ca7d514af9c82d10569483d7d4bed86c6e7a63d1881ecac61e13d6646facdce3370614dc6c86f88599bc6eb955feba38589c398c
data/Gemfile CHANGED
@@ -8,5 +8,6 @@ gem 'rake', '~> 13.0'
8
8
  gem 'rubocop', '~> 1.21'
9
9
  gem 'rubocop-minitest', '~> 0.20'
10
10
  gem 'rubocop-rake', '~> 0.6'
11
+ gem 'yard', '~> 0.9'
11
12
 
12
13
  gemspec
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
- #### huh?
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 for an inherited temporal table of a model including {Hoardable::Model}
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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hoardable
4
- # A subclass of StandardError for general use within Hoardable
4
+ # A subclass of +StandardError+ for general use within {Hoardable}.
5
5
  class Error < StandardError; end
6
6
  end
@@ -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
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hoardable
4
- # This concern dynamically generates the Version variant of the class module Model and includes
5
- # the API methods and relationships on the source model
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 a Source model
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
 
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hoardable
4
- # This concern provides support for PostgreSQL's tableoid system column
4
+ # This concern provides support for PostgreSQLs 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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hoardable
4
- VERSION = '0.1.2'
4
+ VERSION = '0.1.3'
5
5
  end
@@ -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 models.
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.2
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-01 00:00:00.000000000 Z
11
+ date: 2022-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord