hoardable 0.3.0 → 0.5.0

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: 50086ef99aac41454b28ab8bff2e8abf94d7870b371da0c706c477e0b296ffc3
4
- data.tar.gz: 6e6d4ab40470bbfb93e23e96fdbb2d8b086878d24e55d499ac368523125e5397
3
+ metadata.gz: 4c4bc9bc4cdd73263a6afe5551b59f085cbf7dc3877e472df8abdba62f5fcbd6
4
+ data.tar.gz: 7aa8ea828b2f6083a80c93e34a1fcc5756508db15f2a317dc95bfea2ae8b1e28
5
5
  SHA512:
6
- metadata.gz: 747ac52845c950eb655cb7b0af22794dda49ed08e2b0f1aee472595fd2c63b1f886d4f516e686807607efe4946ca588792913ea92ec1c3152a44c23f8cd84487
7
- data.tar.gz: 3460ccd66ebe6eed7cdb129bca6150696b82ad9f8613965e80b08fb458ac6e2b8b644e662036533f73f95f52b97a10bf50f2782f76d3fd6671f23f9c2b58df08
6
+ metadata.gz: 915cba36e937b34667b2ad31ae8dd780224a417669a6bb3c1abdfb2c8a19c10525e7d29a0a4f264aa475362f9b823104bd5a40d71bfc01848db2eb6a463f7c1d
7
+ data.tar.gz: ab0faaab94b03c5b6452b7aad505e16b4bb98538ae8649cfd65e17d397e3d917e6e61c752ed39b820dc30d72503e993ec5d6fbe353391c29025759a4bedeff74
data/.rubocop.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 2.6
3
3
  NewCops: enable
4
+ SuggestExtensions: false
4
5
 
5
6
  Layout/LineLength:
6
7
  Max: 120
@@ -8,3 +9,6 @@ Layout/LineLength:
8
9
  Metrics/ClassLength:
9
10
  Exclude:
10
11
  - 'test/**/*.rb'
12
+
13
+ Style/DocumentDynamicEvalDefinition:
14
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.0] - 2022-09-25
4
+
5
+ - **Breaking Change** - Untrashing a version will now insert a version for the untrash event with
6
+ it's own temporal timespan. This simplifies the ability to query versions temporarily for when
7
+ they were trashed or not. This changes, but corrects, temporal query results using `.at`.
8
+
9
+ - **Breaking Change** - Because of the above, a new operation enum value of "insert" was added. If
10
+ you already have the `hoardable_operation` enum in your PostgreSQL schema, you can add it by
11
+ executing the following SQL in a new migration: `ALTER TYPE hoardable_operation ADD VALUE
12
+ 'insert';`.
13
+
14
+ ## [0.4.0] - 2022-09-24
15
+
16
+ - **Breaking Change** - Trashed versions now pull from the same postgres sequenced used by the
17
+ source model’s table.
18
+
3
19
  ## [0.1.0] - 2022-07-23
4
20
 
5
21
  - Initial release
data/README.md CHANGED
@@ -69,7 +69,7 @@ Rails 7.
69
69
  ### Overview
70
70
 
71
71
  Once you include `Hoardable::Model` into a model, it will dynamically generate a "Version" subclass
72
- of that model. As we continue our example above:
72
+ of that model. As we continue our example from above:
73
73
 
74
74
  ```
75
75
  $ irb
@@ -79,8 +79,8 @@ $ irb
79
79
  => PostVersion(id: integer, body: text, user_id: integer, created_at: datetime, _data: jsonb, _during: tsrange, post_id: integer)
80
80
  ```
81
81
 
82
- A `Post` now `has_many :versions`. Whenever an update and deletion of a `Post` occurs, a version is
83
- created (by default):
82
+ A `Post` now `has_many :versions`. With the default configuration, whenever an update and deletion
83
+ of a `Post` occurs, a version is created:
84
84
 
85
85
  ```ruby
86
86
  post = Post.create!(title: "Title")
@@ -97,9 +97,29 @@ Post.find(post.id) # raises ActiveRecord::RecordNotFound
97
97
  Each `PostVersion` has access to the same attributes, relationships, and other model behavior that
98
98
  `Post` has, but as a read-only record.
99
99
 
100
- If you ever need to revert to a specific version, you can call `version.revert!` on it. If you would
101
- like to untrash a specific version, you can call `version.untrash!` on it. This will re-insert the
102
- model in the parent class’ table with it’s original primary key.
100
+ If you ever need to revert to a specific version, you can call `version.revert!` on it.
101
+
102
+ ``` ruby
103
+ post = Post.create!(title: "Title")
104
+ post.update!(title: "Whoops")
105
+ post.versions.last.revert!
106
+ post.title # => "Title"
107
+ ```
108
+
109
+ If you would like to untrash a specific version, you can call `version.untrash!` on it. This will
110
+ re-insert the model in the parent class’s table with it’s original primary key.
111
+
112
+ ```ruby
113
+ post = Post.create!(title: "Title")
114
+ post.id # => 1
115
+ post.destroy!
116
+ post.versions.size # => 1
117
+ Post.find(post.id) # raises ActiveRecord::RecordNotFound
118
+ trashed_post = post.versions.trashed.last
119
+ trashed_post.id # => 2
120
+ trashed_post.untrash!
121
+ Post.find(post.id) # #<Post>
122
+ ```
103
123
 
104
124
  ### Querying and Temporal Lookup
105
125
 
@@ -112,20 +132,30 @@ post.versions.where(user_id: Current.user.id, body: "Cool!")
112
132
  If you want to look-up the version of a record at a specific time, you can use the `.at` method:
113
133
 
114
134
  ```ruby
115
- post.at(1.day.ago) # => #<PostVersion:0x000000010d44fa30>
116
- # or
117
- PostVersion.at(1.day.ago).find_by(post_id: post.id) # => #<PostVersion:0x000000010d44fa30>
135
+ post.at(1.day.ago) # => #<PostVersion>
136
+ # or you can use the scope on the version model class
137
+ PostVersion.at(1.day.ago).find_by(post_id: post.id) # => #<PostVersion>
138
+ ```
139
+
140
+ The source model class also has an `.at` method:
141
+
142
+ ``` ruby
143
+ Post.at(1.day.ago) # => [#<Post>, #<Post>]
118
144
  ```
119
145
 
120
- _Note:_ A `Version` is not created upon initial parent model creation. If you would like to
121
- accurately capture the valid temporal frame of the first version, make sure your model’s table has a
122
- `created_at` timestamp field.
146
+ This will return an ActiveRecord scoped query of all `Posts` and `PostVersions` that were valid at
147
+ that time, all cast as instances of `Post`.
123
148
 
124
- By default, `hoardable` will keep copies of records you have destroyed. You can query for them as
125
- well:
149
+ _Note:_ A `Version` is not created upon initial parent model creation. To accurately track the
150
+ beginning of the first temporal period, you will need to ensure the source model table has a
151
+ `created_at` timestamp column.
152
+
153
+ By default, `hoardable` will keep copies of records you have destroyed. You can query them
154
+ specifically with:
126
155
 
127
156
  ```ruby
128
157
  PostVersion.trashed
158
+ Post.version_class.trashed # <- same thing as above
129
159
  ```
130
160
 
131
161
  _Note:_ Creating an inherited table does not copy over the indexes from the parent table. If you
@@ -146,7 +176,7 @@ choosing.
146
176
  One convenient way to assign contextual data to these is by defining a proc in an initializer, i.e.:
147
177
 
148
178
  ```ruby
149
- # config/initiailzers/hoardable.rb
179
+ # config/initializers/hoardable.rb
150
180
  Hoardable.whodunit = -> { Current.user&.id }
151
181
 
152
182
  # somewhere in your app code
@@ -177,7 +207,7 @@ class ApplicationController < ActionController::Base
177
207
  Hoardable.with(whodunit: current_user.id, meta: { request_uuid: request.uuid }) do
178
208
  yield
179
209
  end
180
- # `Hoardable.whodunit` is back to nil or the previously set value
210
+ # `Hoardable.whodunit` and `Hoardable.meta` are back to nil or their previously set values
181
211
  end
182
212
  end
183
213
  ```
@@ -227,7 +257,7 @@ end
227
257
 
228
258
  ### Configuration
229
259
 
230
- There are three configurable options currently:
260
+ The configurable options are:
231
261
 
232
262
  ```ruby
233
263
  Hoardable.enabled # => default true
@@ -278,18 +308,18 @@ If a model-level option exists, it will use that. Otherwise, it will fall back t
278
308
 
279
309
  ### Relationships
280
310
 
281
- As in life, sometimes relationships can be hard. `hoardable` is still working out best practices and
282
- features in this area, but here are a couple pointers.
311
+ As in life, sometimes relationships can be hard, but here are some pointers on handling associations
312
+ with `Hoardable` considerations.
283
313
 
284
- Sometimes you’ll have a record that belongs to a record that you’ll trash. Now the child record’s
285
- foreign key will point to the non-existent trashed version of the parent. If you would like this
286
- `belongs_to` relationship to always resolve to the parent as if it was not trashed, you can include
287
- the `include_versions` scope on the relationship definition:
314
+ Sometimes you’ll have a record that belongs to a parent record that you’ll trash. Now the child
315
+ record’s foreign key will point to the non-existent trashed version of the parent. If you would like
316
+ to have `belongs_to` resolve to the trashed parent model in this case, you can use
317
+ `belongs_to_trashable` in place of `belongs_to`:
288
318
 
289
319
  ```ruby
290
320
  class Comment
291
- include Hoardable::Model
292
- belongs_to :post, -> { include_versions } # `Post` also includes `Hoardable::Model`
321
+ include Hoardable::Associations # <- This includes is not required if this model already includes `Hoardable::Model`
322
+ belongs_to_trashable :post, -> { where(status: 'published') }, class_name: 'Article' # <- Accepts normal `belongs_to` arguments
293
323
  end
294
324
  ```
295
325
 
@@ -317,7 +347,8 @@ end
317
347
 
318
348
  If there are models that might be related to versions that are trashed or otherwise, and/or might
319
349
  trashed themselves, you can bypass the inherited tables query handling altogether by using the
320
- `return_everything` configuration variable in `Hoardable.with`:
350
+ `return_everything` configuration variable in `Hoardable.with`. This will ensure that you always see
351
+ all records, including update and trashed versions.
321
352
 
322
353
  ```ruby
323
354
  post.destroy!
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Create<%= class_name.singularize %>Versions < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
4
4
  def change
5
- create_enum :hoardable_operation, %w[update delete]
5
+ create_enum :hoardable_operation, %w[update delete insert]
6
6
  create_table :<%= singularized_table_name %>_versions, id: false, options: 'INHERITS (<%= table_name %>)' do |t|
7
7
  t.jsonb :_data
8
8
  t.tsrange :_during, null: false
@@ -11,7 +11,7 @@ class Create<%= class_name.singularize %>Versions < ActiveRecord::Migration[<%=
11
11
  SELECT 1 FROM pg_type t
12
12
  WHERE t.typname = 'hoardable_operation'
13
13
  ) THEN
14
- CREATE TYPE hoardable_operation AS ENUM ('update', 'delete');
14
+ CREATE TYPE hoardable_operation AS ENUM ('update', 'delete', 'insert');
15
15
  END IF;
16
16
  END
17
17
  $$;
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hoardable
4
+ # This concern contains +ActiveRecord+ association considerations for {SourceModel}. It is
5
+ # included by {Model} but can be included on it’s own for models that +belongs_to+ a Hoardable
6
+ # {Model}.
7
+ module Associations
8
+ extend ActiveSupport::Concern
9
+
10
+ class_methods do
11
+ # A wrapper for +ActiveRecord+’s +belongs_to+ that allows for falling back to the most recent
12
+ # trashed +version+, in the case that the related source has been trashed.
13
+ def belongs_to_trashable(name, scope = nil, **options)
14
+ belongs_to(name, scope, **options)
15
+
16
+ trashable_relationship_name = "trashable_#{name}"
17
+
18
+ define_method(trashable_relationship_name) do
19
+ source_reflection = self.class.reflections[name.to_s]
20
+ version_class = source_reflection.klass.version_class
21
+ version_class.trashed.only_most_recent.find_by(
22
+ version_class.hoardable_source_foreign_key => source_reflection.foreign_key
23
+ )
24
+ end
25
+
26
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
27
+ def #{name}
28
+ super || #{trashable_relationship_name}
29
+ end
30
+ RUBY
31
+ end
32
+ end
33
+ end
34
+ end
@@ -5,18 +5,19 @@ module Hoardable
5
5
  # Symbols for use with setting contextual data, when creating versions. See
6
6
  # {file:README.md#tracking-contextual-data README} for more.
7
7
  DATA_KEYS = %i[meta whodunit note event_uuid].freeze
8
+
8
9
  # Symbols for use with setting {Hoardable} configuration. See {file:README.md#configuration
9
10
  # README} for more.
10
11
  CONFIG_KEYS = %i[enabled version_updates save_trash return_everything].freeze
11
12
 
12
- # @!visibility private
13
13
  VERSION_CLASS_SUFFIX = 'Version'
14
+ private_constant :VERSION_CLASS_SUFFIX
14
15
 
15
- # @!visibility private
16
16
  VERSION_TABLE_SUFFIX = "_#{VERSION_CLASS_SUFFIX.tableize}"
17
+ private_constant :VERSION_TABLE_SUFFIX
17
18
 
18
- # @!visibility private
19
19
  DURING_QUERY = '_during @> ?::timestamp'
20
+ private_constant :DURING_QUERY
20
21
 
21
22
  @context = {}
22
23
  @config = CONFIG_KEYS.to_h do |key|
@@ -44,7 +45,8 @@ module Hoardable
44
45
  end
45
46
  end
46
47
 
47
- # This is a general use method for setting {DATA_KEYS} or {CONFIG_KEYS} around a scoped block.
48
+ # This is a general use method for setting {file:README.md#tracking-contextual-data Contextual
49
+ # Data} or {file:README.md#configuration Configuration} around a block.
48
50
  #
49
51
  # @param hash [Hash] config and contextual data to set within a block
50
52
  def with(hash)
@@ -21,10 +21,10 @@ module Hoardable
21
21
  # @return [Hash]
22
22
  def hoardable_config(hash = nil)
23
23
  if hash
24
- @_hoardable_config = hash.slice(*Hoardable::CONFIG_KEYS)
24
+ @_hoardable_config = hash.slice(*CONFIG_KEYS)
25
25
  else
26
26
  @_hoardable_config ||= {}
27
- Hoardable::CONFIG_KEYS.to_h do |key|
27
+ CONFIG_KEYS.to_h do |key|
28
28
  [key, @_hoardable_config.key?(key) ? @_hoardable_config[key] : Hoardable.send(key)]
29
29
  end
30
30
  end
@@ -37,7 +37,7 @@ module Hoardable
37
37
  # {CONFIG_KEYS}
38
38
  def with_hoardable_config(hash)
39
39
  current_config = @_hoardable_config
40
- @_hoardable_config = hash.slice(*Hoardable::CONFIG_KEYS)
40
+ @_hoardable_config = hash.slice(*CONFIG_KEYS)
41
41
  yield
42
42
  ensure
43
43
  @_hoardable_config = current_config
@@ -45,6 +45,7 @@ module Hoardable
45
45
  end
46
46
 
47
47
  included do
48
+ include Associations
48
49
  define_model_callbacks :versioned
49
50
  define_model_callbacks :reverted, only: :after
50
51
  define_model_callbacks :untrashed, only: :after
@@ -34,18 +34,33 @@ module Hoardable
34
34
 
35
35
  # Returns all +versions+ in ascending order of their temporal timeframes.
36
36
  has_many(
37
- :versions, -> { order(:_during) },
37
+ :versions, -> { order('UPPER(_during) ASC') },
38
38
  dependent: nil,
39
39
  class_name: version_class.to_s,
40
40
  inverse_of: model_name.i18n_key
41
41
  )
42
+
43
+ # @!scope class
44
+ # @!method at
45
+ # @return [ActiveRecord<Object>]
46
+ #
47
+ # Returns instances of the source model and versions that were valid at the supplied
48
+ # +datetime+ or +time+, all cast as instances of the source model.
49
+ scope :at, lambda { |datetime|
50
+ versioned = version_class.at(datetime)
51
+ trashed = version_class.trashed_at(datetime)
52
+ foreign_key = version_class.hoardable_source_foreign_key
53
+ include_versions.where(id: versioned.select('id')).or(
54
+ where.not(id: versioned.select(foreign_key)).where.not(id: trashed.select(foreign_key))
55
+ )
56
+ }
42
57
  end
43
58
 
44
59
  # Returns a boolean of whether the record is actually a trashed +version+.
45
60
  #
46
61
  # @return [Boolean]
47
62
  def trashed?
48
- versions.trashed.limit(1).order(_during: :desc).first&.id == id
63
+ versions.trashed.only_most_recent.first&.hoardable_source_foreign_id == id
49
64
  end
50
65
 
51
66
  # Returns the +version+ at the supplied +datetime+ or +time+. It will return +self+ if there is
@@ -83,15 +98,19 @@ module Hoardable
83
98
  end
84
99
 
85
100
  def insert_hoardable_version_on_update(&block)
86
- insert_hoardable_version('update', attributes_before_type_cast.without('id'), &block)
101
+ insert_hoardable_version('update', &block)
87
102
  end
88
103
 
89
104
  def insert_hoardable_version_on_destroy(&block)
90
- insert_hoardable_version('delete', attributes_before_type_cast, &block)
105
+ insert_hoardable_version('delete', &block)
106
+ end
107
+
108
+ def insert_hoardable_version_on_untrashed
109
+ initialize_hoardable_version('insert').save(validate: false, touch: false)
91
110
  end
92
111
 
93
- def insert_hoardable_version(operation, attrs)
94
- @hoardable_version = initialize_hoardable_version(operation, attrs)
112
+ def insert_hoardable_version(operation)
113
+ @hoardable_version = initialize_hoardable_version(operation)
95
114
  run_callbacks(:versioned) do
96
115
  yield
97
116
  hoardable_version.save(validate: false, touch: false)
@@ -102,9 +121,9 @@ module Hoardable
102
121
  Thread.current[:hoardable_event_uuid] ||= ActiveRecord::Base.connection.query('SELECT gen_random_uuid();')[0][0]
103
122
  end
104
123
 
105
- def initialize_hoardable_version(operation, attrs)
124
+ def initialize_hoardable_version(operation)
106
125
  versions.new(
107
- attrs.merge(
126
+ attributes_before_type_cast.without('id').merge(
108
127
  changes.transform_values { |h| h[0] },
109
128
  {
110
129
  _event_uuid: find_or_initialize_hoardable_event_uuid,
@@ -5,13 +5,13 @@ module Hoardable
5
5
  module Tableoid
6
6
  extend ActiveSupport::Concern
7
7
 
8
- # @!visibility private
9
8
  TABLEOID_AREL_CONDITIONS = lambda do |arel_table, condition|
10
9
  arel_table[:tableoid].send(
11
10
  condition,
12
11
  Arel::Nodes::NamedFunction.new('CAST', [Arel::Nodes::Quoted.new(arel_table.name).as('regclass')])
13
12
  )
14
13
  end.freeze
14
+ private_constant :TABLEOID_AREL_CONDITIONS
15
15
 
16
16
  included do
17
17
  # @!visibility private
@@ -24,7 +24,7 @@ module Hoardable
24
24
  if hoardable_config[:return_everything]
25
25
  where(nil)
26
26
  else
27
- where(TABLEOID_AREL_CONDITIONS.call(arel_table, :eq))
27
+ exclude_versions
28
28
  end
29
29
  end
30
30
 
@@ -43,6 +43,14 @@ module Hoardable
43
43
  # Returns only +versions+ of the parent +ActiveRecord+ class, cast as instances of the source
44
44
  # model’s class.
45
45
  scope :versions, -> { include_versions.where(TABLEOID_AREL_CONDITIONS.call(arel_table, :not_eq)) }
46
+
47
+ # @!scope class
48
+ # @!method exclude_versions
49
+ # @return [ActiveRecord<Object>]
50
+ #
51
+ # Excludes +versions+ of the parent +ActiveRecord+ class. This is included by default in the
52
+ # source model’s +default_scope+.
53
+ scope :exclude_versions, -> { where(TABLEOID_AREL_CONDITIONS.call(arel_table, :eq)) }
46
54
  end
47
55
 
48
56
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hoardable
4
- VERSION = '0.3.0'
4
+ VERSION = '0.5.0'
5
5
  end
@@ -6,6 +6,13 @@ module Hoardable
6
6
  module VersionModel
7
7
  extend ActiveSupport::Concern
8
8
 
9
+ class_methods do
10
+ # Returns the foreign column that holds the reference to the source model of the version.
11
+ def hoardable_source_foreign_key
12
+ @hoardable_source_foreign_key ||= "#{superclass.model_name.i18n_key}_id"
13
+ end
14
+ end
15
+
9
16
  included do
10
17
  hoardable_source_key = superclass.model_name.i18n_key
11
18
 
@@ -13,7 +20,7 @@ module Hoardable
13
20
  belongs_to hoardable_source_key, inverse_of: :versions
14
21
  alias_method :hoardable_source, hoardable_source_key
15
22
 
16
- self.table_name = "#{table_name.singularize}#{Hoardable::VERSION_TABLE_SUFFIX}"
23
+ self.table_name = "#{table_name.singularize}#{VERSION_TABLE_SUFFIX}"
17
24
 
18
25
  alias_method :readonly?, :persisted?
19
26
  alias_attribute :hoardable_operation, :_operation
@@ -26,7 +33,7 @@ module Hoardable
26
33
  # @!method trashed
27
34
  # @return [ActiveRecord<Object>]
28
35
  #
29
- # Returns only trashed +versions+ that are orphans.
36
+ # Returns only trashed +versions+ that are currently orphans.
30
37
  scope :trashed, lambda {
31
38
  left_outer_joins(hoardable_source_key)
32
39
  .where(superclass.table_name => { id: nil })
@@ -38,7 +45,14 @@ module Hoardable
38
45
  # @return [ActiveRecord<Object>]
39
46
  #
40
47
  # Returns +versions+ that were valid at the supplied +datetime+ or +time+.
41
- scope :at, ->(datetime) { where(DURING_QUERY, datetime) }
48
+ scope :at, ->(datetime) { where(_operation: %w[delete update]).where(DURING_QUERY, datetime) }
49
+
50
+ # @!scope class
51
+ # @!method trashed_at
52
+ # @return [ActiveRecord<Object>]
53
+ #
54
+ # Returns +versions+ that were trashed at the supplied +datetime+ or +time+.
55
+ scope :trashed_at, ->(datetime) { where(_operation: 'insert').where(DURING_QUERY, datetime) }
42
56
 
43
57
  # @!scope class
44
58
  # @!method with_hoardable_event_uuid
@@ -47,6 +61,13 @@ module Hoardable
47
61
  # Returns all +versions+ that were created as part of the same +ActiveRecord+ database
48
62
  # transaction of the supplied +event_uuid+. Useful in +reverted+ and +untrashed+ callbacks.
49
63
  scope :with_hoardable_event_uuid, ->(event_uuid) { where(_event_uuid: event_uuid) }
64
+
65
+ # @!scope class
66
+ # @!method only_most_recent
67
+ # @return [ActiveRecord<Object>]
68
+ #
69
+ # Returns a limited +ActiveRecord+ scope of only the most recent version.
70
+ scope :only_most_recent, -> { limit(1).reorder('UPPER(_during) DESC') }
50
71
  end
51
72
 
52
73
  # Reverts the parent +ActiveRecord+ instance to the saved attributes of this +version+. Raises
@@ -70,8 +91,9 @@ module Hoardable
70
91
 
71
92
  transaction do
72
93
  superscope = self.class.superclass.unscoped
73
- superscope.insert(untrashable_hoardable_source_attributes)
94
+ superscope.insert(hoardable_source_attributes.merge('id' => hoardable_source_foreign_id))
74
95
  superscope.find(hoardable_source_foreign_id).tap do |untrashed|
96
+ untrashed.send('insert_hoardable_version_on_untrashed')
75
97
  untrashed.instance_variable_set(:@hoardable_version, self)
76
98
  untrashed.run_callbacks(:untrashed)
77
99
  end
@@ -91,13 +113,14 @@ module Hoardable
91
113
  _data&.dig('changes')
92
114
  end
93
115
 
116
+ # Returns the foreign reference that represents the source model of the version.
117
+ def hoardable_source_foreign_id
118
+ @hoardable_source_foreign_id ||= public_send(hoardable_source_foreign_key)
119
+ end
120
+
94
121
  private
95
122
 
96
- def untrashable_hoardable_source_attributes
97
- hoardable_source_attributes.merge('id' => hoardable_source_foreign_id).tap do |hash|
98
- hash['updated_at'] = Time.now if self.class.column_names.include?('updated_at')
99
- end
100
- end
123
+ delegate :hoardable_source_foreign_key, to: :class
101
124
 
102
125
  def hoardable_source_attributes
103
126
  @hoardable_source_attributes ||=
@@ -106,16 +129,8 @@ module Hoardable
106
129
  .reject { |k, _v| k.start_with?('_') }
107
130
  end
108
131
 
109
- def hoardable_source_foreign_key
110
- @hoardable_source_foreign_key ||= "#{self.class.superclass.model_name.i18n_key}_id"
111
- end
112
-
113
- def hoardable_source_foreign_id
114
- @hoardable_source_foreign_id ||= public_send(hoardable_source_foreign_key)
115
- end
116
-
117
132
  def previous_temporal_tsrange_end
118
- hoardable_source.versions.limit(1).order(_during: :desc).pluck('_during').first&.end
133
+ hoardable_source.versions.only_most_recent.pluck('_during').first&.end
119
134
  end
120
135
 
121
136
  def assign_temporal_tsrange
@@ -124,10 +139,10 @@ module Hoardable
124
139
  if hoardable_source.class.column_names.include?('created_at')
125
140
  hoardable_source.created_at
126
141
  else
127
- Time.at(0)
142
+ Time.at(0).utc
128
143
  end
129
144
  )
130
- self._during = (range_start..Time.now)
145
+ self._during = (range_start..Time.now.utc)
131
146
  end
132
147
  end
133
148
  end
data/lib/hoardable.rb CHANGED
@@ -7,4 +7,5 @@ require_relative 'hoardable/error'
7
7
  require_relative 'hoardable/source_model'
8
8
  require_relative 'hoardable/version_model'
9
9
  require_relative 'hoardable/model'
10
+ require_relative 'hoardable/associations'
10
11
  require_relative 'generators/hoardable/migration_generator'
data/sig/hoardable.rbs CHANGED
@@ -71,6 +71,7 @@ module Hoardable
71
71
  include VersionModel
72
72
  include SourceModel
73
73
 
74
+ attr_reader _hoardable_config: Hash[untyped, untyped]
74
75
  def hoardable_config: (?nil hash) -> untyped
75
76
  def with_hoardable_config: (untyped hash) -> untyped
76
77
  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.3.0
4
+ version: 0.5.0
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-23 00:00:00.000000000 Z
11
+ date: 2022-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -108,6 +108,7 @@ files:
108
108
  - lib/generators/hoardable/templates/migration.rb.erb
109
109
  - lib/generators/hoardable/templates/migration_6.rb.erb
110
110
  - lib/hoardable.rb
111
+ - lib/hoardable/associations.rb
111
112
  - lib/hoardable/error.rb
112
113
  - lib/hoardable/hoardable.rb
113
114
  - lib/hoardable/model.rb