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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 44c1a6651a51d141af62a548478d3434d1ade17975090d3cd0a3c574c774fa0a
4
- data.tar.gz: 8ac5a6548b0612eca38f70cc915c65f6fcbb3f74fd52e4ce1fe69fc244fe0de7
2
+ SHA1:
3
+ metadata.gz: c6c302894250fb88b745cb86ae6d329c24c8f601
4
+ data.tar.gz: 691b3d3bbcff0db46980266e8818a644da0f8626
5
5
  SHA512:
6
- metadata.gz: 3b0869c41f2031c89c9703d10a32f18e53bde40781d17ee90316706419e3826164bd475888a4906365c4f13feb5815a32b95b6f207adaa7a85d7e85526c9c04d
7
- data.tar.gz: d534b0614f37999c26607c3e4a3b5c6e015e69442d609a7c6af690e7904237cdccf1a30eb4875ce8dd8db441ae24daa14b1048a340158010ac5fb69419823a2b
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
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+
3
+ until nc -z localhost 27017
4
+ do
5
+ sleep 1
6
+ done
@@ -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
- 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
- )
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
- You have to write some emitters for your app. By default, CloneKit specifications utilize and empty emitter, making all clones no-operations.
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
- TODO
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
- ## Writing a Cloner
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
- TODO
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
- ## Extending the built-in Cloners
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
- TODO
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
- $ bundle
152
+ ```bash
153
+ $ bundle
154
+ ```
66
155
 
67
156
  Or install it yourself as:
68
157
 
69
- $ gem install clone_kit
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/[USERNAME]/clone_kit.
170
+ Bug reports and pull requests are welcome on GitHub at https://github.com/kapost/clone_kit.
@@ -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
- details = model_that_we_wont_save.errors.full_messages.to_sentence
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
- # The purpose of this rule is to only include attributes that are presently defined on the model
6
- # (and its embedded models)
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(*attributes)
7
- self.except_attributes = attributes
9
+ def initialize(*excepted_attributes)
10
+ self.excepted_attributes = excepted_attributes
8
11
  end
9
12
 
10
13
  def fix(_old_id, attributes)
11
- except_attributes.each do |key|
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 :except_attributes
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CloneKit
4
- VERSION = "0.4.1"
4
+ VERSION = "0.4.2"
5
5
  end
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.1
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-04-27 00:00:00.000000000 Z
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.7.6
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