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 +4 -4
- data/CHANGELOG.md +28 -2
- data/Gemfile +1 -0
- data/README.md +13 -14
- data/lib/generators/hoardable/templates/migration.rb.erb +3 -2
- data/lib/generators/hoardable/templates/migration_6.rb.erb +3 -2
- data/lib/hoardable/associations.rb +2 -2
- data/lib/hoardable/database_client.rb +89 -0
- data/lib/hoardable/model.rb +1 -1
- data/lib/hoardable/source_model.rb +11 -63
- data/lib/hoardable/version.rb +1 -1
- data/lib/hoardable/version_model.rb +23 -71
- data/lib/hoardable.rb +1 -0
- data/sig/hoardable.rbs +26 -31
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1cd77fbd3d35df59423ba95f7286703ae0d3b0f78bd9097b3574c713c38aa4c7
|
4
|
+
data.tar.gz: d5971e9daf59a9fa5343c13e389b20fc5b6ff193b398d05c958f34dfd056d0cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
7
|
-
inverse_of :hoardable_source` to not potentially conflict with previously existing
|
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
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,
|
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(
|
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
|
229
|
-
|
230
|
-
|
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
|
-
|
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
|
245
|
-
hoardable_version
|
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 %>
|
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
|
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 %>
|
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
|
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.
|
20
|
+
version_class = source_reflection.version_class
|
21
21
|
version_class.trashed.only_most_recent.find_by(
|
22
|
-
|
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
|
data/lib/hoardable/model.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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 {
|
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(
|
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?(
|
89
|
+
version.is_a?(version_class) ? version.revert! : self
|
89
90
|
end
|
90
91
|
|
91
|
-
|
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
|
-
|
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
|
-
|
148
|
-
|
96
|
+
def hoardable_client
|
97
|
+
@hoardable_client ||= DatabaseClient.new(self)
|
149
98
|
end
|
150
|
-
private_constant :Service
|
151
99
|
end
|
152
100
|
end
|
data/lib/hoardable/version.rb
CHANGED
@@ -7,9 +7,11 @@ module Hoardable
|
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
9
|
class_methods do
|
10
|
-
#
|
11
|
-
|
12
|
-
|
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!(
|
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
|
-
|
96
|
-
untrashed.send('
|
97
|
-
|
98
|
-
|
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(
|
118
|
+
@hoardable_source_foreign_id ||= public_send(:hoardable_source_id)
|
119
119
|
end
|
120
120
|
|
121
|
-
|
121
|
+
private
|
122
122
|
|
123
|
-
def
|
124
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
-
@
|
48
|
+
@hoardable_client: DatabaseClient
|
34
49
|
|
35
|
-
attr_reader hoardable_version:
|
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
|
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
|
-
@
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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.
|
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-
|
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
|