happymapper-differ 0.1

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.
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