active_snapshot 0.5.0 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -1
- data/README.md +47 -21
- data/lib/active_snapshot/config.rb +21 -9
- data/lib/active_snapshot/models/snapshot.rb +25 -10
- data/lib/active_snapshot/models/snapshot_item.rb +15 -7
- data/lib/active_snapshot/version.rb +1 -1
- data/lib/generators/active_snapshot/install/templates/create_snapshots_tables.rb.erb +2 -2
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 170e9249f3a190a13d266d247f3d11c96ce3f1a0eedb4660aaf17a16fccb9a55
|
4
|
+
data.tar.gz: c8a233b6eccb33f331fefa0020225f57d99cb2ffe4ec528e3dd0bb7509383816
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 25dd13a665ef38d7a5fbc9f461e531195753742b2968007616e3ebc5427d31c8e68188153091708f488137bc8de7ac09dbca2cd23c79af03fd6f2c607c5df7c7
|
7
|
+
data.tar.gz: b8277f58992d150915f477160f6e95f4863bea51563403b8c6e8c6d13237796747fb41491509d490be79d51bd0f6c33313c83e93048a6c34da0c1acfa915cea4
|
data/CHANGELOG.md
CHANGED
@@ -2,9 +2,19 @@ CHANGELOG
|
|
2
2
|
---------
|
3
3
|
|
4
4
|
- **Unreleased**
|
5
|
-
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.5.
|
5
|
+
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.5.2...master)
|
6
6
|
* Nothing yet
|
7
7
|
|
8
|
+
- **Unreleased**
|
9
|
+
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.5.1...v0.5.2)
|
10
|
+
* [#67](https://github.com/westonganger/active_snapshot/pull/67) - Switch default storage method to native SQL JSON columns. No longer recommend to set `ActiveSnapshot.config.storage_method`, its only retained to support legacy installations
|
11
|
+
* Drop support for Rails 6.0. Rails 6.1 is minimum required version now.
|
12
|
+
|
13
|
+
- **v0.5.1** - Nov 11, 2024
|
14
|
+
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.5.0...v0.5.1)
|
15
|
+
* [#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.
|
16
|
+
* [#63](https://github.com/westonganger/active_snapshot/pull/63) - Fix bug when enum value is nil
|
17
|
+
|
8
18
|
- **v0.5.0** - Nov 8, 2024
|
9
19
|
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.4.0...v0.5.0)
|
10
20
|
* [#61](https://github.com/westonganger/active_snapshot/pull/61) - Ensure snapshot record returned by `create_snapshot!` is `valid?`
|
data/README.md
CHANGED
@@ -53,25 +53,6 @@ It defines an optional extension to your model: `has_snapshot_children`.
|
|
53
53
|
|
54
54
|
It defines one instance method to your model: `create_snapshot!`
|
55
55
|
|
56
|
-
# Using a different storage format
|
57
|
-
|
58
|
-
By default ActiveSnapshot encodes objects to JSON and stores in the database as plain text. If you prefer to have YAML encoded columns or native JSON DB columns you can configure this as follows:
|
59
|
-
|
60
|
-
```ruby
|
61
|
-
ActiveSnapshot.config do |config|
|
62
|
-
config.storage_method = "serialized_json" # default, for text column
|
63
|
-
#config.storage_method = "serialized_yaml" # for text column
|
64
|
-
#config.storage_method = "native_json" # for json/jsonb column
|
65
|
-
end
|
66
|
-
```
|
67
|
-
|
68
|
-
If using a native json column, you should configure the `storage_method` before generating the migration. If this step was missed then you would need to create a migration to change the `:object` and `:metadata` columns to json (or jsonb)
|
69
|
-
|
70
|
-
```ruby
|
71
|
-
change_column :snapshots, :object, :json
|
72
|
-
change_column :snapshots, :metadata, :json
|
73
|
-
```
|
74
|
-
|
75
56
|
# Basic Usage
|
76
57
|
|
77
58
|
You now have access to the following methods:
|
@@ -131,9 +112,11 @@ end
|
|
131
112
|
|
132
113
|
Now when you run `create_snapshot!` the associations will be tracked accordingly
|
133
114
|
|
134
|
-
# Reifying
|
115
|
+
# Reifying Snapshots
|
116
|
+
|
117
|
+
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.
|
135
118
|
|
136
|
-
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.
|
119
|
+
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
120
|
|
138
121
|
```ruby
|
139
122
|
reified_parent, reified_children_hash = snapshot.fetch_reified_items
|
@@ -149,6 +132,49 @@ reified_parent, reified_children_hash = snapshot.fetch_reified_items
|
|
149
132
|
reified_children_hash.first.instance_variable_set("@readonly", false)
|
150
133
|
```
|
151
134
|
|
135
|
+
# Diffing Versions
|
136
|
+
|
137
|
+
You can use the following example code to generate your own diffs.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
snapshot = post.snapshots.find_by!(identifier: "some-identifier")
|
141
|
+
|
142
|
+
snapshot_item = snapshot.snapshot_items.find_by!(item_type: "Post")
|
143
|
+
|
144
|
+
old_attrs = snapshot_item.object
|
145
|
+
new_attrs = post.attributes # or could be another snapshot object
|
146
|
+
|
147
|
+
attrs_not_changed = old_attrs.to_a.intersection(new_attrs.to_a).to_h
|
148
|
+
|
149
|
+
attrs_changed = new_attrs.to_a - attrs_not_changed.to_a
|
150
|
+
```
|
151
|
+
|
152
|
+
# Important Data Considerations / Warnings
|
153
|
+
|
154
|
+
### Dropping columns
|
155
|
+
|
156
|
+
If you plan to use the snapshot restore capabilities please be aware:
|
157
|
+
|
158
|
+
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.
|
159
|
+
|
160
|
+
I recommend that you add an entry to this in your applications safe-migrations guidelines.
|
161
|
+
|
162
|
+
If you would like to detect if this situation has already ocurred you can use the following script:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
SnapshotItem.all.each do |snapshot_item|
|
166
|
+
snapshot_item.object.keys.each do |key|
|
167
|
+
klass = Class.const_get(snapshot_item.item_type)
|
168
|
+
|
169
|
+
if !klass.column_names.include?(key)
|
170
|
+
invalid_data = snapshot_item.object.slice(*klass.column_names)
|
171
|
+
|
172
|
+
raise "invalid data found - #{invalid_data}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
152
178
|
# Key Models Provided & Additional Customizations
|
153
179
|
|
154
180
|
A key aspect of this library is its simplicity and small API. For major functionality customizations we encourage you to first delete this gem and then copy this gems code directly into your repository.
|
@@ -1,12 +1,24 @@
|
|
1
1
|
module ActiveSnapshot
|
2
2
|
class Config
|
3
|
-
attr_reader :storage_method
|
4
|
-
|
5
3
|
def initialize
|
6
|
-
|
4
|
+
end
|
5
|
+
|
6
|
+
def storage_method
|
7
|
+
if @storage_method.nil?
|
8
|
+
if ActiveSnapshot::SnapshotItem.table_exists? && ActiveSnapshot::SnapshotItem.type_for_attribute(:object).type == :text
|
9
|
+
# for legacy active_snapshot configurations only
|
10
|
+
self.storage_method = 'serialized_json'
|
11
|
+
else
|
12
|
+
self.storage_method = 'native_json'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
@storage_method
|
7
17
|
end
|
8
18
|
|
9
19
|
def storage_method=(value)
|
20
|
+
# for legacy active_snapshot configurations only
|
21
|
+
|
10
22
|
value_str = value.to_s
|
11
23
|
|
12
24
|
if ['serialized_yaml', 'serialized_json', 'native_json'].include?(value_str)
|
@@ -17,15 +29,15 @@ module ActiveSnapshot
|
|
17
29
|
end
|
18
30
|
|
19
31
|
def storage_method_yaml?
|
20
|
-
|
32
|
+
# for legacy active_snapshot configurations only
|
33
|
+
storage_method == 'serialized_yaml'
|
21
34
|
end
|
22
35
|
|
23
|
-
def
|
24
|
-
|
36
|
+
def storage_method_serialized_json?
|
37
|
+
# for legacy active_snapshot configurations only
|
38
|
+
storage_method == 'serialized_json'
|
25
39
|
end
|
40
|
+
alias_method :storage_method_json?, :storage_method_serialized_json?
|
26
41
|
|
27
|
-
def storage_method_native_json?
|
28
|
-
@storage_method == 'native_json'
|
29
|
-
end
|
30
42
|
end
|
31
43
|
end
|
@@ -18,9 +18,11 @@ module ActiveSnapshot
|
|
18
18
|
def metadata
|
19
19
|
return @metadata if @metadata
|
20
20
|
|
21
|
-
if ActiveSnapshot.config.
|
21
|
+
if ActiveSnapshot.config.storage_method_serialized_json?
|
22
|
+
# for legacy active_snapshot configurations only
|
22
23
|
@metadata = JSON.parse(self[:metadata])
|
23
24
|
elsif ActiveSnapshot.config.storage_method_yaml?
|
25
|
+
# for legacy active_snapshot configurations only
|
24
26
|
yaml_method = "unsafe_load"
|
25
27
|
|
26
28
|
if !YAML.respond_to?("unsafe_load")
|
@@ -28,7 +30,7 @@ module ActiveSnapshot
|
|
28
30
|
end
|
29
31
|
|
30
32
|
@metadata = YAML.send(yaml_method, self[:metadata])
|
31
|
-
|
33
|
+
else
|
32
34
|
@metadata = self[:metadata]
|
33
35
|
end
|
34
36
|
end
|
@@ -36,25 +38,30 @@ module ActiveSnapshot
|
|
36
38
|
def metadata=(h)
|
37
39
|
@metadata = nil
|
38
40
|
|
39
|
-
if ActiveSnapshot.config.
|
41
|
+
if ActiveSnapshot.config.storage_method_serialized_json?
|
42
|
+
# for legacy active_snapshot configurations only
|
40
43
|
self[:metadata] = h.to_json
|
41
44
|
elsif ActiveSnapshot.config.storage_method_yaml?
|
45
|
+
# for legacy active_snapshot configurations only
|
42
46
|
self[:metadata] = YAML.dump(h)
|
43
|
-
|
47
|
+
else
|
44
48
|
self[:metadata] = h
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
48
52
|
def build_snapshot_item(instance, child_group_name: nil)
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
+
attrs = instance.attributes
|
54
|
+
|
55
|
+
if instance.class.defined_enums.any?
|
56
|
+
instance.class.defined_enums.slice(*attrs.keys).each do |enum_col_name, enum_mapping|
|
57
|
+
val = attrs.fetch(enum_col_name)
|
58
|
+
next if val.nil?
|
59
|
+
attrs[enum_col_name] = enum_mapping.fetch(val)
|
53
60
|
end
|
54
61
|
end
|
55
62
|
|
56
63
|
self.snapshot_items.new({
|
57
|
-
object:
|
64
|
+
object: attrs,
|
58
65
|
item_id: instance.id,
|
59
66
|
item_type: instance.class.name,
|
60
67
|
child_group_name: child_group_name,
|
@@ -109,7 +116,15 @@ module ActiveSnapshot
|
|
109
116
|
reified_parent = nil
|
110
117
|
|
111
118
|
snapshot_items.each do |si|
|
112
|
-
reified_item = si.item_type.constantize.new
|
119
|
+
reified_item = si.item_type.constantize.new
|
120
|
+
|
121
|
+
si.object.each do |k,v|
|
122
|
+
if reified_item.respond_to?("#{k}=")
|
123
|
+
reified_item[k] = v
|
124
|
+
else
|
125
|
+
# database column was likely dropped since the snapshot was created
|
126
|
+
end
|
127
|
+
end
|
113
128
|
|
114
129
|
if readonly
|
115
130
|
reified_item.readonly!
|
@@ -17,27 +17,29 @@ module ActiveSnapshot
|
|
17
17
|
def object
|
18
18
|
return @object if @object
|
19
19
|
|
20
|
-
if ActiveSnapshot.config.
|
20
|
+
if ActiveSnapshot.config.storage_method_serialized_json?
|
21
|
+
# for legacy active_snapshot configurations only
|
21
22
|
@object = self[:object] ? JSON.parse(self[:object]) : {}
|
22
23
|
elsif ActiveSnapshot.config.storage_method_yaml?
|
24
|
+
# for legacy active_snapshot configurations only
|
23
25
|
yaml_method = YAML.respond_to?(:unsafe_load) ? :unsafe_load : :load
|
24
26
|
|
25
27
|
@object = self[:object] ? YAML.public_send(yaml_method, self[:object]) : {}
|
26
|
-
elsif ActiveSnapshot.config.storage_method_native_json?
|
27
|
-
@object = self[:object]
|
28
28
|
else
|
29
|
-
|
29
|
+
@object = self[:object]
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
33
|
def object=(h)
|
34
34
|
@object = nil
|
35
35
|
|
36
|
-
if ActiveSnapshot.config.
|
36
|
+
if ActiveSnapshot.config.storage_method_serialized_json?
|
37
|
+
# for legacy active_snapshot configurations only
|
37
38
|
self[:object] = h.to_json
|
38
39
|
elsif ActiveSnapshot.config.storage_method_yaml?
|
40
|
+
# for legacy active_snapshot configurations only
|
39
41
|
self[:object] = YAML.dump(h)
|
40
|
-
|
42
|
+
else
|
41
43
|
self[:object] = h
|
42
44
|
end
|
43
45
|
end
|
@@ -51,7 +53,13 @@ module ActiveSnapshot
|
|
51
53
|
self.item = item_klass.new
|
52
54
|
end
|
53
55
|
|
54
|
-
|
56
|
+
object.each do |k,v|
|
57
|
+
if item.respond_to?("#{k}=")
|
58
|
+
item[k] = v
|
59
|
+
else
|
60
|
+
# database column was likely dropped since the snapshot was created
|
61
|
+
end
|
62
|
+
end
|
55
63
|
|
56
64
|
item.save!(validate: false, touch: false)
|
57
65
|
end
|
@@ -8,7 +8,7 @@ class <%= migration_name %> < ActiveRecord::Migration::Current
|
|
8
8
|
t.string :identifier, index: true
|
9
9
|
t.index [:identifier, :item_id, :item_type], unique: true
|
10
10
|
|
11
|
-
t
|
11
|
+
t.json :metadata
|
12
12
|
|
13
13
|
t.datetime :created_at, null: false
|
14
14
|
end
|
@@ -18,7 +18,7 @@ class <%= migration_name %> < ActiveRecord::Migration::Current
|
|
18
18
|
t.belongs_to :item, polymorphic: true, null: false, index: true
|
19
19
|
t.index [:snapshot_id, :item_id, :item_type], unique: true
|
20
20
|
|
21
|
-
t
|
21
|
+
t.json :object, null: false
|
22
22
|
|
23
23
|
t.datetime :created_at, null: false
|
24
24
|
t.string :child_group_name
|
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.5.
|
4
|
+
version: 0.5.2
|
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-
|
11
|
+
date: 2024-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '6.
|
19
|
+
version: '6.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '6.
|
26
|
+
version: '6.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: railties
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|