hoardable 0.6.0 → 0.8.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: dff71dd2ebbebaeedfdc9d6fdd8c56c8faaa5505814cfe41c146c4a565375ff1
4
- data.tar.gz: 6693f3634541bc8308ed5b4329d69851d971df1cec2b50e842fa9332c62425b6
3
+ metadata.gz: 1cd77fbd3d35df59423ba95f7286703ae0d3b0f78bd9097b3574c713c38aa4c7
4
+ data.tar.gz: d5971e9daf59a9fa5343c13e389b20fc5b6ff193b398d05c958f34dfd056d0cb
5
5
  SHA512:
6
- metadata.gz: ec2a9df9254cf3623f4fffb0516e99ec30a46ec6496ad0d5e555d4a3ce8324fa81d1880036f42aeb4bbbee136957479f283ccab4b8fb3f2847122bd597483889
7
- data.tar.gz: 25a5040c6dc5b91b0b54020657e436aa775cf2211b56fecf1b277e36c11041b55f5a29861000503d5388a9a3d0280a8ccca99c052a2dc82a45abb8f5c98bb50e
6
+ metadata.gz: 5de03adf485f38e3ee3a24999bf770d92792404894529a5317f9788d15b784c0bcd9aa28b3d2966340a1244296cdc7b4ebe6fdccd6791172b44ca498c4bf5fca
7
+ data.tar.gz: a39bd22b66c727ec791e704ec3b24592c068dd2eb29ae0fa7d70a440073bd6d635fd7f24f12d3ba34bf05976b311bab9ec6a5a6fdf9f9069311347d89ae8f8c0
data/CHANGELOG.md CHANGED
@@ -1,10 +1,36 @@
1
1
  ## [Unreleased]
2
2
 
3
+ - Stability is coming.
4
+
5
+ ## [0.8.0] - 2022-10-01
6
+
7
+ - **Breaking Change** - Due to the performance benefit of using `insert` for database injection of
8
+ versions, and a personal opinion that only an `after_versioned` hook might be needed, the
9
+ `before_versioned` and `around_versioned` ActiveRecord hooks are removed.
10
+
11
+ - **Breaking Change** - Another side effect of the performance benefit gained by using `insert` is
12
+ that a source model will need to be reloaded before a call to `versions` on it can access the
13
+ latest version after an `update` on the source record.
14
+
15
+ - **Breaking Change** - Previously the inherited `_versions` tables did not have a unique index on
16
+ the ID column, though it still pulled from the same sequence as the parent table. Prior to version
17
+ 0.4.0 though, it was possible to have multiple trashed versions with the same ID. Adding unique
18
+ indexes to version tables prior to version 0.4.0 could result in issues.
19
+
20
+ ## [0.7.0] - 2022-09-29
21
+
22
+ - **Breaking Change** - Continuing along with the change below, the `foreign_key` on the `_versions`
23
+ tables is now changed to `hoardable_source_id` instead of the i18n model name dervied foreign key.
24
+ The intent is to never leave room for conflict of foreign keys for existing relationships. This
25
+ can be resolved by renaming the foreign key columns from their i18n model name derived column
26
+ names to `hoardable_source_id`, i.e. `rename_column :post_versions, :post_id, :hoardable_source_id`.
27
+
3
28
  ## [0.6.0] - 2022-09-28
4
29
 
5
30
  - **Breaking Change** - Previously, a source model would `has_many :versions` with an inverse
6
- relationship of the i18n interpreted name of the source model. Now it simply `has_many :versions,
7
- inverse_of :hoardable_source` to not potentially conflict with previously existing relationships.
31
+ relationship based on the i18n interpreted name of the source model. Now it simply `has_many
32
+ :versions, inverse_of :hoardable_source` to not potentially conflict with previously existing
33
+ relationships.
8
34
 
9
35
  ## [0.5.0] - 2022-09-25
10
36
 
data/Gemfile CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ gem 'benchmark-ips', '~> 2.10'
5
6
  gem 'debug', '~> 1.6'
6
7
  gem 'minitest', '~> 5.0'
7
8
  gem 'rake', '~> 13.0'
data/README.md CHANGED
@@ -76,7 +76,7 @@ $ irb
76
76
  >> Post
77
77
  => Post(id: integer, body: text, user_id: integer, created_at: datetime)
78
78
  >> PostVersion
79
- => PostVersion(id: integer, body: text, user_id: integer, created_at: datetime, _data: jsonb, _during: tsrange, post_id: integer)
79
+ => PostVersion(id: integer, body: text, user_id: integer, created_at: datetime, _data: jsonb, _during: tsrange, hoardable_source_id: integer)
80
80
  ```
81
81
 
82
82
  A `Post` now `has_many :versions`. With the default configuration, whenever an update and deletion
@@ -86,7 +86,7 @@ of a `Post` occurs, a version is created:
86
86
  post = Post.create!(title: "Title")
87
87
  post.versions.size # => 0
88
88
  post.update!(title: "Revised Title")
89
- post.versions.size # => 1
89
+ post.reload.versions.size # => 1
90
90
  post.versions.first.title # => "Title"
91
91
  post.destroy!
92
92
  post.trashed? # true
@@ -102,7 +102,7 @@ If you ever need to revert to a specific version, you can call `version.revert!`
102
102
  ``` ruby
103
103
  post = Post.create!(title: "Title")
104
104
  post.update!(title: "Whoops")
105
- post.versions.last.revert!
105
+ post.reload.versions.last.revert!
106
106
  post.title # => "Title"
107
107
  ```
108
108
 
@@ -134,7 +134,7 @@ If you want to look-up the version of a record at a specific time, you can use t
134
134
  ```ruby
135
135
  post.at(1.day.ago) # => #<PostVersion>
136
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>
137
+ PostVersion.at(1.day.ago).find_by(hoardable_source_id: post.id) # => #<PostVersion>
138
138
  ```
139
139
 
140
140
  The source model class also has an `.at` method:
@@ -182,7 +182,7 @@ Hoardable.whodunit = -> { Current.user&.id }
182
182
  # somewhere in your app code
183
183
  Current.user = User.find(123)
184
184
  post.update!(status: 'live')
185
- post.versions.last.hoardable_whodunit # => 123
185
+ post.reload.versions.last.hoardable_whodunit # => 123
186
186
  ```
187
187
 
188
188
  You can also set this context manually as well, just remember to clear them afterwards.
@@ -191,7 +191,7 @@ You can also set this context manually as well, just remember to clear them afte
191
191
  Hoardable.note = "reverting due to accidental deletion"
192
192
  post.update!(title: "We’re back!")
193
193
  Hoardable.note = nil
194
- post.versions.last.hoardable_note # => "reverting due to accidental deletion"
194
+ post.reload.versions.last.hoardable_note # => "reverting due to accidental deletion"
195
195
  ```
196
196
 
197
197
  A more useful pattern is to use `Hoardable.with` to set the context around a block. A good example
@@ -225,9 +225,9 @@ version.hoardable_event_uuid
225
225
 
226
226
  ### Model Callbacks
227
227
 
228
- Sometimes you might want to do something with a version before or after it gets inserted to the
229
- database. You can access it in `before/after/around_versioned` callbacks on the source record as
230
- `hoardable_version`. These happen around `.save`, which is enclosed in an ActiveRecord transaction.
228
+ Sometimes you might want to do something with a version after it gets inserted to the database. You
229
+ can access it in `after_versioned` callbacks on the source record as `hoardable_version`. These
230
+ happen within `ActiveRecord`’s `.save`, which is enclosed in an ActiveRecord transaction.
231
231
 
232
232
  There are also `after_reverted` and `after_untrashed` callbacks available as well, which are called
233
233
  on the source record after a version is reverted or untrashed.
@@ -235,14 +235,14 @@ on the source record after a version is reverted or untrashed.
235
235
  ```ruby
236
236
  class User
237
237
  include Hoardable::Model
238
- before_versioned :sanitize_version
238
+ after_versioned :track_versioned_event
239
239
  after_reverted :track_reverted_event
240
240
  after_untrashed :track_untrashed_event
241
241
 
242
242
  private
243
243
 
244
- def sanitize_version
245
- hoardable_version.sanitize_password
244
+ def track_versioned_event
245
+ track_event(:user_versioned, hoardable_version)
246
246
  end
247
247
 
248
248
  def track_reverted_event
@@ -338,14 +338,13 @@ class Post < ActiveRecord::Base
338
338
  Comment
339
339
  .version_class
340
340
  .trashed
341
- .where(post_id: id)
342
341
  .with_hoardable_event_uuid(hoardable_event_uuid)
343
342
  .find_each(&:untrash!)
344
343
  end
345
344
  end
346
345
  ```
347
346
 
348
- If there are models that might be related to versions that are trashed or otherwise, and/or might
347
+ If there are models that might be related to versions that are trashed or otherwise, and/or might be
349
348
  trashed themselves, you can bypass the inherited tables query handling altogether by using the
350
349
  `return_everything` configuration variable in `Hoardable.with`. This will ensure that you always see
351
350
  all records, including update and trashed versions.
@@ -8,11 +8,12 @@ class Create<%= class_name.singularize %>Versions < ActiveRecord::Migration[<%=
8
8
  t.tsrange :_during, null: false
9
9
  t.uuid :_event_uuid, null: false, index: true
10
10
  t.enum :_operation, enum_type: 'hoardable_operation', null: false, index: true
11
- t.<%= foreign_key_type %> :<%= singularized_table_name %>_id, null: false, index: true
11
+ t.<%= foreign_key_type %> :hoardable_source_id, null: false, index: true
12
12
  end
13
+ add_index(:<%= singularized_table_name %>_versions, :id, unique: true)
13
14
  add_index(
14
15
  :<%= singularized_table_name %>_versions,
15
- %i[_during <%= singularized_table_name %>_id],
16
+ %i[_during hoardable_source_id],
16
17
  name: 'idx_<%= singularized_table_name %>_versions_temporally'
17
18
  )
18
19
  end
@@ -23,11 +23,12 @@ class Create<%= class_name.singularize %>Versions < ActiveRecord::Migration[<%=
23
23
  t.tsrange :_during, null: false
24
24
  t.uuid :_event_uuid, null: false, index: true
25
25
  t.column :_operation, :hoardable_operation, null: false, index: true
26
- t.<%= foreign_key_type %> :<%= singularized_table_name %>_id, null: false, index: true
26
+ t.<%= foreign_key_type %> :hoardable_source_id, null: false, index: true
27
27
  end
28
+ add_index(:<%= singularized_table_name %>_versions, :id, unique: true)
28
29
  add_index(
29
30
  :<%= singularized_table_name %>_versions,
30
- %i[_during <%= singularized_table_name %>_id],
31
+ %i[_during hoardable_source_id],
31
32
  name: 'idx_<%= singularized_table_name %>_versions_temporally'
32
33
  )
33
34
  end
@@ -17,9 +17,9 @@ module Hoardable
17
17
 
18
18
  define_method(trashable_relationship_name) do
19
19
  source_reflection = self.class.reflections[name.to_s]
20
- version_class = source_reflection.klass.version_class
20
+ version_class = source_reflection.version_class
21
21
  version_class.trashed.only_most_recent.find_by(
22
- version_class.hoardable_source_foreign_key => source_reflection.foreign_key
22
+ hoardable_source_id: source_reflection.foreign_key
23
23
  )
24
24
  end
25
25
 
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hoardable
4
+ # This is a private service class that manages the insertion of {VersionModel}s into the
5
+ # PostgreSQL database.
6
+ class DatabaseClient
7
+ attr_reader :source_record
8
+
9
+ def initialize(source_record)
10
+ @source_record = source_record
11
+ end
12
+
13
+ delegate :version_class, to: :source_record
14
+
15
+ def insert_hoardable_version(operation, &block)
16
+ version = version_class.insert(initialize_version_attributes(operation), returning: :id)
17
+ version_id = version[0]['id']
18
+ source_record.instance_variable_set('@hoardable_version', version_class.find(version_id))
19
+ source_record.run_callbacks(:versioned, &block)
20
+ end
21
+
22
+ def find_or_initialize_hoardable_event_uuid
23
+ Thread.current[:hoardable_event_uuid] ||= ActiveRecord::Base.connection.query('SELECT gen_random_uuid();')[0][0]
24
+ end
25
+
26
+ def initialize_version_attributes(operation)
27
+ source_record.attributes_before_type_cast.without('id').merge(
28
+ source_record.changes.transform_values { |h| h[0] },
29
+ {
30
+ 'hoardable_source_id' => source_record.id,
31
+ '_event_uuid' => find_or_initialize_hoardable_event_uuid,
32
+ '_operation' => operation,
33
+ '_data' => initialize_hoardable_data.merge(changes: source_record.changes),
34
+ '_during' => initialize_temporal_range
35
+ }
36
+ )
37
+ end
38
+
39
+ def initialize_temporal_range
40
+ ((previous_temporal_tsrange_end || hoardable_source_epoch)..Time.now.utc)
41
+ end
42
+
43
+ def initialize_hoardable_data
44
+ DATA_KEYS.to_h do |key|
45
+ [key, assign_hoardable_context(key)]
46
+ end
47
+ end
48
+
49
+ def assign_hoardable_context(key)
50
+ return nil if (value = Hoardable.public_send(key)).nil?
51
+
52
+ value.is_a?(Proc) ? value.call : value
53
+ end
54
+
55
+ def unset_hoardable_version_and_event_uuid
56
+ source_record.instance_variable_set('@hoardable_version', nil)
57
+ return if source_record.class.connection.transaction_open?
58
+
59
+ Thread.current[:hoardable_event_uuid] = nil
60
+ end
61
+
62
+ def previous_temporal_tsrange_end
63
+ source_record.versions.only_most_recent.pluck('_during').first&.end
64
+ end
65
+
66
+ def hoardable_source_epoch
67
+ if source_record.class.column_names.include?('created_at')
68
+ source_record.created_at
69
+ else
70
+ maybe_warn_about_missing_created_at_column
71
+ Time.at(0).utc
72
+ end
73
+ end
74
+
75
+ def maybe_warn_about_missing_created_at_column
76
+ return unless source_record.class.hoardable_config[:warn_on_missing_created_at_column]
77
+
78
+ source_table_name = source_record.class.table_name
79
+ Hoardable.logger.info(
80
+ <<~LOG
81
+ '#{source_table_name}' does not have a 'created_at' column, so the first version’s temporal period
82
+ will begin at the unix epoch instead. Add a 'created_at' column to '#{source_table_name}'
83
+ or set 'Hoardable.warn_on_missing_created_at_column = false' to disable this message.
84
+ LOG
85
+ )
86
+ end
87
+ end
88
+ private_constant :DatabaseClient
89
+ end
@@ -46,7 +46,7 @@ module Hoardable
46
46
 
47
47
  included do
48
48
  include Associations
49
- define_model_callbacks :versioned
49
+ define_model_callbacks :versioned, only: :after
50
50
  define_model_callbacks :reverted, only: :after
51
51
  define_model_callbacks :untrashed, only: :after
52
52
 
@@ -27,25 +27,26 @@ module Hoardable
27
27
  include Tableoid
28
28
 
29
29
  around_update(if: [HOARDABLE_CALLBACKS_ENABLED, HOARDABLE_VERSION_UPDATES]) do |_, block|
30
- hoardable_source_service.insert_hoardable_version('update', &block)
30
+ hoardable_client.insert_hoardable_version('update', &block)
31
31
  end
32
32
 
33
33
  around_destroy(if: [HOARDABLE_CALLBACKS_ENABLED, HOARDABLE_SAVE_TRASH]) do |_, block|
34
- hoardable_source_service.insert_hoardable_version('delete', &block)
34
+ hoardable_client.insert_hoardable_version('delete', &block)
35
35
  end
36
36
 
37
37
  before_destroy(if: HOARDABLE_CALLBACKS_ENABLED, unless: HOARDABLE_SAVE_TRASH) do
38
38
  versions.delete_all(:delete_all)
39
39
  end
40
40
 
41
- after_commit { hoardable_source_service.unset_hoardable_version_and_event_uuid }
41
+ after_commit { hoardable_client.unset_hoardable_version_and_event_uuid }
42
42
 
43
43
  # Returns all +versions+ in ascending order of their temporal timeframes.
44
44
  has_many(
45
45
  :versions, -> { order('UPPER(_during) ASC') },
46
46
  dependent: nil,
47
47
  class_name: version_class.to_s,
48
- inverse_of: :hoardable_source
48
+ inverse_of: :hoardable_source,
49
+ foreign_key: :hoardable_source_id
49
50
  )
50
51
 
51
52
  # @!scope class
@@ -56,7 +57,7 @@ module Hoardable
56
57
  # +datetime+ or +time+, all cast as instances of the source model.
57
58
  scope :at, lambda { |datetime|
58
59
  include_versions.where(id: version_class.at(datetime).select('id')).or(
59
- where.not(id: version_class.select(version_class.hoardable_source_foreign_key).where(DURING_QUERY, datetime))
60
+ where.not(id: version_class.select(:hoardable_source_id).where(DURING_QUERY, datetime))
60
61
  )
61
62
  }
62
63
  end
@@ -85,68 +86,15 @@ module Hoardable
85
86
  def revert_to!(datetime)
86
87
  return unless (version = at(datetime))
87
88
 
88
- version.is_a?(self.class.version_class) ? version.revert! : self
89
+ version.is_a?(version_class) ? version.revert! : self
89
90
  end
90
91
 
91
- private
92
-
93
- def hoardable_source_service
94
- @hoardable_source_service ||= Service.new(self)
95
- end
96
-
97
- # This is a private service class that manages the insertion of {VersionModel}s for a
98
- # {SourceModel} into the PostgreSQL database.
99
- class Service
100
- attr_reader :source_model
101
-
102
- def initialize(source_model)
103
- @source_model = source_model
104
- end
105
-
106
- def insert_hoardable_version(operation)
107
- source_model.instance_variable_set('@hoardable_version', initialize_hoardable_version(operation))
108
- source_model.run_callbacks(:versioned) do
109
- yield if block_given?
110
- source_model.hoardable_version.save(validate: false, touch: false)
111
- end
112
- end
113
-
114
- def find_or_initialize_hoardable_event_uuid
115
- Thread.current[:hoardable_event_uuid] ||= ActiveRecord::Base.connection.query('SELECT gen_random_uuid();')[0][0]
116
- end
92
+ delegate :version_class, to: :class
117
93
 
118
- def initialize_hoardable_version(operation)
119
- source_model.versions.new(
120
- source_model.attributes_before_type_cast.without('id').merge(
121
- source_model.changes.transform_values { |h| h[0] },
122
- {
123
- _event_uuid: find_or_initialize_hoardable_event_uuid,
124
- _operation: operation,
125
- _data: initialize_hoardable_data.merge(changes: source_model.changes)
126
- }
127
- )
128
- )
129
- end
130
-
131
- def initialize_hoardable_data
132
- DATA_KEYS.to_h do |key|
133
- [key, assign_hoardable_context(key)]
134
- end
135
- end
136
-
137
- def assign_hoardable_context(key)
138
- return nil if (value = Hoardable.public_send(key)).nil?
139
-
140
- value.is_a?(Proc) ? value.call : value
141
- end
142
-
143
- def unset_hoardable_version_and_event_uuid
144
- source_model.instance_variable_set('@hoardable_version', nil)
145
- return if source_model.class.connection.transaction_open?
94
+ private
146
95
 
147
- Thread.current[:hoardable_event_uuid] = nil
148
- end
96
+ def hoardable_client
97
+ @hoardable_client ||= DatabaseClient.new(self)
149
98
  end
150
- private_constant :Service
151
99
  end
152
100
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hoardable
4
- VERSION = '0.6.0'
4
+ VERSION = '0.8.0'
5
5
  end
@@ -7,9 +7,11 @@ module Hoardable
7
7
  extend ActiveSupport::Concern
8
8
 
9
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"
10
+ # This is needed to omit the pseudo row of 'tableoid' when using +ActiveRecord+’s +insert+.
11
+ #
12
+ # @!visibility private
13
+ def scope_attributes
14
+ super.without('tableoid')
13
15
  end
14
16
  end
15
17
 
@@ -18,8 +20,7 @@ module Hoardable
18
20
  belongs_to(
19
21
  :hoardable_source,
20
22
  inverse_of: :versions,
21
- class_name: superclass.model_name,
22
- foreign_key: hoardable_source_foreign_key
23
+ class_name: superclass.model_name
23
24
  )
24
25
 
25
26
  self.table_name = "#{table_name.singularize}#{VERSION_TABLE_SUFFIX}"
@@ -29,8 +30,6 @@ module Hoardable
29
30
  alias_attribute :hoardable_event_uuid, :_event_uuid
30
31
  alias_attribute :hoardable_during, :_during
31
32
 
32
- before_create { hoardable_version_service.assign_temporal_tsrange }
33
-
34
33
  # @!scope class
35
34
  # @!method trashed
36
35
  # @return [ActiveRecord<Object>]
@@ -79,7 +78,7 @@ module Hoardable
79
78
 
80
79
  transaction do
81
80
  hoardable_source.tap do |reverted|
82
- reverted.update!(hoardable_version_service.hoardable_source_attributes.without('id'))
81
+ reverted.update!(hoardable_source_attributes.without('id'))
83
82
  reverted.instance_variable_set(:@hoardable_version, self)
84
83
  reverted.run_callbacks(:reverted)
85
84
  end
@@ -92,10 +91,11 @@ module Hoardable
92
91
  raise(Error, 'Version is not trashed, cannot untrash') unless hoardable_operation == 'delete'
93
92
 
94
93
  transaction do
95
- hoardable_version_service.insert_untrashed_source.tap do |untrashed|
96
- untrashed.send('hoardable_source_service').insert_hoardable_version('insert')
97
- untrashed.instance_variable_set(:@hoardable_version, self)
98
- untrashed.run_callbacks(:untrashed)
94
+ insert_untrashed_source.tap do |untrashed|
95
+ untrashed.send('hoardable_client').insert_hoardable_version('insert') do
96
+ untrashed.instance_variable_set(:@hoardable_version, self)
97
+ untrashed.run_callbacks(:untrashed)
98
+ end
99
99
  end
100
100
  end
101
101
  end
@@ -115,70 +115,22 @@ module Hoardable
115
115
 
116
116
  # Returns the foreign reference that represents the source model of the version.
117
117
  def hoardable_source_foreign_id
118
- @hoardable_source_foreign_id ||= public_send(hoardable_source_foreign_key)
118
+ @hoardable_source_foreign_id ||= public_send(:hoardable_source_id)
119
119
  end
120
120
 
121
- delegate :hoardable_source_foreign_key, to: :class
121
+ private
122
122
 
123
- def hoardable_version_service
124
- @hoardable_version_service ||= Service.new(self)
123
+ def insert_untrashed_source
124
+ superscope = self.class.superclass.unscoped
125
+ superscope.insert(hoardable_source_attributes.merge('id' => hoardable_source_foreign_id))
126
+ superscope.find(hoardable_source_foreign_id)
125
127
  end
126
128
 
127
- # This is a private service class that manages the construction of {VersionModel} attributes and
128
- # untrashing / re-insertion into the {SourceModel} table.
129
- class Service
130
- attr_reader :version_model
131
-
132
- def initialize(version_model)
133
- @version_model = version_model
134
- end
135
-
136
- delegate :hoardable_source_foreign_id, :hoardable_source_foreign_key, :hoardable_source, to: :version_model
137
-
138
- def insert_untrashed_source
139
- superscope = version_model.class.superclass.unscoped
140
- superscope.insert(hoardable_source_attributes.merge('id' => hoardable_source_foreign_id))
141
- superscope.find(hoardable_source_foreign_id)
142
- end
143
-
144
- def hoardable_source_attributes
145
- @hoardable_source_attributes ||=
146
- version_model
147
- .attributes_before_type_cast
148
- .without(hoardable_source_foreign_key)
149
- .reject { |k, _v| k.start_with?('_') }
150
- end
151
-
152
- def previous_temporal_tsrange_end
153
- hoardable_source.versions.only_most_recent.pluck('_during').first&.end
154
- end
155
-
156
- def hoardable_source_epoch
157
- if hoardable_source.class.column_names.include?('created_at')
158
- hoardable_source.created_at
159
- else
160
- maybe_warn_about_missing_created_at_column
161
- Time.at(0).utc
162
- end
163
- end
164
-
165
- def assign_temporal_tsrange
166
- version_model._during = ((previous_temporal_tsrange_end || hoardable_source_epoch)..Time.now.utc)
167
- end
168
-
169
- def maybe_warn_about_missing_created_at_column
170
- return unless hoardable_source.class.hoardable_config[:warn_on_missing_created_at_column]
171
-
172
- source_table_name = hoardable_source.class.table_name
173
- Hoardable.logger.info(
174
- <<~LOG
175
- '#{source_table_name}' does not have a 'created_at' column, so the first version’s temporal period
176
- will begin at the unix epoch instead. Add a 'created_at' column to '#{source_table_name}'
177
- or set 'Hoardable.warn_on_missing_created_at_column = false' to disable this message.
178
- LOG
179
- )
180
- end
129
+ def hoardable_source_attributes
130
+ @hoardable_source_attributes ||=
131
+ attributes_before_type_cast
132
+ .without('hoardable_source_id')
133
+ .reject { |k, _v| k.start_with?('_') }
181
134
  end
182
- private_constant :Service
183
135
  end
184
136
  end
data/lib/hoardable.rb CHANGED
@@ -4,6 +4,7 @@ require_relative 'hoardable/version'
4
4
  require_relative 'hoardable/hoardable'
5
5
  require_relative 'hoardable/tableoid'
6
6
  require_relative 'hoardable/error'
7
+ require_relative 'hoardable/database_client'
7
8
  require_relative 'hoardable/source_model'
8
9
  require_relative 'hoardable/version_model'
9
10
  require_relative 'hoardable/model'
data/sig/hoardable.rbs CHANGED
@@ -28,57 +28,52 @@ module Hoardable
28
28
  class Error < StandardError
29
29
  end
30
30
 
31
+ class DatabaseClient
32
+ attr_reader source_model: SourceModel
33
+ def initialize: (SourceModel source_model) -> void
34
+ def insert_hoardable_version: (untyped operation) -> untyped
35
+ def find_or_initialize_hoardable_event_uuid: -> untyped
36
+ def initialize_version_attributes: (untyped operation) -> untyped
37
+ def initialize_temporal_range: -> Range
38
+ def initialize_hoardable_data: -> untyped
39
+ def assign_hoardable_context: (:event_uuid | :meta | :note | :whodunit key) -> nil
40
+ def unset_hoardable_version_and_event_uuid: -> nil
41
+ def previous_temporal_tsrange_end: -> untyped
42
+ def hoardable_source_epoch: -> Time
43
+ def maybe_warn_about_missing_created_at_column: -> nil
44
+ end
45
+
31
46
  module SourceModel
32
47
  include Tableoid
33
- @hoardable_source_service: Service
48
+ @hoardable_client: DatabaseClient
34
49
 
35
- attr_reader hoardable_version: nil
50
+ attr_reader hoardable_version: untyped
36
51
  def trashed?: -> untyped
37
52
  def at: (untyped datetime) -> SourceModel
38
53
  def revert_to!: (untyped datetime) -> SourceModel?
39
54
 
40
55
  private
41
- def hoardable_source_service: -> Service
56
+ def hoardable_client: -> DatabaseClient
42
57
 
43
58
  public
44
59
  def version_class: -> untyped
45
-
46
- class Service
47
- attr_reader source_model: SourceModel
48
- def initialize: (SourceModel source_model) -> void
49
- def insert_hoardable_version: (untyped operation) -> untyped
50
- def find_or_initialize_hoardable_event_uuid: -> untyped
51
- def initialize_hoardable_version: (untyped operation) -> untyped
52
- def initialize_hoardable_data: -> untyped
53
- def assign_hoardable_context: (:event_uuid | :meta | :note | :whodunit key) -> nil
54
- def unset_hoardable_version_and_event_uuid: -> nil
55
- end
56
60
  end
57
61
 
58
62
  module VersionModel
59
63
  @hoardable_source_foreign_id: untyped
60
- @hoardable_source_foreign_key: String
61
- @hoardable_version_service: Service
64
+ @hoardable_source_attributes: untyped
62
65
 
63
66
  def revert!: -> untyped
64
67
  def untrash!: -> untyped
65
68
  def changes: -> untyped
66
69
  def hoardable_source_foreign_id: -> untyped
67
- def hoardable_version_service: -> Service
68
- def hoardable_source_foreign_key: -> String
69
-
70
- class Service
71
- @hoardable_source_attributes: untyped
72
-
73
- attr_reader version_model: VersionModel
74
- def initialize: (VersionModel version_model) -> void
75
- def insert_untrashed_source: -> untyped
76
- def hoardable_source_attributes: -> untyped
77
- def previous_temporal_tsrange_end: -> untyped
78
- def hoardable_source_epoch: -> Time
79
- def assign_temporal_tsrange: -> Range
80
- def maybe_warn_about_missing_created_at_column: -> nil
81
- end
70
+
71
+ private
72
+ def insert_untrashed_source: -> untyped
73
+ def hoardable_source_attributes: -> untyped
74
+
75
+ public
76
+ def scope_attributes: -> untyped
82
77
  end
83
78
 
84
79
  module Model
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.6.0
4
+ version: 0.8.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-09-28 00:00:00.000000000 Z
11
+ date: 2022-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -109,6 +109,7 @@ files:
109
109
  - lib/generators/hoardable/templates/migration_6.rb.erb
110
110
  - lib/hoardable.rb
111
111
  - lib/hoardable/associations.rb
112
+ - lib/hoardable/database_client.rb
112
113
  - lib/hoardable/error.rb
113
114
  - lib/hoardable/hoardable.rb
114
115
  - lib/hoardable/model.rb