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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dcfaf7d239a170431fa82d70ab56b18bfb54a327b46376017357368b38f484b3
4
- data.tar.gz: 833d8fb9ca610983f9a9e510cd6fc94776f043bf3d35ec7e052a2ade87cf1e5a
3
+ metadata.gz: 170e9249f3a190a13d266d247f3d11c96ce3f1a0eedb4660aaf17a16fccb9a55
4
+ data.tar.gz: c8a233b6eccb33f331fefa0020225f57d99cb2ffe4ec528e3dd0bb7509383816
5
5
  SHA512:
6
- metadata.gz: a412ace11cbeee26528d5e3b58116a1b716f415ab4c1aead7978e6c5f2ccd1db49ee7c08ea0120226d1f66d1d099384cd6cadec5353a3f2fa5aee28d32b825e8
7
- data.tar.gz: 5290672baca7ed50d67e9654acb85851e997f9c601d6fa6aae898cf51ba448686595e30b61c4819eafe6f81608850777a9b75ca5a995c18f9f6b6d8047542bf8
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.0...master)
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 Snapshot Items
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
- @storage_method = 'serialized_json'
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
- @storage_method == 'serialized_yaml'
32
+ # for legacy active_snapshot configurations only
33
+ storage_method == 'serialized_yaml'
21
34
  end
22
35
 
23
- def storage_method_json?
24
- @storage_method == 'serialized_json'
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.storage_method_json?
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
- elsif ActiveSnapshot.config.storage_method_native_json?
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.storage_method_json?
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
- elsif ActiveSnapshot.config.storage_method_native_json?
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
- attributes = instance.attributes
50
- attributes.each do |k, v|
51
- if instance.class.defined_enums.key?(k)
52
- attributes[k] = instance.class.defined_enums.fetch(k).fetch(v)
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: attributes,
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(si.object)
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.storage_method_json?
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
- raise StandardError, "Unsupported storage_method: `#{ActiveSnapshot.config.storage_method}`"
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.storage_method_json?
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
- elsif ActiveSnapshot.config.storage_method_native_json?
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
- item.assign_attributes(object)
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
@@ -1,3 +1,3 @@
1
1
  module ActiveSnapshot
2
- VERSION = "0.5.0".freeze
2
+ VERSION = "0.5.2".freeze
3
3
  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.<%= ActiveSnapshot.config.storage_method == 'native_json' ? 'json' : 'text' %> :metadata
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.<%= ActiveSnapshot.config.storage_method == 'native_json' ? 'json' : 'text' %> :object, null: false
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.0
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-08 00:00:00.000000000 Z
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.0'
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.0'
26
+ version: '6.1'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: railties
29
29
  requirement: !ruby/object:Gem::Requirement