active_snapshot 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +10 -0
- data/LICENSE +21 -0
- data/README.md +145 -0
- data/Rakefile +52 -0
- data/lib/active_snapshot.rb +17 -0
- data/lib/active_snapshot/models/concerns/snapshots_concern.rb +111 -0
- data/lib/active_snapshot/models/snapshot.rb +104 -0
- data/lib/active_snapshot/models/snapshot_item.rb +40 -0
- data/lib/active_snapshot/version.rb +3 -0
- data/lib/generators/active_snapshot/install/install_generator.rb +22 -0
- data/lib/generators/active_snapshot/install/templates/create_snapshots_tables.rb.erb +21 -0
- data/lib/generators/active_snapshot/migration_generator.rb +76 -0
- data/test/dummy_app/Rakefile +7 -0
- data/test/dummy_app/app/assets/config/manifest.js +3 -0
- data/test/dummy_app/app/assets/javascripts/application.js +0 -0
- data/test/dummy_app/app/assets/stylesheets/application.css +3 -0
- data/test/dummy_app/app/controllers/application_controller.rb +3 -0
- data/test/dummy_app/app/models/application_record.rb +3 -0
- data/test/dummy_app/app/models/comment.rb +5 -0
- data/test/dummy_app/app/models/post.rb +13 -0
- data/test/dummy_app/app/models/volatile_post.rb +5 -0
- data/test/dummy_app/app/views/layouts/application.html.erb +14 -0
- data/test/dummy_app/config.ru +4 -0
- data/test/dummy_app/config/application.rb +61 -0
- data/test/dummy_app/config/boot.rb +10 -0
- data/test/dummy_app/config/database.yml +20 -0
- data/test/dummy_app/config/environment.rb +5 -0
- data/test/dummy_app/config/environments/development.rb +30 -0
- data/test/dummy_app/config/environments/production.rb +60 -0
- data/test/dummy_app/config/environments/test.rb +41 -0
- data/test/dummy_app/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy_app/config/initializers/inflections.rb +10 -0
- data/test/dummy_app/config/initializers/mime_types.rb +5 -0
- data/test/dummy_app/config/initializers/secret_token.rb +11 -0
- data/test/dummy_app/config/initializers/session_store.rb +8 -0
- data/test/dummy_app/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy_app/config/locales/en.yml +5 -0
- data/test/dummy_app/config/routes.rb +6 -0
- data/test/dummy_app/config/secrets.yml +22 -0
- data/test/dummy_app/db/migrate/20210128155312_set_up_test_tables.rb +19 -0
- data/test/dummy_app/db/migrate/20210306070749_create_snapshots_tables.rb +21 -0
- data/test/dummy_app/log/development.log +9 -0
- data/test/dummy_app/log/test.log +46812 -0
- data/test/generators/active_snapshot/install_generator_test.rb +31 -0
- data/test/models/snapshot_item_test.rb +75 -0
- data/test/models/snapshot_test.rb +109 -0
- data/test/models/snapshots_concern_test.rb +111 -0
- data/test/test_helper.rb +71 -0
- data/test/unit/active_snapshot_test.rb +60 -0
- data/test/unit/errors_test.rb +12 -0
- metadata +231 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 59dcd6fb412c1bb5f9fdc5bd9c447e4af9811837e0523da9cf7db4c12ce857fa
|
4
|
+
data.tar.gz: 31942b2cc72329c428f3a322d18d934b531bdd29b7e8a3dc956433dd0e822483
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 367930717d8d578fe2ace8ddd3427a44f55fd52089e18862c7b0751dbc294ef6641c509d6b775fc3dc6e9f21e50ae9ef7b91e4b1c89a45b240da6b0343dbf7cf
|
7
|
+
data.tar.gz: ac0193a62d929e16ed19f8918b7fe1ad96b251819e98770add848d041ce7c4a7cad1771a158ca63130ed05e446ae53b2d0357cf4bb0a33a262c0e6de1543597e
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
CHANGELOG
|
2
|
+
---------
|
3
|
+
|
4
|
+
- **Unreleased**
|
5
|
+
* [View Diff](https://github.com/westonganger/active_snapshot/compare/v0.1.0...master)
|
6
|
+
* Nothing yet
|
7
|
+
|
8
|
+
- **v0.1.0** - Mar 5, 2021
|
9
|
+
* [View Diff](https://github.com/westonganger/active_snapshot/compare/edbbfd3...v0.1.0)
|
10
|
+
* Gem Initial Release
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Weston Ganger
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
# ActiveSnapshot
|
2
|
+
|
3
|
+
<a href="https://badge.fury.io/rb/active_snapshot" target="_blank"><img height="21" style='border:0px;height:21px;' border='0' src="https://badge.fury.io/rb/active_snapshot.svg" alt="Gem Version"></a>
|
4
|
+
<a href='https://github.com/westonganger/active_snapshot/actions' target='_blank'><img src="https://github.com/westonganger/active_snapshot/workflows/Tests/badge.svg" style="max-width:100%;" height='21' style='border:0px;height:21px;' border='0' alt="CI Status"></a>
|
5
|
+
<a href='https://rubygems.org/gems/active_snapshot' target='_blank'><img height='21' style='border:0px;height:21px;' src='https://ruby-gem-downloads-badge.herokuapp.com/active_snapshot?label=rubygems&type=total&total_label=downloads&color=brightgreen' border='0' alt='RubyGems Downloads' /></a>
|
6
|
+
|
7
|
+
Simplified snapshots and restoration for ActiveRecord models and associations with a transparent white-box implementation.
|
8
|
+
|
9
|
+
Key Features:
|
10
|
+
|
11
|
+
- Create and Restore snapshots of a parent record and any specified child records
|
12
|
+
- Predictible and explicit behaviour provides much needed clarity to your restore logic
|
13
|
+
- Snapshots are created upon request only, we do not use any callbacks
|
14
|
+
- Tiny method footprint so its easy to completely override the logic later
|
15
|
+
|
16
|
+
Why This Library:
|
17
|
+
|
18
|
+
Model Versioning and Restoration require concious 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
|
+
|
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.
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
# Installation
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
gem 'active_snapshot'
|
28
|
+
```
|
29
|
+
|
30
|
+
Then generate and run the necessary migrations to setup the `snapshots` and `snapshot_items` tables.
|
31
|
+
|
32
|
+
```
|
33
|
+
rails generate active_snapshot:install
|
34
|
+
rake db:migrate
|
35
|
+
```
|
36
|
+
|
37
|
+
Then add `include ActiveSnapshot` to your ApplicationRecord or individual models.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
class ApplicationRecord < ActiveRecord::Base
|
41
|
+
include ActiveSortOrder
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
This defines the following associations on your models:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
has_many :snapshots, as: :item, class_name: 'Snapshot'
|
49
|
+
has_many :snapshot_items, as: :item, class_name: 'SnapshotItem'
|
50
|
+
```
|
51
|
+
|
52
|
+
It defines an optional extension to your model `has_snapshot_children`.
|
53
|
+
|
54
|
+
It defines one instance method to your model: `create_snapshot!`
|
55
|
+
|
56
|
+
# Basic Usage
|
57
|
+
|
58
|
+
You now have access to the following methods:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
post = Post.first
|
62
|
+
|
63
|
+
# Create snapshot grouped by identifier, only :identifier argument is required, all others are optional
|
64
|
+
snapshot = post.create_snapshot!(
|
65
|
+
identifier: "snapshot_1", # Required
|
66
|
+
user: current_user,
|
67
|
+
metadata: {
|
68
|
+
foo: :bar
|
69
|
+
},
|
70
|
+
)
|
71
|
+
|
72
|
+
# Restore snapshot and all its child snapshots
|
73
|
+
snapshot.restore!
|
74
|
+
|
75
|
+
# Destroy snapshot and all its child snapshots
|
76
|
+
# must be performed manually, snapshots and snapshot items are NEVER destroyed automatically
|
77
|
+
snapshot.destroy!
|
78
|
+
```
|
79
|
+
|
80
|
+
# Restoring Associated / Child Records
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class Post < ActiveRecord::Base
|
84
|
+
include ActiveSnapshot
|
85
|
+
|
86
|
+
has_snapshot_children do
|
87
|
+
### Executed in the context of the instance / self
|
88
|
+
|
89
|
+
### In this example, we choose to do a fresh load from the database of the record and all associated records from the database
|
90
|
+
instance = self.class.includes(:comments, :ip_address).find(id)
|
91
|
+
|
92
|
+
### Define the associated records that will be restored
|
93
|
+
{
|
94
|
+
comments: instance.comments,
|
95
|
+
tags: {
|
96
|
+
records: instance.tags
|
97
|
+
},
|
98
|
+
ip_address: {
|
99
|
+
record: instance.ip_address,
|
100
|
+
delete_method: ->(item){ item.release! }
|
101
|
+
}
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
Now when you run `create_snapshot!` the associations will be tracked accordingly
|
109
|
+
|
110
|
+
# Reifying Snapshot Items
|
111
|
+
|
112
|
+
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.
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
reified_items = snapshot.fetch_reified_items
|
116
|
+
```
|
117
|
+
|
118
|
+
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`.
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
writable_reified_items = snapshot.fetch_reified_items.transform_values do |array|
|
122
|
+
array.map{|x| x.instance_variable_set("@readonly", false); x}
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
# Key Models Provided & Additional Customizations
|
127
|
+
|
128
|
+
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.
|
129
|
+
|
130
|
+
I strongly encourage you to read the code for this library to understand how it works within your project so that you are capable of customizing the functionality later.
|
131
|
+
|
132
|
+
- [SnapshotsConcern](./lib/active_snapshot/models/concerns/snapshots_concern.rb)
|
133
|
+
* Defines `snapshots` and `snapshot_items` has_many associations
|
134
|
+
* Defines `create_snapshot!` and `has_snapshot_children` methods
|
135
|
+
- [Snapshot](./lib/active_snapshot/models/snapshot.rb)
|
136
|
+
* Contains a unique `identifier` column
|
137
|
+
* `has_many :item_snapshots`
|
138
|
+
- [SnapshotItem](./lib/active_snapshot/models/snapshot_item.rb)
|
139
|
+
* Contains `object` column with yaml encoded model instance `attributes`
|
140
|
+
* `belongs_to :snapshot`
|
141
|
+
|
142
|
+
|
143
|
+
# Credits
|
144
|
+
|
145
|
+
Created & Maintained by [Weston Ganger](https://westonganger.com) - [@westonganger](https://github.com/westonganger)
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/lib/active_snapshot/version.rb')
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rake/testtask"
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
t.libs << "test"
|
8
|
+
t.libs << "lib"
|
9
|
+
t.test_files = FileList["test/**/*_test.rb"]
|
10
|
+
end
|
11
|
+
|
12
|
+
task :db_prepare do
|
13
|
+
if ENV['CI'] != "true"
|
14
|
+
begin
|
15
|
+
require 'pg'
|
16
|
+
|
17
|
+
### FOR LOCAL TESTING
|
18
|
+
`dropdb active_snapshot_test && createdb active_snapshot_test` rescue true
|
19
|
+
rescue LoadError
|
20
|
+
# Do nothing
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Dir.chdir("test/dummy_app") do
|
25
|
+
### Instantiates Rails
|
26
|
+
require File.expand_path("test/dummy_app/config/environment.rb", __dir__)
|
27
|
+
|
28
|
+
migration_path = "db/migrate"
|
29
|
+
|
30
|
+
### Generate Migration
|
31
|
+
require "generators/active_snapshot/install/install_generator"
|
32
|
+
|
33
|
+
generator = ActiveSnapshot::InstallGenerator.new
|
34
|
+
|
35
|
+
Dir.glob(Rails.root.join(migration_path, "*#{generator.class::MIGRATION_NAME}.rb")).each do |f|
|
36
|
+
FileUtils.rm(f)
|
37
|
+
end
|
38
|
+
|
39
|
+
generator.create_migration_file
|
40
|
+
end ### END chdir
|
41
|
+
end
|
42
|
+
|
43
|
+
task default: [:db_prepare, :test]
|
44
|
+
|
45
|
+
task :console do
|
46
|
+
require 'active_snapshot'
|
47
|
+
|
48
|
+
require_relative 'test/dummy_app/app/models/post'
|
49
|
+
|
50
|
+
require 'irb'
|
51
|
+
binding.irb
|
52
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "activerecord-import"
|
3
|
+
|
4
|
+
require "active_snapshot/version"
|
5
|
+
|
6
|
+
require "active_snapshot/models/snapshot"
|
7
|
+
require "active_snapshot/models/snapshot_item"
|
8
|
+
|
9
|
+
require "active_snapshot/models/concerns/snapshots_concern"
|
10
|
+
|
11
|
+
module ActiveSnapshot
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
|
14
|
+
included do
|
15
|
+
include ActiveSnapshot::SnapshotsConcern
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module ActiveSnapshot
|
2
|
+
module SnapshotsConcern
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
### We do NOT mark these as dependent: :destroy, the developer must manually destroy the snapshots or individual snapshot items
|
7
|
+
has_many :snapshots, as: :item, class_name: 'ActiveSnapshot::Snapshot'
|
8
|
+
has_many :snapshot_items, as: :item, class_name: 'ActiveSnapshot::SnapshotItem'
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_snapshot!(identifier, user: nil, metadata: nil)
|
12
|
+
snapshot = snapshots.create!({
|
13
|
+
identifier: identifier,
|
14
|
+
user_id: (user.id if user),
|
15
|
+
user_type: (user.class.name if user),
|
16
|
+
metadata: (metadata || {}),
|
17
|
+
})
|
18
|
+
|
19
|
+
snapshot_items = []
|
20
|
+
|
21
|
+
snapshot_items << snapshot.build_snapshot_item(self)
|
22
|
+
|
23
|
+
snapshot_children = self.children_to_snapshot
|
24
|
+
|
25
|
+
if snapshot_children
|
26
|
+
snapshot_children.each do |child_group_name, h|
|
27
|
+
h[:records].each do |child_item|
|
28
|
+
snapshot_items << snapshot.build_snapshot_item(child_item, child_group_name: child_group_name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
SnapshotItem.import(snapshot_items, validate: true)
|
34
|
+
|
35
|
+
snapshot
|
36
|
+
end
|
37
|
+
|
38
|
+
class_methods do
|
39
|
+
|
40
|
+
def has_snapshot_children(&block)
|
41
|
+
if !block_given? && !defined?(@snapshot_children_proc)
|
42
|
+
raise ArgumentError.new("Invalid `has_snapshot_children` requires block to be defined")
|
43
|
+
elsif block_given?
|
44
|
+
@snapshot_children_proc = block
|
45
|
+
else
|
46
|
+
@snapshot_children_proc
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
def children_to_snapshot
|
53
|
+
snapshot_children_proc = self.class.has_snapshot_children
|
54
|
+
|
55
|
+
if !snapshot_children_proc
|
56
|
+
raise ArgumentError.new("`has_snapshot_children` must be defined on your class")
|
57
|
+
else
|
58
|
+
records = self.instance_exec(&snapshot_children_proc)
|
59
|
+
|
60
|
+
if records.is_a?(Hash)
|
61
|
+
records = records.with_indifferent_access
|
62
|
+
else
|
63
|
+
raise ArgumentError.new("Invalid `has_snapshot_children` definition. Must return a Hash")
|
64
|
+
end
|
65
|
+
|
66
|
+
snapshot_children = {}.with_indifferent_access
|
67
|
+
|
68
|
+
records.each do |assoc_name, opts|
|
69
|
+
snapshot_children[assoc_name] = {}
|
70
|
+
|
71
|
+
if opts.is_a?(ActiveRecord::Relation) || opts.is_a?(Array)
|
72
|
+
snapshot_children[assoc_name][:records] = opts
|
73
|
+
|
74
|
+
elsif opts.is_a?(Hash)
|
75
|
+
opts = opts.with_indifferent_access
|
76
|
+
|
77
|
+
records = opts[:records] || opts[:record]
|
78
|
+
|
79
|
+
if records
|
80
|
+
if records.respond_to?(:to_a)
|
81
|
+
records = records.to_a
|
82
|
+
else
|
83
|
+
records = [records]
|
84
|
+
end
|
85
|
+
|
86
|
+
snapshot_children[assoc_name][:records] = records
|
87
|
+
else
|
88
|
+
raise ArgumentError.new("Invalid `has_snapshot_children` definition. Must define a :records key for each child association.")
|
89
|
+
end
|
90
|
+
|
91
|
+
delete_method = opts[:delete_method]
|
92
|
+
|
93
|
+
if delete_method.present? && delete_method.to_s != "default"
|
94
|
+
if delete_method.respond_to?(:call)
|
95
|
+
snapshot_children[assoc_name][:delete_method] = delete_method
|
96
|
+
else
|
97
|
+
raise ArgumentError.new("Invalid `has_snapshot_children` definition. Invalid :delete_method argument. Must be a Lambda / Proc")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
else
|
102
|
+
raise ArgumentError.new("Invalid `has_snapshot_children` definition. Invalid :records argument. Must be a Hash or Array")
|
103
|
+
end
|
104
|
+
|
105
|
+
return snapshot_children
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module ActiveSnapshot
|
2
|
+
class Snapshot < ActiveRecord::Base
|
3
|
+
self.table_name = "snapshots"
|
4
|
+
|
5
|
+
if defined?(ProtectedAttributes)
|
6
|
+
attr_accessible :item_id, :item_type, :identifier, :user_id, :user_type
|
7
|
+
end
|
8
|
+
|
9
|
+
belongs_to :user, polymorphic: true
|
10
|
+
belongs_to :item, polymorphic: true
|
11
|
+
has_many :snapshot_items, class_name: 'ActiveSnapshot::SnapshotItem', dependent: :destroy
|
12
|
+
|
13
|
+
validates :item_id, presence: true
|
14
|
+
validates :item_type, presence: true
|
15
|
+
validates :identifier, presence: true, uniqueness: { scope: [:item_id, :item_type] }
|
16
|
+
validates :user_type, presence: true, if: :user_id
|
17
|
+
|
18
|
+
def metadata
|
19
|
+
@metadata ||= self[:metadata].with_indifferent_access
|
20
|
+
end
|
21
|
+
|
22
|
+
def metadata=(val)
|
23
|
+
@metadata = nil
|
24
|
+
self[:metadata] = val
|
25
|
+
end
|
26
|
+
|
27
|
+
def build_snapshot_item(instance, child_group_name: nil)
|
28
|
+
self.snapshot_items.new({
|
29
|
+
object: instance.attributes,
|
30
|
+
item_id: instance.id,
|
31
|
+
item_type: instance.class.name,
|
32
|
+
child_group_name: child_group_name,
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
36
|
+
def restore!
|
37
|
+
ActiveRecord::Base.transaction do
|
38
|
+
### Cache the child snapshots in a variable for re-use
|
39
|
+
cached_snapshot_items = snapshot_items.includes(:item)
|
40
|
+
|
41
|
+
existing_snapshot_children = item ? item.children_to_snapshot : []
|
42
|
+
|
43
|
+
if existing_snapshot_children.any?
|
44
|
+
children_to_keep = Set.new
|
45
|
+
|
46
|
+
cached_snapshot_items.each do |snapshot_item|
|
47
|
+
key = "#{snapshot_item.item_type} #{snapshot_item.item_id}"
|
48
|
+
|
49
|
+
children_to_keep << key
|
50
|
+
end
|
51
|
+
|
52
|
+
### Destroy or Detach Items not included in this Snapshot's Items
|
53
|
+
### We do this first in case you later decide to validate children in ItemSnapshot#restore_item! method
|
54
|
+
existing_snapshot_children.each do |child_group_name, h|
|
55
|
+
delete_method = h[:delete_method] || ->(child_record){ child_record.destroy! }
|
56
|
+
|
57
|
+
h[:records].each do |child_record|
|
58
|
+
child_record_id = child_record.send(child_record.class.send(:primary_key))
|
59
|
+
|
60
|
+
key = "#{child_record.class.name} #{child_record_id}"
|
61
|
+
|
62
|
+
if children_to_keep.exclude?(key)
|
63
|
+
delete_method.call(child_record)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
### Create or Update Items from Snapshot Items
|
70
|
+
cached_snapshot_items.each do |snapshot_item|
|
71
|
+
snapshot_item.restore_item!
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
return true
|
76
|
+
end
|
77
|
+
|
78
|
+
def fetch_reified_items
|
79
|
+
reified_children_hash = {}.with_indifferent_access
|
80
|
+
|
81
|
+
reified_parent = nil
|
82
|
+
|
83
|
+
snapshot_items.each do |si|
|
84
|
+
reified_item = si.item_type.constantize.new(si.object)
|
85
|
+
|
86
|
+
reified_item.readonly!
|
87
|
+
|
88
|
+
key = si.child_group_name
|
89
|
+
|
90
|
+
if key
|
91
|
+
reified_children_hash[key] ||= []
|
92
|
+
|
93
|
+
reified_children_hash[key] << reified_item
|
94
|
+
|
95
|
+
elsif [self.item_id, self.item_type] == [si.item_id, si.item_type]
|
96
|
+
reified_parent = reified_item
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
return [reified_parent, reified_children_hash]
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|