hoardable 0.6.0 → 0.8.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: 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