active_snapshot 0.2.4 → 0.3.0
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 -3
- data/README.md +27 -8
- data/lib/active_snapshot/config.rb +31 -0
- data/lib/active_snapshot/models/concerns/snapshots_concern.rb +9 -1
- data/lib/active_snapshot/models/snapshot.rb +24 -9
- data/lib/active_snapshot/models/snapshot_item.rb +22 -7
- data/lib/active_snapshot/version.rb +1 -1
- data/lib/active_snapshot.rb +11 -0
- data/lib/generators/active_snapshot/install/templates/create_snapshots_tables.rb.erb +3 -3
- data/lib/generators/active_snapshot/migration_generator.rb +1 -1
- data/test/dummy_app/config/application.rb +4 -7
- data/test/dummy_app/db/migrate/20210306100122_create_snapshots_tables.rb +15 -3
- data/test/dummy_app/db/test.sqlite3 +0 -0
- data/test/dummy_app/log/test.log +34414 -0
- data/test/models/snapshot_item_test.rb +1 -1
- data/test/models/snapshot_test.rb +16 -8
- data/test/models/snapshots_concern_test.rb +29 -4
- data/test/test_helper.rb +26 -2
- data/test/unit/active_snapshot_test.rb +2 -1
- data/test/unit/config_test.rb +66 -0
- metadata +21 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89b0b688c46d6f42572e718276d0bb406beedf4aa3fcca78329ebd598d7a53f2
|
4
|
+
data.tar.gz: 210d3f36e4a8c12d4e85665e6d0324a0054333771ab0d7cb6b104f28be7b5b46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ff8613e2c3eaf100e8234b7da297ffc82d47cc387c3b7d22511f947b6e934382261751fb8c8518f5c06e8274341432416b9bcd42e9ab07b513717de9bfa5116
|
7
|
+
data.tar.gz: a9d43b41f96ace17d9a9320bbe231dabc326effc049ea1d82f44cc0cae920a61aeecc0d6a43c412e1680740a944bee56a253bdd0b8cf7b2770ea24fa71da0fde
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,22 @@
|
|
1
1
|
CHANGELOG
|
2
2
|
---------
|
3
3
|
|
4
|
-
- **
|
5
|
-
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.
|
6
|
-
|
4
|
+
- **Unreleased**
|
5
|
+
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.3.0...master)
|
6
|
+
- Nothing yet
|
7
|
+
|
8
|
+
- **v0.3.0** - November 14, 2022
|
9
|
+
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.2.4...v0.3.0)
|
10
|
+
* [PR #24](https://github.com/westonganger/active_snapshot/pull/24) - Fix arguments for db migration for mysql
|
11
|
+
* [PR #29](https://github.com/westonganger/active_snapshot/pull/29) - Deprecate :identifier argument as a positional argument
|
12
|
+
* [PR #30](https://github.com/westonganger/active_snapshot/pull/30) - Make snapshot identifier optional
|
13
|
+
* [PR #32](https://github.com/westonganger/active_snapshot/pull/26) - Add configuration option `ActiveSnapshot.config.storage_method = 'serialized_json'` with support for `serialized_json`, `serialized_yaml`, `native_json`
|
14
|
+
* [PR #32](https://github.com/westonganger/active_snapshot/pull/32) - Change default storage method from `serialized_yaml` to `serialized_json`.
|
15
|
+
* [PR #32](https://github.com/westonganger/active_snapshot/pull/32) - `snapshot.metadata` and `snapshot_item.object` no longer return a HashWithIndifferentAccess. Now they simply return a regular Hash.
|
16
|
+
* **Upgrade Instructions**
|
17
|
+
* Change all instances of `create_snapshot!("my-snapshot-1"` to `create_snapshot!(identifier: "my-snapshot-1"`
|
18
|
+
* Create a migration with the following `change_column_null :snapshots, :identifier, true` to remove the null constraint here
|
19
|
+
* If you have existing snapshots from a previous version then please set `ActiveSnapshot.config.storage_method = "serialized_yaml"`
|
7
20
|
|
8
21
|
- **v0.2.4** - Feb 25, 2022
|
9
22
|
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.2.3...v0.2.4)
|
data/README.md
CHANGED
@@ -53,6 +53,25 @@ 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
|
+
|
56
75
|
# Basic Usage
|
57
76
|
|
58
77
|
You now have access to the following methods:
|
@@ -60,9 +79,9 @@ You now have access to the following methods:
|
|
60
79
|
```ruby
|
61
80
|
post = Post.first
|
62
81
|
|
63
|
-
# Create snapshot
|
82
|
+
# Create snapshot, all fields are optional
|
64
83
|
snapshot = post.create_snapshot!(
|
65
|
-
"snapshot_1",
|
84
|
+
identifier: "snapshot_1",
|
66
85
|
user: current_user,
|
67
86
|
metadata: {
|
68
87
|
foo: :bar
|
@@ -117,15 +136,15 @@ Now when you run `create_snapshot!` the associations will be tracked accordingly
|
|
117
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.
|
118
137
|
|
119
138
|
```ruby
|
120
|
-
|
139
|
+
reified_parent, reified_children_hash = snapshot.fetch_reified_items
|
121
140
|
```
|
122
141
|
|
123
142
|
As a safety these records have the `@readonly = true` attribute set on them. If you want to perform any write actions on the returned instances you will have to set `@readonly = nil`.
|
124
143
|
|
125
144
|
```ruby
|
126
|
-
|
127
|
-
|
128
|
-
|
145
|
+
reified_parent, reified_children_hash = snapshot.fetch_reified_items
|
146
|
+
|
147
|
+
reified_parent.instance_variable_set("@readonly", false)
|
129
148
|
```
|
130
149
|
|
131
150
|
# Key Models Provided & Additional Customizations
|
@@ -138,10 +157,10 @@ I strongly encourage you to read the code for this library to understand how it
|
|
138
157
|
* Defines `snapshots` and `snapshot_items` has_many associations
|
139
158
|
* Defines `create_snapshot!` and `has_snapshot_children` methods
|
140
159
|
- [Snapshot](./lib/active_snapshot/models/snapshot.rb)
|
141
|
-
* Contains a unique `identifier` column
|
160
|
+
* Contains a unique `identifier` column (optional, but available for custom identification purposes)
|
142
161
|
* `has_many :item_snapshots`
|
143
162
|
- [SnapshotItem](./lib/active_snapshot/models/snapshot_item.rb)
|
144
|
-
* Contains `object` column
|
163
|
+
* Contains `object` column which contains an encoded database row
|
145
164
|
* `belongs_to :snapshot`
|
146
165
|
|
147
166
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ActiveSnapshot
|
2
|
+
class Config
|
3
|
+
attr_reader :storage_method
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@storage_method = 'serialized_json'
|
7
|
+
end
|
8
|
+
|
9
|
+
def storage_method=(value)
|
10
|
+
value_str = value.to_s
|
11
|
+
|
12
|
+
if ['serialized_yaml', 'serialized_json', 'native_json'].include?(value_str)
|
13
|
+
@storage_method = value_str
|
14
|
+
else
|
15
|
+
raise ArgumentError.new("Invalid storage_method provided")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def storage_method_yaml?
|
20
|
+
@storage_method == 'serialized_yaml'
|
21
|
+
end
|
22
|
+
|
23
|
+
def storage_method_json?
|
24
|
+
@storage_method == 'serialized_json'
|
25
|
+
end
|
26
|
+
|
27
|
+
def storage_method_native_json?
|
28
|
+
@storage_method == 'native_json'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -8,7 +8,13 @@ module ActiveSnapshot
|
|
8
8
|
has_many :snapshot_items, as: :item, class_name: 'ActiveSnapshot::SnapshotItem'
|
9
9
|
end
|
10
10
|
|
11
|
-
def create_snapshot!(identifier, user: nil, metadata: nil)
|
11
|
+
def create_snapshot!(legacy_identifier=nil, identifier: nil, user: nil, metadata: nil)
|
12
|
+
if identifier.nil? && legacy_identifier
|
13
|
+
identifier = legacy_identifier
|
14
|
+
|
15
|
+
ActiveSupport::Deprecation.warn(LEGACY_POSITIONAL_ARGUMENT_WARNING)
|
16
|
+
end
|
17
|
+
|
12
18
|
snapshot = snapshots.create!({
|
13
19
|
identifier: identifier,
|
14
20
|
user_id: (user.id if user),
|
@@ -119,5 +125,7 @@ module ActiveSnapshot
|
|
119
125
|
end
|
120
126
|
end
|
121
127
|
|
128
|
+
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
|
129
|
+
|
122
130
|
end
|
123
131
|
end
|
@@ -12,27 +12,42 @@ module ActiveSnapshot
|
|
12
12
|
|
13
13
|
validates :item_id, presence: true
|
14
14
|
validates :item_type, presence: true
|
15
|
-
validates :identifier,
|
15
|
+
validates :identifier, uniqueness: { scope: [:item_id, :item_type], allow_nil: true}
|
16
16
|
validates :user_type, presence: true, if: :user_id
|
17
17
|
|
18
18
|
def metadata
|
19
|
-
|
19
|
+
return @metadata if @metadata
|
20
20
|
|
21
|
-
if
|
22
|
-
|
23
|
-
|
21
|
+
if ActiveSnapshot.config.storage_method_json?
|
22
|
+
@metadata = JSON.parse(self[:metadata])
|
23
|
+
elsif ActiveSnapshot.config.storage_method_yaml?
|
24
|
+
yaml_method = "unsafe_load"
|
25
|
+
|
26
|
+
if !YAML.respond_to?("unsafe_load")
|
27
|
+
yaml_method = "load"
|
28
|
+
end
|
24
29
|
|
25
|
-
|
30
|
+
@metadata = YAML.send(yaml_method, self[:metadata])
|
31
|
+
elsif ActiveSnapshot.config.storage_method_native_json?
|
32
|
+
@metadata = self[:metadata]
|
33
|
+
end
|
26
34
|
end
|
27
35
|
|
28
36
|
def metadata=(h)
|
29
37
|
@metadata = nil
|
30
|
-
|
38
|
+
|
39
|
+
if ActiveSnapshot.config.storage_method_json?
|
40
|
+
self[:metadata] = h.to_json
|
41
|
+
elsif ActiveSnapshot.config.storage_method_yaml?
|
42
|
+
self[:metadata] = YAML.dump(h)
|
43
|
+
elsif ActiveSnapshot.config.storage_method_native_json?
|
44
|
+
self[:metadata] = h
|
45
|
+
end
|
31
46
|
end
|
32
47
|
|
33
48
|
def build_snapshot_item(instance, child_group_name: nil)
|
34
49
|
self.snapshot_items.new({
|
35
|
-
object: instance.attributes,
|
50
|
+
object: instance.attributes,
|
36
51
|
item_id: instance.id,
|
37
52
|
item_type: instance.class.name,
|
38
53
|
child_group_name: child_group_name,
|
@@ -86,7 +101,7 @@ module ActiveSnapshot
|
|
86
101
|
|
87
102
|
reified_parent = nil
|
88
103
|
|
89
|
-
snapshot_items.each do |si|
|
104
|
+
snapshot_items.each do |si|
|
90
105
|
reified_item = si.item_type.constantize.new(si.object)
|
91
106
|
|
92
107
|
reified_item.readonly!
|
@@ -14,23 +14,38 @@ module ActiveSnapshot
|
|
14
14
|
validates :item_type, presence: true, uniqueness: { scope: [:snapshot_id, :item_id] }
|
15
15
|
|
16
16
|
def object
|
17
|
-
|
17
|
+
return @object if @object
|
18
18
|
|
19
|
-
if
|
20
|
-
|
21
|
-
|
19
|
+
if ActiveSnapshot.config.storage_method_json?
|
20
|
+
@object = JSON.parse(self[:object])
|
21
|
+
elsif ActiveSnapshot.config.storage_method_yaml?
|
22
|
+
yaml_method = "unsafe_load"
|
23
|
+
|
24
|
+
if !YAML.respond_to?("unsafe_load")
|
25
|
+
yaml_method = "load"
|
26
|
+
end
|
22
27
|
|
23
|
-
|
28
|
+
@object = YAML.send(yaml_method, self[:object])
|
29
|
+
elsif ActiveSnapshot.config.storage_method_native_json?
|
30
|
+
@object = self[:object]
|
31
|
+
end
|
24
32
|
end
|
25
33
|
|
26
34
|
def object=(h)
|
27
35
|
@object = nil
|
28
|
-
|
36
|
+
|
37
|
+
if ActiveSnapshot.config.storage_method_json?
|
38
|
+
self[:object] = h.to_json
|
39
|
+
elsif ActiveSnapshot.config.storage_method_yaml?
|
40
|
+
self[:object] = YAML.dump(h)
|
41
|
+
elsif ActiveSnapshot.config.storage_method_native_json?
|
42
|
+
self[:object] = h
|
43
|
+
end
|
29
44
|
end
|
30
45
|
|
31
46
|
def restore_item!
|
32
47
|
### Add any custom logic here
|
33
|
-
|
48
|
+
|
34
49
|
if !item
|
35
50
|
item_klass = item_type.constantize
|
36
51
|
|
data/lib/active_snapshot.rb
CHANGED
@@ -2,6 +2,7 @@ require "active_record"
|
|
2
2
|
require "activerecord-import"
|
3
3
|
|
4
4
|
require "active_snapshot/version"
|
5
|
+
require "active_snapshot/config"
|
5
6
|
|
6
7
|
require "active_snapshot/models/snapshot"
|
7
8
|
require "active_snapshot/models/snapshot_item"
|
@@ -14,4 +15,14 @@ module ActiveSnapshot
|
|
14
15
|
included do
|
15
16
|
include ActiveSnapshot::SnapshotsConcern
|
16
17
|
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
|
17
28
|
end
|
@@ -3,16 +3,16 @@ class <%= migration_name %> < ActiveRecord::Migration::Current
|
|
3
3
|
def change
|
4
4
|
create_table :snapshots<%= table_options %> do |t|
|
5
5
|
t.belongs_to :item, polymorphic: true, null: false, index: true
|
6
|
-
t.string :identifier,
|
6
|
+
t.string :identifier, unique: [:item_id, :item_type], index: true
|
7
7
|
t.belongs_to :user, polymorphic: true
|
8
|
-
t.text :metadata
|
8
|
+
t.<%= ActiveSnapshot.config.storage_method == 'native_json' ? 'json' : 'text' %> :metadata
|
9
9
|
t.datetime :created_at, null: false
|
10
10
|
end
|
11
11
|
|
12
12
|
create_table :snapshot_items<%= table_options %> do |t|
|
13
13
|
t.belongs_to :snapshot, null: false, index: true
|
14
14
|
t.belongs_to :item, polymorphic: true, null: false, unique: [:snapshot_id], index: true
|
15
|
-
t.text :object, null: false
|
15
|
+
t.<%= ActiveSnapshot.config.storage_method == 'native_json' ? 'json' : 'text' %> :object, null: false
|
16
16
|
t.datetime :created_at, null: false
|
17
17
|
t.string :child_group_name
|
18
18
|
end
|
@@ -6,6 +6,10 @@ Bundler.require
|
|
6
6
|
|
7
7
|
module Dummy
|
8
8
|
class Application < Rails::Application
|
9
|
+
if Rails::VERSION::STRING.to_f >= 5.1
|
10
|
+
config.load_defaults Rails::VERSION::STRING.to_f
|
11
|
+
end
|
12
|
+
|
9
13
|
# Settings in config/environments/* take precedence over those specified here.
|
10
14
|
# Application configuration should go into files in config/initializers
|
11
15
|
# -- all .rb files in that directory are automatically loaded.
|
@@ -42,12 +46,5 @@ module Dummy
|
|
42
46
|
config.after_initialize do
|
43
47
|
ActiveRecord::Migration.migrate(Rails.root.join("db/migrate/*").to_s)
|
44
48
|
end
|
45
|
-
|
46
|
-
if ActiveRecord.respond_to?(:gem_version)
|
47
|
-
gem_version = ActiveRecord.gem_version
|
48
|
-
if gem_version.to_s.start_with?("5.2.")
|
49
|
-
config.active_record.sqlite3.represent_boolean_as_integer = true
|
50
|
-
end
|
51
|
-
end
|
52
49
|
end
|
53
50
|
end
|
@@ -3,16 +3,28 @@ class CreateSnapshotsTables < ActiveRecord::Migration::Current
|
|
3
3
|
def change
|
4
4
|
create_table :snapshots do |t|
|
5
5
|
t.belongs_to :item, polymorphic: true, null: false, index: true
|
6
|
-
t.string :identifier,
|
6
|
+
t.string :identifier, unique: true, index: true
|
7
7
|
t.belongs_to :user, polymorphic: true
|
8
|
-
|
8
|
+
|
9
|
+
if ActiveSnapshot.config.storage_method_native_json?
|
10
|
+
t.json :metadata
|
11
|
+
else
|
12
|
+
t.text :metadata
|
13
|
+
end
|
14
|
+
|
9
15
|
t.datetime :created_at, null: false
|
10
16
|
end
|
11
17
|
|
12
18
|
create_table :snapshot_items do |t|
|
13
19
|
t.belongs_to :snapshot, null: false, index: true
|
14
20
|
t.belongs_to :item, polymorphic: true, null: false, unique: [:snapshot_id], index: true
|
15
|
-
|
21
|
+
|
22
|
+
if ActiveSnapshot.config.storage_method_native_json?
|
23
|
+
t.json :object, null: false
|
24
|
+
else
|
25
|
+
t.text :object, null: false
|
26
|
+
end
|
27
|
+
|
16
28
|
t.datetime :created_at, null: false
|
17
29
|
t.string :child_group_name
|
18
30
|
end
|
Binary file
|