hoardable 0.3.0 → 0.5.0

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: 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