clone_kit 0.4.1 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.circleci/config.yml +49 -0
- data/.circleci/wait_for_mongo.sh +6 -0
- data/CHANGELOG.md +8 -0
- data/README.md +109 -18
- data/clone_kit.gemspec +1 -0
- data/lib/clone_kit/cloners/mongoid_ruleset_cloner.rb +33 -3
- data/lib/clone_kit/rules/allow_only_mongoid_fields.rb +4 -2
- data/lib/clone_kit/rules/except.rb +7 -4
- data/lib/clone_kit/rules/remap.rb +15 -0
- data/lib/clone_kit/rules/safe_remap.rb +4 -0
- data/lib/clone_kit/version.rb +1 -1
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c6c302894250fb88b745cb86ae6d329c24c8f601
|
4
|
+
data.tar.gz: 691b3d3bbcff0db46980266e8818a644da0f8626
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6442ecee0507caa2d84382a296272f955340aac5e16837183e0c9f4adf8513b535afbf5384a87fe905bcc1c0c467973fce81f5ac5ae207c185805d696a91e5ee
|
7
|
+
data.tar.gz: b38a06f98cf9b42a0005a7cc6b5a0cbd3d0ec7113b2d207e2508acfa9cb64786b2b148abfdb4392b1325090179402f22d59621be1e213e7b58d6ae19012c2049
|
@@ -0,0 +1,49 @@
|
|
1
|
+
version: 2
|
2
|
+
|
3
|
+
jobs:
|
4
|
+
build:
|
5
|
+
docker:
|
6
|
+
- image: kapost/ruby:2.3.1-node-6.11.5
|
7
|
+
environment:
|
8
|
+
DATABASE_URL: "postgres://circleci@localhost/clone_kit_test"
|
9
|
+
- image: circleci/postgres:9.6-alpine
|
10
|
+
environment:
|
11
|
+
POSTGRES_USER: circleci
|
12
|
+
POSTGRES_DB: clone_kit_test
|
13
|
+
- image: kapost/mongo:3.2
|
14
|
+
|
15
|
+
working_directory: ~/clone_kit
|
16
|
+
|
17
|
+
steps:
|
18
|
+
- checkout
|
19
|
+
- run:
|
20
|
+
name: Install deps
|
21
|
+
command: |
|
22
|
+
apt-get update
|
23
|
+
apt-get -y install netcat
|
24
|
+
- restore_cache:
|
25
|
+
keys:
|
26
|
+
- bundle-v1-{{ arch }}-{{ .Branch }}-{{ checksum "clone_kit.gemspec" }}
|
27
|
+
- bundle-v1-{{ arch }}-{{ .Branch }}
|
28
|
+
- bundle-v1-{{ arch }}
|
29
|
+
- run:
|
30
|
+
name: Bundle Install
|
31
|
+
command: |
|
32
|
+
bundle check --path=vendor/bundle || bundle install --path=vendor/bundle --jobs=4 --retry=3
|
33
|
+
- save_cache:
|
34
|
+
key: bundle-v1-{{ arch }}-{{ .Branch }}-{{ checksum "clone_kit.gemspec" }}
|
35
|
+
paths:
|
36
|
+
- vendor/bundle
|
37
|
+
- run:
|
38
|
+
name: Wait for mongo
|
39
|
+
command: ./.circleci/wait_for_mongo.sh
|
40
|
+
- run:
|
41
|
+
name: Run Specs
|
42
|
+
command: |
|
43
|
+
bundle exec rspec --profile 10 \
|
44
|
+
--format RspecJunitFormatter \
|
45
|
+
--out test_results/rspec.xml \
|
46
|
+
--format progress \
|
47
|
+
$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
|
48
|
+
- store_test_results:
|
49
|
+
path: test_results
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
3
|
+
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
4
|
+
|
5
|
+
## [0.4.2](https://github.com/kapost/clone_kit/compare/v0.4.1...v0.4.2) - 2018-11-14
|
6
|
+
### Added
|
7
|
+
- CHANGELOG
|
8
|
+
- Validation errors on embedded models when cloning
|
data/README.md
CHANGED
@@ -26,31 +26,118 @@ You can specify the dependency order of cloning, the scope of the operation, and
|
|
26
26
|
|
27
27
|
```ruby
|
28
28
|
CloneKit::Specification.new(BlogPost) do |spec|
|
29
|
-
spec.dependencies = %w(Account BlogType) # Helps derive the cloning order
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
29
|
+
spec.dependencies = %w(Account BlogType) # Helps derive the cloning order
|
30
|
+
spec.emitter = TenantEmitter.new(BlogPost) # The scope of the operation for this collection
|
31
|
+
spec.cloner = CloneKit::Cloners::MongoidRulesetCloner.new( # The cloning behavior
|
32
|
+
BlogPost,
|
33
|
+
rules: [
|
34
|
+
ReTenantRule.new,
|
35
|
+
CloneKit::Rules::Remap.new("BlogPost", "Account" => "account_id", "BlogType" => "blog_type_id")
|
36
|
+
]
|
37
|
+
)
|
38
|
+
spec.after_operation do |operation|
|
39
|
+
...
|
40
|
+
end
|
38
41
|
end
|
39
42
|
```
|
40
43
|
|
41
44
|
## Writing an Emitter
|
42
45
|
|
43
|
-
|
46
|
+
By default, CloneKit specifications utilize an empty emitter, making all clones no-ops. Emitters are expected to make db calls using logic defined in the emitter.
|
44
47
|
|
45
|
-
|
48
|
+
#### Emitter rules
|
49
|
+
- Emitters must respond to `#emit_all` and `#scope`.
|
50
|
+
- `emit_all` must return an object that responds to `#pluck`.
|
46
51
|
|
47
|
-
|
52
|
+
```ruby
|
53
|
+
CloneKit::ActiveRecordSpecification.new(BlogPost) do |spec|
|
54
|
+
...
|
55
|
+
spec.emitter = ActiveRecordEmitter.new(BlogPost)
|
56
|
+
...
|
57
|
+
end
|
58
|
+
|
59
|
+
class ActiveRecordEmitter
|
60
|
+
def initialize(klass)
|
61
|
+
self.klass = klass
|
62
|
+
end
|
63
|
+
|
64
|
+
def scope(*)
|
65
|
+
klass.all # add any scope restrictions here
|
66
|
+
end
|
67
|
+
|
68
|
+
def emit_all # the method that will be used to pluck the record ids
|
69
|
+
scope
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
attr_accessor :klass
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
## Custom Cloners
|
79
|
+
|
80
|
+
Cloners are the classes that determine what model class is cloned and how. There are several built-in cloners that can be extended. See `lib/clone_kit/cloners` for a list.
|
81
|
+
|
82
|
+
Custom cloners will need to define:
|
83
|
+
|
84
|
+
1. The Mongoid or ActiveRecord model class, which will be used to make db calls
|
85
|
+
2. Rules, which are executed in the defined order and determine how the ids are mapped from source to destination records. See more in next section.
|
86
|
+
3. Merge fields, which allow two records to be merged into one provided all listed fields are equal.
|
48
87
|
|
49
|
-
|
88
|
+
Optionally, if you are merging records you will probably want to override the `compare` and `merge` methods with custom logic, though basic logic comes for free.
|
50
89
|
|
51
|
-
|
90
|
+
```ruby
|
91
|
+
CloneKit::ActiveRecordSpecification.new(self) do |spec|
|
92
|
+
...
|
93
|
+
spec.cloner = BlogPostCloner.new
|
94
|
+
...
|
95
|
+
end
|
96
|
+
|
97
|
+
class BlogPostCloner < ActiveRecordRulesetCloner
|
98
|
+
OMIT_ATTRIBUTES = [:created_at, :updated_at]
|
99
|
+
|
100
|
+
def initialize
|
101
|
+
super(
|
102
|
+
BlogPost, # model class
|
103
|
+
rules: [ # rules
|
104
|
+
CloneKit::Rules::Except.new(*OMIT_ATTRIBUTES),
|
105
|
+
CloneKit::Rules::Remap.new(BlogPost)
|
106
|
+
],
|
107
|
+
merge_fields: []) # merge fields
|
108
|
+
end
|
109
|
+
|
110
|
+
def compare(first, second)
|
111
|
+
# returns a boolean to determine if two records are mergeable
|
112
|
+
end
|
113
|
+
|
114
|
+
def merge(records)
|
115
|
+
# returns a single record that is the merged result
|
116
|
+
# of all argument `records`,
|
117
|
+
# e.g. [{ a: 1, b: 1 }, { a: 2, b: 1}] => { a: 2, b: 1 }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
```
|
122
|
+
|
123
|
+
## Writing a Cloner rule
|
52
124
|
|
53
|
-
|
125
|
+
Rules respond to a single `#fix` method. `#fix` mutates a record's attributes, allowing the same `attributes` object to be passed down a pipeline of rules.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
# given the following rules
|
129
|
+
rules: [
|
130
|
+
CloneKit::Rules::Except.new(:title),
|
131
|
+
CloneKit::Rules::Remap.new(BlogPost, "Author" => "author_id" )
|
132
|
+
]
|
133
|
+
|
134
|
+
# a blog post's attributes will be changed
|
135
|
+
{ title: "Title", content: "Content", author_id: 5 }
|
136
|
+
{ content: "Content", author_id: 5 } # CloneKit::Rules::Except
|
137
|
+
{ content: "Content", author_id: 6 } # CloneKit::Rules::Remap
|
138
|
+
```
|
139
|
+
|
140
|
+
See `lib/clone_kit/rules` for examples with documentation.
|
54
141
|
|
55
142
|
## Installation
|
56
143
|
|
@@ -62,11 +149,15 @@ gem 'clone_kit'
|
|
62
149
|
|
63
150
|
And then execute:
|
64
151
|
|
65
|
-
|
152
|
+
```bash
|
153
|
+
$ bundle
|
154
|
+
```
|
66
155
|
|
67
156
|
Or install it yourself as:
|
68
157
|
|
69
|
-
|
158
|
+
```bash
|
159
|
+
$ gem install clone_kit
|
160
|
+
```
|
70
161
|
|
71
162
|
## Development
|
72
163
|
|
@@ -76,4 +167,4 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
76
167
|
|
77
168
|
## Contributing
|
78
169
|
|
79
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
170
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/kapost/clone_kit.
|
data/clone_kit.gemspec
CHANGED
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_development_dependency "pry-byebug"
|
37
37
|
spec.add_development_dependency "rake", "~> 11.0"
|
38
38
|
spec.add_development_dependency "rspec-collection_matchers"
|
39
|
+
spec.add_development_dependency "rspec_junit_formatter", "~> 0.3"
|
39
40
|
spec.add_development_dependency "rspec-rails", "~> 3.4"
|
40
41
|
spec.add_development_dependency "rubocop"
|
41
42
|
spec.add_development_dependency "simplecov", "~> 0.12.0"
|
@@ -107,12 +107,42 @@ module CloneKit
|
|
107
107
|
if model_that_we_wont_save.valid?
|
108
108
|
model_klass.collection.insert(attributes)
|
109
109
|
else
|
110
|
-
|
111
|
-
id = attributes["_id"]
|
112
|
-
current_operation.error("#{model_klass} #{id} failed model validation and was not cloned: #{details}")
|
110
|
+
report_errors(attributes, model_that_we_wont_save)
|
113
111
|
end
|
114
112
|
end
|
115
113
|
|
114
|
+
def report_errors(attributes, model)
|
115
|
+
model_error = model.errors.full_messages.to_sentence
|
116
|
+
embedded_errors = collect_embedded_errors(model)
|
117
|
+
id = attributes["_id"]
|
118
|
+
|
119
|
+
current_operation.error("#{model_klass} #{id} failed model validation and was not cloned: #{model_error}")
|
120
|
+
|
121
|
+
embedded_errors.each do |e|
|
122
|
+
current_operation.error("[#{model_klass} #{id}]: #{e}")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def collect_embedded_errors(model)
|
127
|
+
embedded_documents(model).each_with_object([]) do |doc, accum|
|
128
|
+
next if doc.blank? || doc.valid?
|
129
|
+
|
130
|
+
klass = doc.class
|
131
|
+
id = doc._id.to_s
|
132
|
+
error = doc.errors.full_messages.to_sentence
|
133
|
+
|
134
|
+
accum << "Embedded #{klass} #{id} failed model validation: #{error}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def embedded_documents(model)
|
139
|
+
model.associations
|
140
|
+
.select { |_key, assoc| assoc.embedded? }
|
141
|
+
.keys
|
142
|
+
.map { |field| model.send(field) }
|
143
|
+
.flatten
|
144
|
+
end
|
145
|
+
|
116
146
|
def each_existing_record(ids)
|
117
147
|
ids.each do |id|
|
118
148
|
record = model_klass.collection.find(_id: id).one
|
@@ -2,8 +2,10 @@
|
|
2
2
|
|
3
3
|
module CloneKit
|
4
4
|
module Rules
|
5
|
-
#
|
6
|
-
#
|
5
|
+
#
|
6
|
+
# The purpose of this rule is to only include attributes that are
|
7
|
+
# presently defined on the model (and its embedded models)
|
8
|
+
|
7
9
|
class AllowOnlyMongoidFields < CloneKit::Rule
|
8
10
|
def initialize(model_klass)
|
9
11
|
self.model_klass = model_klass
|
@@ -2,20 +2,23 @@
|
|
2
2
|
|
3
3
|
module CloneKit
|
4
4
|
module Rules
|
5
|
+
#
|
6
|
+
# Removes attributes defined by an array of keys
|
7
|
+
|
5
8
|
class Except < CloneKit::Rule
|
6
|
-
def initialize(*
|
7
|
-
self.
|
9
|
+
def initialize(*excepted_attributes)
|
10
|
+
self.excepted_attributes = excepted_attributes
|
8
11
|
end
|
9
12
|
|
10
13
|
def fix(_old_id, attributes)
|
11
|
-
|
14
|
+
excepted_attributes.each do |key|
|
12
15
|
attributes.delete(key)
|
13
16
|
end
|
14
17
|
end
|
15
18
|
|
16
19
|
private
|
17
20
|
|
18
|
-
attr_accessor :
|
21
|
+
attr_accessor :excepted_attributes
|
19
22
|
end
|
20
23
|
end
|
21
24
|
end
|
@@ -2,6 +2,21 @@
|
|
2
2
|
|
3
3
|
module CloneKit
|
4
4
|
module Rules
|
5
|
+
#
|
6
|
+
# Utilizes the SharedIdMap stored in Redis to remap original
|
7
|
+
# ids to their new cloned values.
|
8
|
+
#
|
9
|
+
# Given an original blog post being cloned:
|
10
|
+
# { title: "Title", author_id: 5 }
|
11
|
+
# And a blog post rule:
|
12
|
+
# Remap.new(BlogPost, "Author" => "author_id")
|
13
|
+
# And an author record that was cloned from => to:
|
14
|
+
# { id: 5, name: "Pat" } => { id: 6, name: "Pat" }
|
15
|
+
# The cloned blog post will show the remapped author id:
|
16
|
+
# { title: "Title", author_id: 6 }
|
17
|
+
#
|
18
|
+
# When a remapped id is missing, an error is added to the operation.
|
19
|
+
|
5
20
|
class Remap < CloneKit::Rule
|
6
21
|
def initialize(model_name, remap_hash = {}, id_generator: nil)
|
7
22
|
super(id_generator: id_generator)
|
@@ -4,6 +4,10 @@ require "clone_kit/rules/remap"
|
|
4
4
|
|
5
5
|
module CloneKit
|
6
6
|
module Rules
|
7
|
+
#
|
8
|
+
# Operates like Remap, but returns a default value instead of `nil`.
|
9
|
+
# When the default is used, the event outlet receives a #warn message.
|
10
|
+
|
7
11
|
class SafeRemap < Remap
|
8
12
|
def initialize(model_name, remap_hash = {}, safe_value = nil, id_generator: nil)
|
9
13
|
super(model_name, remap_hash, id_generator: id_generator)
|
data/lib/clone_kit/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clone_kit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brandon Croft
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -192,6 +192,20 @@ dependencies:
|
|
192
192
|
- - ">="
|
193
193
|
- !ruby/object:Gem::Version
|
194
194
|
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: rspec_junit_formatter
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - "~>"
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0.3'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - "~>"
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0.3'
|
195
209
|
- !ruby/object:Gem::Dependency
|
196
210
|
name: rspec-rails
|
197
211
|
requirement: !ruby/object:Gem::Requirement
|
@@ -241,11 +255,14 @@ executables: []
|
|
241
255
|
extensions: []
|
242
256
|
extra_rdoc_files: []
|
243
257
|
files:
|
258
|
+
- ".circleci/config.yml"
|
259
|
+
- ".circleci/wait_for_mongo.sh"
|
244
260
|
- ".gitignore"
|
245
261
|
- ".rspec"
|
246
262
|
- ".rubocop.kapost.yml"
|
247
263
|
- ".rubocop.yml"
|
248
264
|
- ".ruby-version"
|
265
|
+
- CHANGELOG.md
|
249
266
|
- Gemfile
|
250
267
|
- README.md
|
251
268
|
- Rakefile
|
@@ -297,7 +314,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
297
314
|
version: '0'
|
298
315
|
requirements: []
|
299
316
|
rubyforge_project:
|
300
|
-
rubygems_version: 2.
|
317
|
+
rubygems_version: 2.5.2.2
|
301
318
|
signing_key:
|
302
319
|
specification_version: 4
|
303
320
|
summary: A toolkit to assist in complex cloning operations
|