happymapper-differ 0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8762cf0e3b779fa85dad307850b73fff48980c87
4
+ data.tar.gz: 5d77b1d3cae7582cf841387ec3c98473a46866ff
5
+ SHA512:
6
+ metadata.gz: e034ea0d0d7455438b6afe0cc6c60d870e78b85fe7afcc9c22a671a8af8e84546e222e64a51cad1d3223c1d3808250f52f78e48ea97dd8031923d69734dbfe1e
7
+ data.tar.gz: a1d3704b0af7d435e5625404f1ac4c847af7a72463b2304815e55fb9c6fd1fd1a1c496c34c67bd63abf112c51a45f26441e3219381e9db2a6b36c067e53cc612
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in goalkeeper.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,31 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ happymapper_differ (0.1)
5
+ nokogiri-happymapper (~> 0.5.9)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ byebug (3.5.1)
11
+ columnize (~> 0.8)
12
+ debugger-linecache (~> 1.2)
13
+ slop (~> 3.6)
14
+ columnize (0.9.0)
15
+ debugger-linecache (1.2.0)
16
+ mini_portile (0.6.2)
17
+ nokogiri (1.6.6.2)
18
+ mini_portile (~> 0.6.0)
19
+ nokogiri-happymapper (0.5.9)
20
+ nokogiri (~> 1.5)
21
+ rake (10.4.2)
22
+ slop (3.6.0)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ bundler (~> 1.7)
29
+ byebug (~> 3.5)
30
+ happymapper_differ!
31
+ rake (~> 10.0)
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 John Weir john.weir@pharos-ei.com, Pharos Enterprise Intelligence LLC
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,89 @@
1
+ # HappyMapper::Differ
2
+
3
+ In the unlikely scenario you use [HappyMapper](
4
+ https://github.com/dam5s/happymapper ) and you need to find differences between
5
+ two documents, this could be a solution.
6
+
7
+ Ok, your probably won't use or need this. But since I enjoy writing to myself here is how to use it.
8
+
9
+ __Note: this library is wildly memory inefficient__
10
+
11
+ ````ruby
12
+ def left
13
+ %Q{
14
+ <person name="James">
15
+ <item><name>Car</name></item>
16
+ <item><name>Box</name></item>
17
+ </person>
18
+ }
19
+ end
20
+
21
+ def right
22
+ %Q{
23
+ <person name="Carl">
24
+ <item><name>Car</name></item>
25
+ <item><name>Red Box</name></item>
26
+ <item><name>Cup</name></item>
27
+ </person>
28
+ }
29
+ end
30
+
31
+ require 'happymapper_differ'
32
+
33
+ class Person
34
+ include HappyMapper
35
+
36
+ tag 'person'
37
+ attribute :name, String
38
+ has_many :items, "Item"
39
+ end
40
+
41
+ class Item
42
+ include HappyMapper
43
+ has_one :name, String
44
+ end
45
+
46
+ result = HappyMapper::Differ.new(Person.parse(left), Person.parse(right)).diff
47
+
48
+ result.changed?
49
+ #=> true
50
+
51
+ result.changes
52
+ #=> {"name"=>"Carl", "items"=>[#<Item:0x007ffd2d8d5a68 @name="Red Box">, #<Item:0x007ffd2d8d5630 @name="Cup">]}
53
+
54
+ result.name.changed?
55
+ #=> true
56
+
57
+ result.name.was
58
+ #=> "Carl"
59
+
60
+ result.items[0].changed?
61
+ #=> false
62
+ result.items[1].changes?
63
+ #=> {"name"=>"Red Box"}
64
+ result.items[2].changed?
65
+ #=> true
66
+ result.items[2]
67
+ #=> nil
68
+ result.items[2].was
69
+ #=> #<Item:0x007fde2c045ee0 @name="Cup">
70
+ ````
71
+
72
+ ### Extended methods
73
+
74
+ HappyMapper::Differ extends each HappyMapper instance and element with the following methods:
75
+
76
+ |method|purpose|
77
+ |------|-------|
78
+ |changed?| returns true if this element, or a child has changed |
79
+ |changes | returns a has with each element or attribute which changed, and changed value |
80
+ |was | returns the prior(value from the right). If there is no change this will be the current value. |
81
+
82
+
83
+ ## Contributing
84
+
85
+ 1. Fork it ( https://github.com/pharos-ie/happymapper-differ/fork )
86
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
87
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
88
+ 4. Push to the branch (`git push origin my-new-feature`)
89
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ task default: [:test]
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'test'
8
+ t.pattern = "test/**/*_test.rb"
9
+ end
10
+
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'happymapper_differ'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "happymapper-differ"
8
+ spec.version = HappyMapper::Differ::VERSION
9
+ spec.authors = ["John Weir"]
10
+ spec.email = ["john@smokinggun.com"]
11
+ spec.summary = %q{Find changes between two like HappyMapper objects}
12
+ spec.description = %q{
13
+ In the unlikely event you are using HappyMapper, and the more unlikely
14
+ event you need to find changes between two HappyMapper objects,
15
+ HappyMapper::Differ might help you.
16
+
17
+ HappyMapper::Differ takes two HappyMapper objects and compares all the
18
+ attributes and elements. It modfies each element to include if it has changed
19
+ and what it changes are.
20
+ }
21
+
22
+ spec.homepage = "https://github.com/pharos-ei/happymapper-differ"
23
+ spec.license = "MIT"
24
+
25
+ spec.files = `git ls-files -z`.split("\x0")
26
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
27
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "nokogiri-happymapper", "~> 0.5"
31
+
32
+ spec.add_development_dependency "byebug", "~> 3.5"
33
+ spec.add_development_dependency "bundler", "~> 1.7"
34
+ spec.add_development_dependency "rake", "~> 10.0"
35
+ end
@@ -0,0 +1,132 @@
1
+ module HappyMapper
2
+ class Differ
3
+ VERSION = 0.1
4
+
5
+ def initialize(left, right)
6
+ @left = left
7
+ @right = right
8
+ end
9
+
10
+ def changed?
11
+ @left.to_xml == @right.to_xml
12
+ end
13
+
14
+ # Diff is a memory ineffecient and ugly method to find what elements and
15
+ # attributes have changed and how.
16
+ #
17
+ # It extends each element and attribute with the DiffedItem module
18
+ # and makes a clone of the original element for comparison.
19
+ def diff
20
+ out = @left
21
+ setup(@left, @right)
22
+
23
+ all = out.class.attributes + out.class.elements
24
+
25
+ # setup for each element (has_one and has_many) and attribute
26
+ all.map(&:name).compact.each do |name|
27
+ value = out.send(name)
28
+ if value.nil?
29
+ value = NilLike.new
30
+ out.send("#{name}=", value)
31
+ end
32
+
33
+ if value.is_a?(Array)
34
+ # Find the side with the most items
35
+ # If the right has more, the left will be padded with NilLike instances
36
+ count = [value.size, (@right.send(name) || []).size].max
37
+
38
+ count.times do |i|
39
+ value[i] = NilLike.new if value[i].nil?
40
+ setup_element(value[i], @right.send(name)[i])
41
+ end
42
+ else
43
+ setup_element(value, @right.send(name))
44
+ end
45
+ end
46
+
47
+ out
48
+ end
49
+
50
+ def handle_nil(name)
51
+ out.send("#{name}=", NilLike.new)
52
+ end
53
+
54
+ def setup(item, compared)
55
+ n = item.clone
56
+ item.extend(DiffedItem)
57
+ item.compared = compared
58
+ item.original = n
59
+ end
60
+
61
+ def setup_element(item, compared)
62
+ if(item.is_a?(HappyMapper))
63
+ Differ.new(item, compared).diff
64
+ else
65
+ setup(item, compared)
66
+ end
67
+ end
68
+
69
+ # nil can't be cloned or extended
70
+ # so this object behaves like nil
71
+ class NilLike
72
+ def nil?
73
+ true
74
+ end
75
+
76
+ def to_s
77
+ "nil"
78
+ end
79
+
80
+ def inspect
81
+ nil.inspect
82
+ end
83
+ end
84
+ end
85
+
86
+ module DiffedItem
87
+ attr_accessor :original
88
+
89
+ # The object this item is being compared to
90
+ attr_accessor :compared
91
+ alias :was :compared
92
+
93
+ def changed?
94
+ original != compared
95
+ end
96
+
97
+ def changes
98
+ cs = {} # the changes
99
+
100
+ original.class.attributes.map(&:name).each do |attr|
101
+ other_value = compared.send(attr)
102
+ if original.send(attr) != other_value
103
+ cs[attr] = other_value
104
+ end
105
+ end
106
+
107
+ original.class.elements.map(&:name).each do |name|
108
+ other_els = compared.send(name)
109
+ this_els = original.send(name)
110
+
111
+ if this_els.is_a?(Array)
112
+ this_els.each_with_index do |el, i|
113
+ if el != other_els[i]
114
+ cs[name] ||= []
115
+ cs[name] << other_els[i]
116
+ end
117
+ end
118
+ else
119
+ if this_els != other_els
120
+ cs[name] = other_els
121
+ end
122
+ end
123
+ end
124
+
125
+ cs
126
+ end
127
+
128
+ def ==(other)
129
+ original == other
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,11 @@
1
+ require 'happymapper'
2
+
3
+ module HappyMapper
4
+ require 'happymapper/differ'
5
+
6
+ # equality based on the underlying XML
7
+ def ==(other)
8
+ self.to_xml == other.to_xml
9
+ end
10
+ end
11
+
@@ -0,0 +1,173 @@
1
+ require 'test_helper'
2
+
3
+ describe "HappyMapper with Comparable" do
4
+ let(:left) { TParent.parse(sample_a) }
5
+ let(:right) { TParent.parse(sample_b) }
6
+
7
+ it "sez two identical documents should be equal" do
8
+ assert_equal left, TParent.parse(sample_a)
9
+ end
10
+
11
+ it "sez two different documents should not be equal" do
12
+ refute_equal left, right
13
+ end
14
+
15
+ describe HappyMapper::Differ do
16
+ let(:result) { HappyMapper::Differ.new(left, right).diff }
17
+
18
+ it "finds attribute changes" do
19
+ result = HappyMapper::Differ.new(
20
+ TParent.parse("<parent name='Roz'/>"),
21
+ TParent.parse("<parent name='Alex'/>"),
22
+ ).diff
23
+
24
+ assert result.changed?
25
+ assert result.name.changed?
26
+ assert_equal({"name" => "Alex"}, result.changes)
27
+ end
28
+
29
+ it "finds changes to has_one elements" do
30
+ a = TAddress.parse(address_a)
31
+ b = TAddress.parse(address_b)
32
+ result = HappyMapper::Differ.new(a, b).diff
33
+
34
+ assert_equal({"street" => "123 Park Ave"}, result.changes)
35
+ end
36
+
37
+ it "finds changes to has_many elements" do
38
+ assert ! result.children[0].changed?
39
+ assert ! result.children[1].changed?
40
+ assert result.children[2].changed?
41
+ assert_equal({"name" => "Vlad", "children" => [right.children[2]]}, result.changes)
42
+ assert_equal({}, result.children[1].changes)
43
+ assert_equal({"name" => "Alex"}, result.children[2].changes)
44
+ end
45
+
46
+ it "finds changes to nested data" do
47
+ result = HappyMapper::Differ.new(
48
+ TParent.parse(nested_a),
49
+ TParent.parse(nested_b),
50
+ ).diff
51
+
52
+ assert result.changed?
53
+ assert result.children[0].changed?
54
+ assert result.children[0].address.changed?
55
+ assert result.children[0].address.street.changed?
56
+
57
+ assert_equal({"children" => [result.children[0].compared]}, result.changes)
58
+ assert_equal({"street" => "789 Maple St"}, result.children[0].address.changes)
59
+ assert_equal("789 Maple St", result.children[0].address.street.compared)
60
+ end
61
+
62
+ it "find changes when the value is nil" do
63
+ result = HappyMapper::Differ.new(
64
+ TParent.parse("<parent/>"),
65
+ TParent.parse("<parent name='Alex'/>"),
66
+ ).diff
67
+
68
+ assert result.changed?
69
+ assert result.name.changed?
70
+ assert_equal 'Alex', result.name.compared
71
+ end
72
+
73
+ it "finds changes when the left side element count is less than the right" do
74
+ result = HappyMapper::Differ.new(
75
+ TParent.parse(sample_a),
76
+ TParent.parse(sample_a_plus),
77
+ ).diff
78
+
79
+ assert result.changed?
80
+ assert result.children[3].changed?
81
+ end
82
+ end
83
+
84
+ def sample_a
85
+ <<-XML
86
+ <parent name="Roz">
87
+ <child name="Joe"/>
88
+ <child name="Jane"/>
89
+ <child name="Jason"/>
90
+ </parent>
91
+ XML
92
+ end
93
+
94
+ def sample_a_plus
95
+ <<-XML
96
+ <parent name="Roz">
97
+ <child name="Joe"/>
98
+ <child name="Jane"/>
99
+ <child name="Jason"/>
100
+ <child name="Baxter"/>
101
+ </parent>
102
+ XML
103
+ end
104
+
105
+ def sample_b
106
+ <<-XML
107
+ <parent name="Vlad">
108
+ <child name="Joe"/>
109
+ <child name="Jane"/>
110
+ <child name="Alex"/>
111
+ </parent>
112
+ XML
113
+ end
114
+
115
+ def address_a
116
+ <<-XML
117
+ <address>
118
+ <name>Mr. Jones</name>
119
+ <street>123 Maple St</street>
120
+ <city>Brooklyn</city>
121
+ </address>
122
+ XML
123
+ end
124
+
125
+ def address_b
126
+ <<-XML
127
+ <address>
128
+ <name>Mr. Jones</name>
129
+ <street>123 Park Ave</street>
130
+ <city>Brooklyn</city>
131
+ </address>
132
+ XML
133
+ end
134
+
135
+ def nested_a
136
+ <<-XML
137
+ <parent name="Roz">
138
+ <child name="Joe">
139
+ <address>
140
+ <street>123 Maple St</street>
141
+ <city>Brooklyn</city>
142
+ </address>
143
+ </child>
144
+ <child name="Jane">
145
+ <address>
146
+ <street>567 Olice St</street>
147
+ <city>Brooklyn</city>
148
+ </address>
149
+ </child>
150
+ </parent>
151
+ XML
152
+ end
153
+
154
+ # Joe's address changed
155
+ def nested_b
156
+ <<-XML
157
+ <parent name="Roz">
158
+ <child name="Joe">
159
+ <address>
160
+ <street>789 Maple St</street>
161
+ <city>Brooklyn</city>
162
+ </address>
163
+ </child>
164
+ <child name="Jane">
165
+ <address>
166
+ <street>567 Olice St</street>
167
+ <city>Brooklyn</city>
168
+ </address>
169
+ </child>
170
+ </parent>
171
+ XML
172
+ end
173
+ end
@@ -0,0 +1,30 @@
1
+ require 'minitest/unit'
2
+ require 'minitest/autorun'
3
+ require 'minitest/pride'
4
+ require './lib/happymapper_differ'
5
+ require 'byebug'
6
+
7
+ class TParent
8
+ include HappyMapper
9
+ tag 'parent'
10
+
11
+ attribute :name, String
12
+ has_many :children, "TChild", tag: 'child'
13
+ end
14
+
15
+ class TChild
16
+ include HappyMapper
17
+ tag 'child'
18
+
19
+ attribute :name, String
20
+ has_one :address, "TAddress"
21
+ end
22
+
23
+ class TAddress
24
+ include HappyMapper
25
+ tag 'address'
26
+
27
+ has_one :name, String
28
+ has_one :street, String
29
+ has_one :city, String
30
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: happymapper-differ
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - John Weir
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri-happymapper
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.7'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.7'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ description: "\n In the unlikely event you are using HappyMapper, and the more
70
+ unlikely\n event you need to find changes between two HappyMapper objects,\n
71
+ \ HappyMapper::Differ might help you.\n\n HappyMapper::Differ takes two HappyMapper
72
+ objects and compares all the\n attributes and elements. It modfies each element
73
+ to include if it has changed\n and what it changes are.\n "
74
+ email:
75
+ - john@smokinggun.com
76
+ executables: []
77
+ extensions: []
78
+ extra_rdoc_files: []
79
+ files:
80
+ - Gemfile
81
+ - Gemfile.lock
82
+ - LICENSE
83
+ - README.md
84
+ - Rakefile
85
+ - happymapper-differ.gemspec
86
+ - lib/happymapper/differ.rb
87
+ - lib/happymapper_differ.rb
88
+ - test/happymapper_differ_test.rb
89
+ - test/test_helper.rb
90
+ homepage: https://github.com/pharos-ei/happymapper-differ
91
+ licenses:
92
+ - MIT
93
+ metadata: {}
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 2.2.2
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: Find changes between two like HappyMapper objects
114
+ test_files:
115
+ - test/happymapper_differ_test.rb
116
+ - test/test_helper.rb