hoardable 0.1.2 → 0.1.3

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