active_snapshot 0.4.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -2
- data/README.md +54 -7
- data/lib/active_snapshot/models/concerns/snapshots_concern.rb +3 -9
- data/lib/active_snapshot/models/snapshot.rb +24 -4
- data/lib/active_snapshot/models/snapshot_item.rb +13 -8
- data/lib/active_snapshot/version.rb +1 -1
- data/lib/active_snapshot.rb +13 -11
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d5fd89751eb3c64739f2a3242edfb4a8f3fd1dfce8047bbd4db51b790039ee39
|
4
|
+
data.tar.gz: 5cf804fd3bb81be067153fa1120b45f5045febf0eb93921950ae51bea17c65a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c079cdc615a35293cecbe0ab3c86df6a0b96e6bc0a1a92beb5b8580735d4a61209aa97623a18d9e06aeffeb109f94bbc742cea119e96421ba725ed9a22aae1eb
|
7
|
+
data.tar.gz: dad4d76b90fd8caa0dbd09a2c20d618bd6b8060de77eacbbad4eeda7d79cfa13be48fed5888f80122c607f8ce69acdf67eb7ce48707f54bd08dc3e8f9922604b
|
data/CHANGELOG.md
CHANGED
@@ -2,12 +2,26 @@ CHANGELOG
|
|
2
2
|
---------
|
3
3
|
|
4
4
|
- **Unreleased**
|
5
|
-
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.
|
5
|
+
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.5.1...master)
|
6
6
|
* Nothing yet
|
7
7
|
|
8
|
+
- **v0.5.1** - Nov 11, 2024
|
9
|
+
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.5.0...v0.5.1)
|
10
|
+
* [#66](https://github.com/westonganger/active_snapshot/pull/66) - Ensure `SnapshotItem#restore_item!` and `Snapshot#fetch_reified_items` bypass assignment for snapshot object data where the associated column no longer exists.
|
11
|
+
* [#63](https://github.com/westonganger/active_snapshot/pull/63) - Fix bug when enum value is nil
|
12
|
+
|
13
|
+
- **v0.5.0** - Nov 8, 2024
|
14
|
+
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.4.0...v0.5.0)
|
15
|
+
* [#61](https://github.com/westonganger/active_snapshot/pull/61) - Ensure snapshot record returned by `create_snapshot!` is `valid?`
|
16
|
+
* [#60](https://github.com/westonganger/active_snapshot/pull/60) - Store enum value as integer
|
17
|
+
* [#56](https://github.com/westonganger/active_snapshot/pull/56) - Add presence validation for object in SnapshotItem model
|
18
|
+
* [#57](https://github.com/westonganger/active_snapshot/pull/57) - Add readonly argument to `Shapshot#fetch_reified_items`
|
19
|
+
* [#53](https://github.com/westonganger/active_snapshot/pull/53) - Allow `ActiveSnapshot.config` to be called before ActiveRecord `on_load` hook has occurred
|
20
|
+
* [#52](https://github.com/westonganger/active_snapshot/pull/52) - Remove deprecated positional argument on `create_snapshot!`
|
21
|
+
|
8
22
|
- **v0.4.0** - July 23, 2024
|
9
23
|
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.3.2...v0.4.0)
|
10
|
-
* [#44](https://github.com/westonganger/active_snapshot/pull/44) - Remove dependency on activerecord-import with vanilla ActiveRecord upsert_all
|
24
|
+
* [#44](https://github.com/westonganger/active_snapshot/pull/44) - Remove dependency on `activerecord-import` with vanilla ActiveRecord `upsert_all`
|
11
25
|
|
12
26
|
- **v0.3.2** - Oct 17, 2023
|
13
27
|
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.3.1...v0.3.2)
|
data/README.md
CHANGED
@@ -9,15 +9,15 @@ Simplified snapshots and restoration for ActiveRecord models and associations wi
|
|
9
9
|
Key Features:
|
10
10
|
|
11
11
|
- Create and Restore snapshots of a parent record and any specified child records
|
12
|
-
-
|
12
|
+
- Predictable and explicit behaviour provides much needed clarity to your restore logic
|
13
13
|
- Snapshots are created upon request only, we do not use any callbacks
|
14
14
|
- Tiny method footprint so its easy to completely override the logic later
|
15
15
|
|
16
16
|
Why This Library:
|
17
17
|
|
18
|
-
Model Versioning and Restoration require
|
18
|
+
Model Versioning and Restoration require conscious thought, design, and understanding. You should understand your versioning and restoration process completely. This gem's small API and fully understandable design fully supports this.
|
19
19
|
|
20
|
-
I do not recommend using paper_trail-association_tracking because it is mostly a blackbox solution which encourages you to set it up and then assume its Just Working<sup>TM</sup>. This makes for major data problems later. Dont fall into this trap. Instead read this gems brief source code completely before use OR copy the code straight into your codebase. Once you know it, then you are free.
|
20
|
+
I do not recommend using [paper_trail-association_tracking](https://github.com/westonganger/paper_trail-association_tracking) because it is mostly a blackbox solution which encourages you to set it up and then assume its Just Working<sup>TM</sup>. This makes for major data problems later. Dont fall into this trap. Instead read this gems brief source code completely before use OR copy the code straight into your codebase. Once you know it, then you are free.
|
21
21
|
|
22
22
|
|
23
23
|
|
@@ -131,20 +131,67 @@ end
|
|
131
131
|
|
132
132
|
Now when you run `create_snapshot!` the associations will be tracked accordingly
|
133
133
|
|
134
|
-
# Reifying
|
134
|
+
# Reifying Snapshots
|
135
135
|
|
136
|
-
|
136
|
+
A reified record refers to an ActiveRecord instance where the local objects data is set to match the snaphotted data, but the database remains changed.
|
137
|
+
|
138
|
+
You can view all of the "reified" snapshot items by calling the following method. Its completely up to you on how to use this data.
|
137
139
|
|
138
140
|
```ruby
|
139
141
|
reified_parent, reified_children_hash = snapshot.fetch_reified_items
|
140
142
|
```
|
141
143
|
|
142
|
-
As a safety these records have the
|
144
|
+
As a safety these records have the `readonly` attribute set on them.
|
145
|
+
If you want to perform any write actions on the returned instances you will have to set the `readonly` attribute to `false`
|
143
146
|
|
144
147
|
```ruby
|
148
|
+
reified_parent, reified_children_hash = snapshot.fetch_reified_items(readonly: false)
|
149
|
+
# or
|
145
150
|
reified_parent, reified_children_hash = snapshot.fetch_reified_items
|
151
|
+
reified_children_hash.first.instance_variable_set("@readonly", false)
|
152
|
+
```
|
153
|
+
|
154
|
+
# Diffing Versions
|
155
|
+
|
156
|
+
You can use the following example code to generate your own diffs.
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
snapshot = post.snapshots.find_by!(identifier: "some-identifier")
|
160
|
+
|
161
|
+
snapshot_item = snapshot.snapshot_items.find_by!(item_type: "Post")
|
162
|
+
|
163
|
+
old_attrs = snapshot_item.object
|
164
|
+
new_attrs = post.attributes # or could be another snapshot object
|
165
|
+
|
166
|
+
attrs_not_changed = old_attrs.to_a.intersection(new_attrs.to_a).to_h
|
167
|
+
|
168
|
+
attrs_changed = new_attrs.to_a - attrs_not_changed.to_a
|
169
|
+
```
|
146
170
|
|
147
|
-
|
171
|
+
# Important Data Considerations / Warnings
|
172
|
+
|
173
|
+
### Dropping columns
|
174
|
+
|
175
|
+
If you plan to use the snapshot restore capabilities please be aware:
|
176
|
+
|
177
|
+
Whenever you drop a database column and there already exists snapshots of that model then you are kind of silently breaking your restore mechanism. Because now the application will not be able to assign data to columns that dont exist on the model. We work around this by bypassing the attribute assignment for snapshot item object entries that does not correlate to a current database column.
|
178
|
+
|
179
|
+
I recommend that you add an entry to this in your applications safe-migrations guidelines.
|
180
|
+
|
181
|
+
If you would like to detect if this situation has already ocurred you can use the following script:
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
SnapshotItem.all.each do |snapshot_item|
|
185
|
+
snapshot_item.object.keys.each do |key|
|
186
|
+
klass = Class.const_get(snapshot_item.item_type)
|
187
|
+
|
188
|
+
if !klass.column_names.include?(key)
|
189
|
+
invalid_data = snapshot_item.object.slice(*klass.column_names)
|
190
|
+
|
191
|
+
raise "invalid data found - #{invalid_data}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
148
195
|
```
|
149
196
|
|
150
197
|
# Key Models Provided & Additional Customizations
|
@@ -8,13 +8,7 @@ module ActiveSnapshot
|
|
8
8
|
has_many :snapshot_items, as: :item, class_name: 'ActiveSnapshot::SnapshotItem'
|
9
9
|
end
|
10
10
|
|
11
|
-
def create_snapshot!(
|
12
|
-
if identifier.nil? && legacy_identifier
|
13
|
-
identifier = legacy_identifier
|
14
|
-
|
15
|
-
ActiveSupport::Deprecation.warn(LEGACY_POSITIONAL_ARGUMENT_WARNING)
|
16
|
-
end
|
17
|
-
|
11
|
+
def create_snapshot!(identifier: nil, user: nil, metadata: nil)
|
18
12
|
snapshot = snapshots.create!({
|
19
13
|
identifier: identifier,
|
20
14
|
user_id: (user.id if user),
|
@@ -40,6 +34,8 @@ module ActiveSnapshot
|
|
40
34
|
|
41
35
|
SnapshotItem.upsert_all(new_entries.map{|x| x.delete("id"); x }, returning: false)
|
42
36
|
|
37
|
+
snapshot.snapshot_items.reset # clear the association cache otherwise snapshot.valid? returns false
|
38
|
+
|
43
39
|
snapshot
|
44
40
|
end
|
45
41
|
|
@@ -127,7 +123,5 @@ module ActiveSnapshot
|
|
127
123
|
end
|
128
124
|
end
|
129
125
|
|
130
|
-
LEGACY_POSITIONAL_ARGUMENT_WARNING = "Supplying the snapshots :identifier as a positional argument is now deprecated and will be removed in upcoming versions. Please supply the snapshot identifier using the :identifier keyword argument instead.".freeze
|
131
|
-
|
132
126
|
end
|
133
127
|
end
|
@@ -46,8 +46,18 @@ module ActiveSnapshot
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def build_snapshot_item(instance, child_group_name: nil)
|
49
|
+
attrs = instance.attributes
|
50
|
+
|
51
|
+
if instance.class.defined_enums.any?
|
52
|
+
instance.class.defined_enums.slice(*attrs.keys).each do |enum_col_name, enum_mapping|
|
53
|
+
val = attrs.fetch(enum_col_name)
|
54
|
+
next if val.nil?
|
55
|
+
attrs[enum_col_name] = enum_mapping.fetch(val)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
49
59
|
self.snapshot_items.new({
|
50
|
-
object:
|
60
|
+
object: attrs,
|
51
61
|
item_id: instance.id,
|
52
62
|
item_type: instance.class.name,
|
53
63
|
child_group_name: child_group_name,
|
@@ -96,15 +106,25 @@ module ActiveSnapshot
|
|
96
106
|
return true
|
97
107
|
end
|
98
108
|
|
99
|
-
def fetch_reified_items
|
109
|
+
def fetch_reified_items(readonly: true)
|
100
110
|
reified_children_hash = {}.with_indifferent_access
|
101
111
|
|
102
112
|
reified_parent = nil
|
103
113
|
|
104
114
|
snapshot_items.each do |si|
|
105
|
-
reified_item = si.item_type.constantize.new
|
115
|
+
reified_item = si.item_type.constantize.new
|
106
116
|
|
107
|
-
|
117
|
+
si.object.each do |k,v|
|
118
|
+
if reified_item.respond_to?("#{k}=")
|
119
|
+
reified_item[k] = v
|
120
|
+
else
|
121
|
+
# database column was likely dropped since the snapshot was created
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
if readonly
|
126
|
+
reified_item.readonly!
|
127
|
+
end
|
108
128
|
|
109
129
|
key = si.child_group_name
|
110
130
|
|
@@ -12,22 +12,21 @@ module ActiveSnapshot
|
|
12
12
|
validates :snapshot_id, presence: true
|
13
13
|
validates :item_id, presence: true, uniqueness: { scope: [:snapshot_id, :item_type] }
|
14
14
|
validates :item_type, presence: true
|
15
|
+
validates :object, presence: true
|
15
16
|
|
16
17
|
def object
|
17
18
|
return @object if @object
|
18
19
|
|
19
20
|
if ActiveSnapshot.config.storage_method_json?
|
20
|
-
@object = JSON.parse(self[:object])
|
21
|
+
@object = self[:object] ? JSON.parse(self[:object]) : {}
|
21
22
|
elsif ActiveSnapshot.config.storage_method_yaml?
|
22
|
-
yaml_method =
|
23
|
+
yaml_method = YAML.respond_to?(:unsafe_load) ? :unsafe_load : :load
|
23
24
|
|
24
|
-
|
25
|
-
yaml_method = "load"
|
26
|
-
end
|
27
|
-
|
28
|
-
@object = YAML.send(yaml_method, self[:object])
|
25
|
+
@object = self[:object] ? YAML.public_send(yaml_method, self[:object]) : {}
|
29
26
|
elsif ActiveSnapshot.config.storage_method_native_json?
|
30
27
|
@object = self[:object]
|
28
|
+
else
|
29
|
+
raise StandardError, "Unsupported storage_method: `#{ActiveSnapshot.config.storage_method}`"
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
@@ -52,7 +51,13 @@ module ActiveSnapshot
|
|
52
51
|
self.item = item_klass.new
|
53
52
|
end
|
54
53
|
|
55
|
-
|
54
|
+
object.each do |k,v|
|
55
|
+
if item.respond_to?("#{k}=")
|
56
|
+
item[k] = v
|
57
|
+
else
|
58
|
+
# database column was likely dropped since the snapshot was created
|
59
|
+
end
|
60
|
+
end
|
56
61
|
|
57
62
|
item.save!(validate: false, touch: false)
|
58
63
|
end
|
data/lib/active_snapshot.rb
CHANGED
@@ -3,27 +3,29 @@ require "active_snapshot/config"
|
|
3
3
|
|
4
4
|
require 'active_support/lazy_load_hooks'
|
5
5
|
|
6
|
+
module ActiveSnapshot
|
7
|
+
@@config = ActiveSnapshot::Config.new
|
8
|
+
|
9
|
+
def self.config(&block)
|
10
|
+
if block_given?
|
11
|
+
block.call(@@config)
|
12
|
+
else
|
13
|
+
return @@config
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
6
18
|
ActiveSupport.on_load(:active_record) do
|
7
19
|
require "active_snapshot/models/snapshot"
|
8
20
|
require "active_snapshot/models/snapshot_item"
|
9
21
|
|
10
22
|
require "active_snapshot/models/concerns/snapshots_concern"
|
11
23
|
|
12
|
-
|
24
|
+
ActiveSnapshot.module_eval do
|
13
25
|
extend ActiveSupport::Concern
|
14
26
|
|
15
27
|
included do
|
16
28
|
include ActiveSnapshot::SnapshotsConcern
|
17
29
|
end
|
18
|
-
|
19
|
-
@@config = ActiveSnapshot::Config.new
|
20
|
-
|
21
|
-
def self.config(&block)
|
22
|
-
if block_given?
|
23
|
-
block.call(@@config)
|
24
|
-
else
|
25
|
-
return @@config
|
26
|
-
end
|
27
|
-
end
|
28
30
|
end
|
29
31
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_snapshot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Weston Ganger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|