DUI 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/.rvmrc +55 -0
- data/DUI.gemspec +25 -0
- data/Gemfile +13 -0
- data/Guardfile +14 -0
- data/Rakefile +1 -0
- data/lib/DUI/matcher.rb +49 -0
- data/lib/DUI/version.rb +3 -0
- data/lib/DUI.rb +6 -0
- data/spec/DUI/matcher_spec.rb +256 -0
- data/spec/spec_helper.rb +11 -0
- data/test/test_helper.rb +2 -0
- metadata +108 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# This is an RVM Project .rvmrc file, used to automatically load the ruby
|
4
|
+
# development environment upon cd'ing into the directory
|
5
|
+
|
6
|
+
# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
|
7
|
+
environment_id="ruby-1.8.7-p352@DUI"
|
8
|
+
|
9
|
+
#
|
10
|
+
# Uncomment following line if you want options to be set only for given project.
|
11
|
+
#
|
12
|
+
# PROJECT_JRUBY_OPTS=( --1.9 )
|
13
|
+
|
14
|
+
#
|
15
|
+
# First we attempt to load the desired environment directly from the environment
|
16
|
+
# file. This is very fast and efficient compared to running through the entire
|
17
|
+
# CLI and selector. If you want feedback on which environment was used then
|
18
|
+
# insert the word 'use' after --create as this triggers verbose mode.
|
19
|
+
#
|
20
|
+
if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
|
21
|
+
&& -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
|
22
|
+
then
|
23
|
+
\. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
|
24
|
+
|
25
|
+
if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
|
26
|
+
then
|
27
|
+
. "${rvm_path:-$HOME/.rvm}/hooks/after_use"
|
28
|
+
fi
|
29
|
+
else
|
30
|
+
# If the environment file has not yet been created, use the RVM CLI to select.
|
31
|
+
if ! rvm --create "$environment_id"
|
32
|
+
then
|
33
|
+
echo "Failed to create RVM environment '${environment_id}'."
|
34
|
+
return 1
|
35
|
+
fi
|
36
|
+
fi
|
37
|
+
|
38
|
+
#
|
39
|
+
# If you use an RVM gemset file to install a list of gems (*.gems), you can have
|
40
|
+
# it be automatically loaded. Uncomment the following and adjust the filename if
|
41
|
+
# necessary.
|
42
|
+
#
|
43
|
+
# filename=".gems"
|
44
|
+
# if [[ -s "$filename" ]]
|
45
|
+
# then
|
46
|
+
# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
|
47
|
+
# fi
|
48
|
+
|
49
|
+
# If you use bundler, this might be useful to you:
|
50
|
+
# if command -v bundle && [[ -s Gemfile ]]
|
51
|
+
# then
|
52
|
+
# bundle install
|
53
|
+
# fi
|
54
|
+
|
55
|
+
|
data/DUI.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "DUI/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "DUI"
|
7
|
+
s.version = DUI::VERSION
|
8
|
+
s.authors = ["Darren Cauthon"]
|
9
|
+
s.email = ["darren@cauthon.com"]
|
10
|
+
s.homepage = "http://www.github.com/darrencauthon/DUI"
|
11
|
+
s.summary = %q{Easier syncing with Delete - Update - Insert}
|
12
|
+
s.description = %q{This gem provides a small API for comparing two datasets,
|
13
|
+
for determining what records should be deleted, updated, or inserted.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "DUI"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
# specify any dependencies here; for example:
|
23
|
+
s.add_development_dependency "minitest"
|
24
|
+
s.add_runtime_dependency "hashie"
|
25
|
+
end
|
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in DUI.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'minitest'
|
7
|
+
gem 'hashie'
|
8
|
+
|
9
|
+
group :development do
|
10
|
+
gem 'guard'
|
11
|
+
gem 'guard-minitest', :git => 'git://github.com/aspiers/guard-minitest.git'
|
12
|
+
gem 'ruby_gntp'
|
13
|
+
end
|
data/Guardfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
guard 'minitest' do
|
4
|
+
watch(%r|^test/test_(.*)\.rb|)
|
5
|
+
watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
|
6
|
+
watch(%r|^test/test_helper\.rb|) { "test" }
|
7
|
+
watch(%r|^lib/(.*)\.rb|) { |m| "test/test_#{m[1]}.rb" }
|
8
|
+
|
9
|
+
watch(%r{^spec/.+_spec\.rb$})
|
10
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
11
|
+
watch(%r{^lib/DUI/(.+)\.rb$}) { |m| "spec/DUI/#{m[1]}_spec.rb" }
|
12
|
+
watch(%r{^spec/models/.+\.rb$}) { ["spec/models", "spec/acceptance"] }
|
13
|
+
watch('spec/spec_helper.rb') { "spec" }
|
14
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/DUI/matcher.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module DUI
|
2
|
+
|
3
|
+
class Matcher
|
4
|
+
|
5
|
+
def initialize(&compare_method)
|
6
|
+
@compare_method = compare_method || Proc.new {|c, n| c.id == n.id }
|
7
|
+
end
|
8
|
+
|
9
|
+
def get_results(current_data, new_data)
|
10
|
+
results = get_the_results_of_the_delete_and_update_process(current_data, new_data)
|
11
|
+
results.records_to_insert = get_records_to_insert(results, new_data)
|
12
|
+
results
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def get_the_results_of_the_delete_and_update_process(current_data, new_data)
|
18
|
+
results = an_object_with(:records_to_delete => [], :records_to_update => [])
|
19
|
+
all_current_data_with_possible_matches_in_new_data(current_data, new_data).each do |match|
|
20
|
+
if match.current_not_found_in_new
|
21
|
+
results.records_to_delete << match.current
|
22
|
+
else
|
23
|
+
results.records_to_update << match
|
24
|
+
end
|
25
|
+
end
|
26
|
+
results
|
27
|
+
end
|
28
|
+
|
29
|
+
def all_current_data_with_possible_matches_in_new_data(current_data, new_data)
|
30
|
+
current_data.map do |c|
|
31
|
+
an_object_with({:current => c}) do |result|
|
32
|
+
result.new = new_data.select {|n| @compare_method.call(c, n) }.first
|
33
|
+
result.current_not_found_in_new = result.new.nil?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_records_to_insert(results, new_data)
|
39
|
+
current_records = results.records_to_update.map{|u| u.current}
|
40
|
+
new_data.select {|n| current_records.select {|c| @compare_method.call(c, n) }.count == 0 }
|
41
|
+
end
|
42
|
+
|
43
|
+
def an_object_with(hash)
|
44
|
+
the_object = Hashie::Mash.new(hash)
|
45
|
+
yield the_object if block_given?
|
46
|
+
the_object
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/DUI/version.rb
ADDED
data/lib/DUI.rb
ADDED
@@ -0,0 +1,256 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "Matcher" do
|
4
|
+
before do
|
5
|
+
@matcher = DUI::Matcher.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "when there is no existing data" do
|
9
|
+
before do
|
10
|
+
@current_data = []
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "and it is given no data" do
|
14
|
+
before do
|
15
|
+
@new_data = []
|
16
|
+
@results = @matcher.get_results @current_data, @new_data
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should have no records to delete" do
|
20
|
+
assert_equal 0, @results.records_to_delete.count
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should have no records to update" do
|
24
|
+
assert_equal 0, @results.records_to_update.count
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should have no records to insert" do
|
28
|
+
assert_equal 0, @results.records_to_insert.count
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "and it is given one new record" do
|
33
|
+
before do
|
34
|
+
@new_data = [new_record_with_id(1)]
|
35
|
+
@results = @matcher.get_results @current_data, @new_data
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should have no records to delete" do
|
39
|
+
assert_equal 0, @results.records_to_delete.count
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should have no records to update" do
|
43
|
+
assert_equal 0, @results.records_to_update.count
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should have one records to insert" do
|
47
|
+
assert_equal 1, @results.records_to_insert.count
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should return the new record as a record to insert" do
|
51
|
+
@new_data.each do |x|
|
52
|
+
assert_equal true, @results.records_to_insert.include?(x)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "when there is one existing record" do
|
59
|
+
before do
|
60
|
+
@current_data = [new_record_with_id(3)]
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "and it is given no new data" do
|
64
|
+
before do
|
65
|
+
@new_data = []
|
66
|
+
@results = @matcher.get_results @current_data, @new_data
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should have one records to delete" do
|
70
|
+
assert_equal 1, @results.records_to_delete.count
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should return the current record as a record to delete" do
|
74
|
+
@current_data.each do |x|
|
75
|
+
assert_equal true, @results.records_to_delete.include?(x)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should have no records to update" do
|
80
|
+
assert_equal 0, @results.records_to_update.count
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should have no records to insert" do
|
84
|
+
assert_equal 0, @results.records_to_insert.count
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "and it is given the same record" do
|
89
|
+
before do
|
90
|
+
@new_data = [new_record_with_id(3)]
|
91
|
+
@results = @matcher.get_results @current_data, @new_data
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should have no records to delete" do
|
95
|
+
assert_equal 0, @results.records_to_delete.count
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should have one records to update" do
|
99
|
+
assert_equal 1, @results.records_to_update.count
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should return the existing record as a record to update" do
|
103
|
+
assert_equal 1, @results.records_to_update.select{|x| x.current.id == 3}.count
|
104
|
+
assert_equal 1, @results.records_to_update.select{|x| x.new.id == 3}.count
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should have no records to insert" do
|
108
|
+
assert_equal 0, @results.records_to_insert.count
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "and it is given a different record" do
|
113
|
+
before do
|
114
|
+
@new_data = [new_record_with_id(5)]
|
115
|
+
@results = @matcher.get_results @current_data, @new_data
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should have no records to delete" do
|
119
|
+
assert_equal 1, @results.records_to_delete.count
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should return the existing record as a record to delete" do
|
123
|
+
assert_equal 1, @results.records_to_delete.select{|x| x.id == 3}.count
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should have no records to update" do
|
127
|
+
assert_equal 0, @results.records_to_update.count
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should have one record to insert" do
|
131
|
+
assert_equal 1, @results.records_to_insert.count
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should return the new record as a record to insert" do
|
135
|
+
assert_equal 1, @results.records_to_insert.select{|x| x.id == 5}.count
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "and it is given the existing record and the same record" do
|
140
|
+
before do
|
141
|
+
@new_data = [new_record_with_id(3), new_record_with_id(5)]
|
142
|
+
@results = @matcher.get_results @current_data, @new_data
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should have no records to delete" do
|
146
|
+
assert_equal 0, @results.records_to_delete.count
|
147
|
+
end
|
148
|
+
|
149
|
+
it "should have one records to update" do
|
150
|
+
assert_equal 1, @results.records_to_update.count
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should return the existing record as a record to update" do
|
154
|
+
assert_equal 1, @results.records_to_update.select{|x| x.current.id == 3}.count
|
155
|
+
assert_equal 1, @results.records_to_update.select{|x| x.new.id == 3}.count
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should have one records to insert" do
|
159
|
+
assert_equal 1, @results.records_to_insert.count
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should return the new records as a record to insert" do
|
163
|
+
assert_equal 1, @results.records_to_insert.select{|x| x.id == 5}.count
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
describe "when there are two existing records" do
|
168
|
+
before do
|
169
|
+
@current_data = [new_record_with_id(7), new_record_with_id(8)]
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "and no new data is passed" do
|
173
|
+
before do
|
174
|
+
@new_data = []
|
175
|
+
@results = @matcher.get_results @current_data, @new_data
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should return 2 records to delete" do
|
179
|
+
assert_equal 2, @results.records_to_delete.count
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should return 0 records to update" do
|
183
|
+
assert_equal 0, @results.records_to_update.count
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should return 0 records to insert" do
|
187
|
+
assert_equal 0, @results.records_to_insert.count
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe "and both existing records are passed" do
|
192
|
+
before do
|
193
|
+
@new_data = [new_record_with_id(8), new_record_with_id(7)]
|
194
|
+
@results = @matcher.get_results @current_data, @new_data
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should return 0 records to delete" do
|
198
|
+
assert_equal 0, @results.records_to_delete.count
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should return 2 records to update" do
|
202
|
+
assert_equal 2, @results.records_to_update.count
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should return both records as ready for update" do
|
206
|
+
assert_equal 1, @results.records_to_update.select{|x|x.current.id == 8}.count
|
207
|
+
assert_equal 1, @results.records_to_update.select{|x|x.current.id == 7}.count
|
208
|
+
assert_equal 1, @results.records_to_update.select{|x|x.new.id == 8}.count
|
209
|
+
assert_equal 1, @results.records_to_update.select{|x|x.new.id == 7}.count
|
210
|
+
end
|
211
|
+
|
212
|
+
it "should return no records for insertion" do
|
213
|
+
assert_equal 0, @results.records_to_insert.count
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe "Matcher, but with email instead of id" do
|
221
|
+
before do
|
222
|
+
@matcher = DUI::Matcher.new {|c, n| c.email == n.email}
|
223
|
+
end
|
224
|
+
|
225
|
+
describe "when there are two existing records" do
|
226
|
+
before do
|
227
|
+
@current_data = [new_record_with_id_and_email(7, "one@test.com"), new_record_with_id_and_email(8, "two@test.com")]
|
228
|
+
end
|
229
|
+
|
230
|
+
describe "and both existing records are passed" do
|
231
|
+
before do
|
232
|
+
@new_data = [new_record_with_id_and_email(88, "two@test.com"), new_record_with_id_and_email(77, "one@test.com")]
|
233
|
+
@results = @matcher.get_results @current_data, @new_data
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should return 0 records to delete" do
|
237
|
+
assert_equal 0, @results.records_to_delete.count
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should return 2 records to update" do
|
241
|
+
assert_equal 2, @results.records_to_update.count
|
242
|
+
end
|
243
|
+
|
244
|
+
it "should return both records as ready for update" do
|
245
|
+
assert_equal 1, @results.records_to_update.select{|x|x.current.email == "one@test.com"}.count
|
246
|
+
assert_equal 1, @results.records_to_update.select{|x|x.current.email == "two@test.com"}.count
|
247
|
+
assert_equal 1, @results.records_to_update.select{|x|x.new.email == "one@test.com"}.count
|
248
|
+
assert_equal 1, @results.records_to_update.select{|x|x.new.email == "two@test.com"}.count
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should return no records for insertion" do
|
252
|
+
assert_equal 0, @results.records_to_insert.count
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: DUI
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 59
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
- 0
|
10
|
+
version: 0.9.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Darren Cauthon
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-03-01 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: minitest
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: hashie
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
description: |-
|
49
|
+
This gem provides a small API for comparing two datasets,
|
50
|
+
for determining what records should be deleted, updated, or inserted.
|
51
|
+
email:
|
52
|
+
- darren@cauthon.com
|
53
|
+
executables: []
|
54
|
+
|
55
|
+
extensions: []
|
56
|
+
|
57
|
+
extra_rdoc_files: []
|
58
|
+
|
59
|
+
files:
|
60
|
+
- .gitignore
|
61
|
+
- .rvmrc
|
62
|
+
- DUI.gemspec
|
63
|
+
- Gemfile
|
64
|
+
- Guardfile
|
65
|
+
- Rakefile
|
66
|
+
- lib/DUI.rb
|
67
|
+
- lib/DUI/matcher.rb
|
68
|
+
- lib/DUI/version.rb
|
69
|
+
- spec/DUI/matcher_spec.rb
|
70
|
+
- spec/spec_helper.rb
|
71
|
+
- test/test_helper.rb
|
72
|
+
homepage: http://www.github.com/darrencauthon/DUI
|
73
|
+
licenses: []
|
74
|
+
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
hash: 3
|
95
|
+
segments:
|
96
|
+
- 0
|
97
|
+
version: "0"
|
98
|
+
requirements: []
|
99
|
+
|
100
|
+
rubyforge_project: DUI
|
101
|
+
rubygems_version: 1.8.10
|
102
|
+
signing_key:
|
103
|
+
specification_version: 3
|
104
|
+
summary: Easier syncing with Delete - Update - Insert
|
105
|
+
test_files:
|
106
|
+
- spec/DUI/matcher_spec.rb
|
107
|
+
- spec/spec_helper.rb
|
108
|
+
- test/test_helper.rb
|