pedanco-diffr 1.0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ab7434a821dc08fd4cbaf49a4b1802182b755bed
4
+ data.tar.gz: 06219e44cfbf45f8ff8a867a2058ff8db8dc5e78
5
+ SHA512:
6
+ metadata.gz: c54abecceac0488bc88d4f1069bab65a62d4b20ecd55594e33ec0fbd8bc299941aac5a252c5ad22fc1ae4f9a4cc00f27846b1ae2eea8d9ff328c1b749af26087
7
+ data.tar.gz: 22b9b82fb4856c4957437ce5666494bec1f6f51de3e62b8f75ef21999e1e1343be8208fa0d3900ec5b221474c2ef668a51d56bf33a4cea8b3b0b521b062363d1
@@ -0,0 +1,34 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /lib/bundler/man/
26
+
27
+ # for a library or gem, you might want to ignore these files since the code is
28
+ # intended to run in multiple environments; otherwise, check them in:
29
+ # Gemfile.lock
30
+ # .ruby-version
31
+ # .ruby-gemset
32
+
33
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
34
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
@@ -0,0 +1,5 @@
1
+ Style/LineLength:
2
+ Max: 120
3
+
4
+ Metrics/MethodLength:
5
+ CountComments: false # count full line comments?
@@ -0,0 +1,7 @@
1
+ ## 1.0.0 (2015-03-10)
2
+
3
+ Authors: James Polanco
4
+
5
+ * Initial release of Pedanco::Diffr
6
+ * Some changes to API from internal version
7
+ * Added documentation and code comments
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pedanco-diffr.gemspec
4
+ gemspec
5
+
6
+ gem 'coveralls', require: false
7
+ gem 'codeclimate-test-reporter', group: :test, require: nil
@@ -0,0 +1,71 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ pedanco-diffr (1.0.0)
5
+ activesupport (>= 3.2, <= 4.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (4.2.0)
11
+ i18n (~> 0.7)
12
+ json (~> 1.7, >= 1.7.7)
13
+ minitest (~> 5.1)
14
+ thread_safe (~> 0.3, >= 0.3.4)
15
+ tzinfo (~> 1.1)
16
+ codeclimate-test-reporter (0.4.7)
17
+ simplecov (>= 0.7.1, < 1.0.0)
18
+ coveralls (0.7.11)
19
+ multi_json (~> 1.10)
20
+ rest-client (>= 1.6.8, < 2)
21
+ simplecov (~> 0.9.1)
22
+ term-ansicolor (~> 1.3)
23
+ thor (~> 0.19.1)
24
+ diff-lcs (1.2.5)
25
+ docile (1.1.5)
26
+ i18n (0.7.0)
27
+ json (1.8.2)
28
+ mime-types (2.4.3)
29
+ minitest (5.5.1)
30
+ multi_json (1.11.0)
31
+ netrc (0.10.3)
32
+ rake (10.4.2)
33
+ rest-client (1.7.3)
34
+ mime-types (>= 1.16, < 3.0)
35
+ netrc (~> 0.7)
36
+ rspec (3.2.0)
37
+ rspec-core (~> 3.2.0)
38
+ rspec-expectations (~> 3.2.0)
39
+ rspec-mocks (~> 3.2.0)
40
+ rspec-core (3.2.1)
41
+ rspec-support (~> 3.2.0)
42
+ rspec-expectations (3.2.0)
43
+ diff-lcs (>= 1.2.0, < 2.0)
44
+ rspec-support (~> 3.2.0)
45
+ rspec-mocks (3.2.1)
46
+ diff-lcs (>= 1.2.0, < 2.0)
47
+ rspec-support (~> 3.2.0)
48
+ rspec-support (3.2.2)
49
+ simplecov (0.9.2)
50
+ docile (~> 1.1.0)
51
+ multi_json (~> 1.0)
52
+ simplecov-html (~> 0.9.0)
53
+ simplecov-html (0.9.0)
54
+ term-ansicolor (1.3.0)
55
+ tins (~> 1.0)
56
+ thor (0.19.1)
57
+ thread_safe (0.3.4)
58
+ tins (1.3.5)
59
+ tzinfo (1.2.2)
60
+ thread_safe (~> 0.1)
61
+
62
+ PLATFORMS
63
+ ruby
64
+
65
+ DEPENDENCIES
66
+ bundler (~> 1.7)
67
+ codeclimate-test-reporter
68
+ coveralls
69
+ pedanco-diffr!
70
+ rake (~> 10.0)
71
+ rspec (~> 3.2)
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 DevelopmentArc
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 all
13
+ 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 THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,184 @@
1
+ # pedanco-diffr
2
+
3
+ [![Codeship Status for DevelopmentArc/pedanco-diffr](https://codeship.com/projects/2ca2cec0-a9a6-0132-8917-427bb4181a39/status?branch=master)](https://codeship.com/projects/67769)
4
+ [![Code Climate](https://codeclimate.com/github/DevelopmentArc/pedanco-diffr/badges/gpa.svg)](https://codeclimate.com/github/DevelopmentArc/pedanco-diffr)
5
+ [![Coverage Status](https://coveralls.io/repos/DevelopmentArc/pedanco-diffr/badge.svg?branch=master)](https://coveralls.io/r/DevelopmentArc/pedanco-diffr?branch=master)
6
+
7
+ A change set library for managing how data is evolved overtime. `Pedanco::Diffr` allows you to track changes in a `ChangeSet` and then query the `ChangeSet` to determine if the data has changed, what the current value is and what the previous value was. This was inspired by the [ActiveModel::Dirty](http://api.rubyonrails.org/classes/ActiveModel/Dirty.html) class, but has been isolated so that it can be used to track any kind of data change without the ActiveModel/ActiveRecord inclusions.
8
+
9
+ # Installation
10
+ ```bash
11
+ gem install pedanco-diffr
12
+ ```
13
+
14
+ Or add it to your Gemfile:
15
+
16
+ ```bash
17
+ gem 'pedanco-diffr'
18
+ ```
19
+
20
+ # Usage
21
+
22
+ ```ruby
23
+ # Create a new ChangeSet
24
+ change_set = Pedanco::Diffr::ChangeSet.new(name: ['Tim', 'Tom'])
25
+
26
+ # Add another change and query the ChangeSet
27
+ change_set.add_change(:age, 21, 23)
28
+ change_set.name_changed? # => true
29
+ change_set.changed(:age) # => true
30
+
31
+ # Remove a change and query the ChangeSet
32
+ change_set.remove_change(:age) # => true
33
+ change_set.age_changed? # => false
34
+ change_set.changed?(:age) # => false
35
+
36
+ # Extract a change
37
+ name_change = change_set.get_change(:name) # => Pedanco::Diffr::Change
38
+ name_change.current # => 'Tim'
39
+ name_change.previous # => 'Tom'
40
+ ```
41
+
42
+ # Creating a ChangeSet
43
+ When creating a new ChangeSet you can pass in a `Hash` of named `Arrays`. The key of the hash will be used to generate the change name, and the `Array` defines the current and previous values.
44
+
45
+ ```ruby
46
+ data_set = { city: ['San Diego', 'Denver'], state: ['CA', nil] }
47
+ change_set = Pedanco::Diffr::ChangeSet.new(data_set)
48
+ change_set.state_changed? #=> true
49
+ ```
50
+
51
+ You can also directly add changes using the `add_change` method.
52
+
53
+ ```ruby
54
+ change_set = Pedanco::Diffr::ChangeSet.new
55
+ change_set.add_change(:city, 'San Diego', 'Denver')
56
+ change_set.add_change(:state, 'CA')
57
+
58
+ change_set.city_changed? #=> true
59
+ change_set.state_changed? #=> true
60
+ ```
61
+
62
+ If you want to import `ActiveModel::Dirty` changes into a `ChangeSet` you can call the `parse_changes()` method passing in the data.
63
+
64
+ ```ruby
65
+ # We have updated an Address Model (ActiveRecord::Base) and want to store the changes
66
+ change_set = Pedanco::Diffr::ChangeSet.new
67
+ change_set.parse_changes(address.changes)
68
+ ```
69
+
70
+ # Quering the ChangeSet
71
+
72
+ Once a change has been added you can query the ChangeSet to see if the set contains a change you are looking for. You can do this by using the `_changed?` convinence method, which prepends the name of the change, such as `city_changed?`. You can can also call the `changed?` method passing in the name of the change you want to verify.
73
+
74
+ ```ruby
75
+ change_set = Pedanco::Diffr::ChangeSet.new
76
+ change_set.add_change(:city, 'San Diego', 'Denver')
77
+
78
+ # Using _changed?
79
+ change_set.city_changed? #=> true
80
+ change_set.address_changed? #=> false
81
+
82
+ # Using changed?()
83
+ change_set.changed?(:city) # => true
84
+ change_set.changed?(:address) # => false
85
+ ```
86
+
87
+ The `changed?` method also allows you to pass in an array of names to query by. By default, if the `ChangeSet` has any of the changes requested, it will return `true`.
88
+
89
+ ```ruby
90
+ change_set = Pedanco::Diffr::ChangeSet.new
91
+ change_set.add_change(:city, 'San Diego', 'Denver')
92
+
93
+ # Pass an Array of names to query
94
+ change_set.changed?([:city, :address]) # => true
95
+ ```
96
+
97
+ If you need to make sure that all the key names passed have changed, then you can pass the `:all` flag to `changed?` method.
98
+
99
+ ```ruby
100
+ change_set = Pedanco::Diffr::ChangeSet.new
101
+ change_set.add_change(:city, 'San Diego', 'Denver')
102
+ change_set.changed?([:city, :address], :all) # => false
103
+ ```
104
+
105
+ # Using Changes
106
+ Once you have created a change set you can access the current and previous value of the change by calling `get_change`
107
+
108
+ ```ruby
109
+ change_set = Pedanco::Diffr::ChangeSet.new
110
+ change_set.add_change(:city, 'San Diego', 'Denver')
111
+ change_set.add_change(:state, 'CA')
112
+
113
+ change_set.get_change(:city).current # => 'San Diego'
114
+ change_set.get_change(:city).previous # => 'Denver'
115
+ ```
116
+
117
+ When calling `get_change` the ChangeSet will return a `Pedanco::Diffr::Change` instance. This instance allows you to get the name, current and previous value. If the change request can not be found then an empty `Change` will be returned.
118
+
119
+ ```ruby
120
+ change_set = Pedanco::Diffr::ChangeSet.new
121
+ change = change_set.get_change(:age)
122
+ change.name # => :age
123
+ change.current # => nil
124
+ change.previous # => nil
125
+ ```
126
+
127
+ # Change values
128
+ A `Pedanco::Diffr::Change` instance's current or previous values can be any kind of data. By default both the current and previous are `nil`. This allows for empty changes, a change from empty to something or something to empty. Only the name of the change is required. The name can be a `String` or `Symbol`. By defailt we convert the name to a `Symbol` for lookup purposes.
129
+
130
+ ```ruby
131
+ change_set = Pedanco::Diffr::ChangeSet.new
132
+ change_set.add_change('name', nil, nil) # => Empty change
133
+ change_set.add_change('field', 'Bar') # => Empty to something change
134
+ change_set.add_change(:money, nil, 'Something') # => Soemthing to empty change
135
+ ```
136
+
137
+ When calling `add_change` only a current value must be passed to the method (including `nil`). By default the previous value will be set to `nil`.
138
+
139
+ # Updating changes
140
+ When working with a ChangeSet you can remove an existing change or overwrite it. To remove a change you can call the `remove_change` method. To override a change, just call `add_change` with the same name.
141
+
142
+ ```ruby
143
+ change_set = Pedanco::Diffr::ChangeSet.new
144
+ change_set.add_change(:city, 'San Diego', 'Denver')
145
+ change_set.add_change(:state, 'CA')
146
+
147
+ # overriding a change
148
+ change_set.add_change(:state, 'CA', 'CO')
149
+ change_set.get_change(:state).previous # => 'CO'
150
+
151
+ # removing a change
152
+ change_set.remove_change(:city)
153
+ change_set.city_changed? # => false
154
+ ```
155
+
156
+ # Why Pedanco::Diffr?
157
+ At [Pedanco](https://pedanco.com) we use [Wisper](https://github.com/krisleech/wisper) to handle global events. When these events are dispatched, they trigger cache invalidation, system wide updates, and other complex tasks that usually run in an async worker. The challenge we had was that some changes in the system trigger different actions.
158
+
159
+ For example, when a user changes their Role or name, we need to run a lot of cache updates to change access rights and views. But, if they just change their signature then we don't need to trigger these system updates.
160
+
161
+ `Pedanco::Diffr` allows us to build a `ChangeSet` that is then passed along with the Wisper event. Our global subscribers can then process the changes and trigger different actions based on what changed. We use a combination of custom change tracking in [Mutations](https://github.com/cypriss/mutations) and `ActiveRecord` changes to build out our `ChangeSet` and then dispatch them once the process of creation/updating is complete.
162
+
163
+ # Liscence
164
+ The MIT License (MIT)
165
+
166
+ Copyright (c) 2015 DevelopmentArc
167
+
168
+ Permission is hereby granted, free of charge, to any person obtaining a copy
169
+ of this software and associated documentation files (the "Software"), to deal
170
+ in the Software without restriction, including without limitation the rights
171
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
172
+ copies of the Software, and to permit persons to whom the Software is
173
+ furnished to do so, subject to the following conditions:
174
+
175
+ The above copyright notice and this permission notice shall be included in all
176
+ copies or substantial portions of the Software.
177
+
178
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
179
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
180
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
181
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
182
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
183
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
184
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,33 @@
1
+ #--
2
+ # The MIT License (MIT)
3
+ #
4
+ # Copyright (c) 2015 DevelopmentArc
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all
14
+ # copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ # SOFTWARE.
23
+ #++
24
+
25
+ # 3rd Party Gem Dependencies
26
+ require 'active_support'
27
+ require 'active_support/core_ext/object/blank'
28
+ require 'active_support/core_ext/array/wrap'
29
+
30
+ # Gem Dependencies
31
+ require 'pedanco/diffr/version'
32
+ require 'pedanco/diffr/change'
33
+ require 'pedanco/diffr/change_set'
@@ -0,0 +1,50 @@
1
+ module Pedanco
2
+ module Diffr
3
+ #
4
+ # A Change represents a current and previous state for specifc piece of
5
+ # data. These states can represent any kind of information and are linked
6
+ # by the name of the data they represent.
7
+ #
8
+ # Pedanco::Diffr::Change.new(:first_name, 'Jim', 'James')
9
+ #
10
+ # @!attribute [rw] name
11
+ # @return [String,Symbol] the name identifier for the change set
12
+ # @!attribute [rw] current
13
+ # @return [Any] the current state of the change
14
+ # @!attribute [rw] previous
15
+ # @return [Any] the previous state of the change
16
+ #
17
+ # @author [jpolanco]
18
+ #
19
+ class Change
20
+ attr_accessor :name, :current, :previous
21
+
22
+ #
23
+ # Initializes the data when passed via new().
24
+ #
25
+ # @param name [String/Symbol] (required) The name of the data
26
+ # @param current [Any] (optional) The current value for the
27
+ # change, defaults to nil.
28
+ # @param previous [Any] (optional) The previous value for the
29
+ # change, defaults to nil.
30
+ #
31
+ # @raise [RuntimeError] if name is nil or ''
32
+ def initialize(name, current = nil, previous = nil)
33
+ fail 'Name is required for a Change.' if name.blank?
34
+ @name = name
35
+ @current = current
36
+ @previous = previous
37
+ end
38
+
39
+ #
40
+ # Converts the Change into an array, following the ActiveRecord
41
+ # changes syntax. The first position is the previous value, the
42
+ # second is the current value.
43
+ #
44
+ # @return [Array] The array version of the Change
45
+ def to_a
46
+ [@previous, @current]
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,209 @@
1
+ module Pedanco
2
+ module Diffr
3
+ #
4
+ # A ChangeSet contains a one or more changes. Change data can be
5
+ # passed during `new()` or can be added/removed directly from the
6
+ # change instance.
7
+ #
8
+ # When a change is added, the ChangeSet provides both a direct method
9
+ # to look up a change or a convience method with a `_changed?` prefix.
10
+ #
11
+ # inst = Pedanco::Diffr::ChangeSet.new
12
+ # inst.add_change(:age, 40, 39)
13
+ #
14
+ # inst.age_changed? # true
15
+ # inst.changed?(:age) # true
16
+ #
17
+ # When creating a ChangeSet you can also pass in ActiveModel::Dirty syntax
18
+ # to the `parse_changes()` method to convert the data into a ChangeSet.
19
+ #
20
+ # @author [jpolanco]
21
+ #
22
+ class ChangeSet
23
+ #
24
+ # Initializes the Change instance. Allows for a hash of array pairs, ex:
25
+ #
26
+ # { name: ['Bob', 'Tom'], age: [33, 32] }
27
+ #
28
+ # which represents a change set to be parsed in on creation.
29
+ #
30
+ # @param change_hash [Hash]
31
+ # (optional) hash to be converted into a ChangeSet.
32
+ #
33
+ def initialize(change_hash = {})
34
+ @changes = parse_change_hash(change_hash)
35
+ end
36
+
37
+ #
38
+ # Override to catch `_changed?` postfix convenience calls to
39
+ # determine if a property has changed.
40
+ #
41
+ def method_missing(name, *args, &block)
42
+ if name.to_s =~ /_changed\?/
43
+ @changes.key?(name.to_s.gsub(/_changed\?/, '').to_sym)
44
+ else
45
+ super
46
+ end
47
+ end
48
+
49
+ #
50
+ # Implementation to support respond_to? lookup for
51
+ # `_changed?` postfix convenience calls.
52
+ #
53
+ # @return [Boolean] true if class responds to the requested
54
+ # method name
55
+ def respond_to?(name, include_private = false)
56
+ if name.to_s =~ /_changed\?/
57
+ true
58
+ else
59
+ super
60
+ end
61
+ end
62
+
63
+ #
64
+ # Adds a change to the change set. This method converts the arguments
65
+ # into a Change object internally.
66
+ #
67
+ # inst = Pedanco::Diffr::ChangeSet.new
68
+ # inst.add_change(:age, 40, 39)
69
+ #
70
+ # inst.age_changed? # true
71
+ #
72
+ # @param name [String,Symbol] The name of the value that has changed
73
+ # @param current_value [anything] The current value
74
+ # @param previous_value [anything] (optional) The previous value,
75
+ # the default is nil
76
+ #
77
+ # @return [Change] Returns the generated change
78
+ def add_change(name, current_value, previous_value = nil)
79
+ sym = name.to_sym
80
+ @changes[sym] =
81
+ Change.new(sym, current_value, previous_value)
82
+ @changes[sym]
83
+ end
84
+
85
+ #
86
+ # Removes an existing change by the name of the change. This method
87
+ # is safe to call if a change is not found.
88
+ #
89
+ # inst = Pedanco::Diffr::ChangeSet.new
90
+ # inst.add_change(:age, 40, 39)
91
+ # inst.age_changed? # true
92
+ #
93
+ # inst.remove_change(:age) # true
94
+ #
95
+ # @param name [String,Symbol] The name of the change to remove.
96
+ #
97
+ # @return [Change] The removed change,
98
+ # nil if no change by that name was found
99
+ def remove_change(name)
100
+ @changes.delete(name.to_sym)
101
+ end
102
+
103
+ #
104
+ # Determines if the provided change(s) exist in the change set.
105
+ # When the match type is set to `:any`, if any key matches at least
106
+ # one change the method returns false.
107
+ #
108
+ # inst = Pedanco::Diffr::ChangeSet.new
109
+ # inst.add_change(:age, 40, 39)
110
+ # inst.add_change(:name, 'George', 'Frank')
111
+ #
112
+ # inst.changed?([:foo, :age]) # true
113
+ #
114
+ # If the match type is :all, all matches must exist.
115
+ #
116
+ # inst = Pedanco::Diffr::ChangeSet.new
117
+ # inst.add_change(:age, 40, 39)
118
+ # inst.add_change(:name, 'George', 'Frank')
119
+ #
120
+ # inst.changed?([:foo, :age], :all) # false
121
+ #
122
+ # @param keys [String,Symbol,Array] Defines a single or list
123
+ # of names to validate existence of.
124
+ # @param match_type [Symbol] (optional) Defines how a check is
125
+ # made, default is :any also accepts :all
126
+ #
127
+ # @return [Boolean] True if matches found, false if not.
128
+ def changed?(keys, match_type = :any)
129
+ keys = Array.wrap(keys).map(&:to_sym)
130
+ if match_type == :all
131
+ (keys & @changes.keys).length == keys.length
132
+ else
133
+ (keys & @changes.keys).present?
134
+ end
135
+ end
136
+
137
+ #
138
+ # Looks up a change by name and returns it if found. If the
139
+ # change is not found an empty Change instance is returned. This
140
+ # prevents having to check for nil.
141
+ #
142
+ # inst = Pedanco::Diffr::ChangeSet.new
143
+ # inst.add_change(:age, 40, 39)
144
+ #
145
+ # # returns Pedanco::Diffr::Change(:age, current: 40, previous: 39)
146
+ # inst.get_change(:age)
147
+ #
148
+ # @param name [String,Symbol] The name of the change to lookup.
149
+ #
150
+ # @return [Change] The Change object by name,
151
+ # an empty change is returned if not found.
152
+ def get_change(name)
153
+ @changes.fetch(name.to_sym, Change.new(name.to_sym))
154
+ end
155
+
156
+ #
157
+ # Finds and returns a ChangeSet as an array, used for legacy rendering
158
+ # that expects changes to be in [current, previous] format.
159
+ #
160
+ # @param name [String,Symbol] (optional) The name of the change
161
+ #
162
+ # @return [Array] The Change as an array or empty if no change is found.
163
+ def to_a(name = nil)
164
+ if name.present?
165
+ change = @changes[name.to_sym]
166
+ change.present? ? change.to_a : []
167
+ else
168
+ @changes.map { |k, v| [k, v.to_a] }
169
+ end
170
+ end
171
+
172
+ #
173
+ # Converts the Change Set into an Hash.
174
+ #
175
+ # {
176
+ # name: [current, previous]
177
+ # }
178
+ #
179
+ #
180
+ # @return [Hash] The Change Set as an hash.
181
+ def to_hash
182
+ Hash[@changes.map { |k, v| [k, v.to_a] }]
183
+ end
184
+
185
+ #
186
+ # Parses the ActiveModel::Dirty syntax to build a changeset.
187
+ # The Dirty syntax is:
188
+ #
189
+ # { 'property_name' => [old_value, new_value] }
190
+ #
191
+ # @param changes [Hash] An ActiveModel::Dirty changes hash
192
+ #
193
+ def parse_changes(changes)
194
+ changes.each { |name, change| add_change(name, change[1], change[0]) }
195
+ end
196
+
197
+ private
198
+
199
+ def parse_change_hash(hash)
200
+ hash_array = hash.map do |k, v|
201
+ name = k.to_sym
202
+ value = Array.wrap(v)
203
+ [name, Change.new(name, value[0], value[1])]
204
+ end
205
+ Hash[hash_array]
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,5 @@
1
+ module Pedanco
2
+ module Diffr
3
+ VERSION = '1.0.0'
4
+ end
5
+ end
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pedanco/diffr/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'pedanco-diffr'
8
+ spec.version = Pedanco::Diffr::VERSION
9
+ spec.authors = ['James Polanco']
10
+ spec.email = ['james@developmentarc.com']
11
+ spec.summary = 'Provides a change library for managing system changes.'
12
+ spec.description = <<-EOM
13
+ Pedanco::Diffr provides a change set management system for tracking changes
14
+ anywhere in the system. Diffr works with both ActiveRecord style changes and
15
+ provides a custom system so that it kind be used in a wider syntax without
16
+ requiring ActiveRecord or ActiveModel.
17
+ EOM
18
+
19
+ spec.homepage = 'https://github.com/DevelopmentArc/pedanco-diffr'
20
+ spec.license = 'MIT'
21
+
22
+ spec.files = `git ls-files -z`.split("\x0")
23
+ spec.test_files = `git ls-files test`.split("\x0")
24
+ spec.require_path = 'lib'
25
+
26
+ ### Gem Dependencies
27
+ spec.add_runtime_dependency 'activesupport', '<= 4.2', '>= 3.2'
28
+
29
+ ### Development Dependencies
30
+ spec.add_development_dependency 'bundler', '~> 1.7'
31
+ spec.add_development_dependency 'rake', '~> 10.0'
32
+ spec.add_development_dependency 'rspec', '~> 3.2'
33
+ end
@@ -0,0 +1,184 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pedanco::Diffr::ChangeSet do
4
+
5
+ let(:change_set) { Pedanco::Diffr::ChangeSet.new }
6
+
7
+ describe '#initialize' do
8
+ it 'parses change hash' do
9
+ set = described_class.new(foo: %w(bar baz), biff: [nil, 'hello'])
10
+ expect(set.changed?([:foo, 'biff'])).to be_truthy
11
+ expect(set.get_change(:foo).current).to eq 'bar'
12
+ end
13
+ end
14
+
15
+ describe '#method_missing' do
16
+ it 'returns true for _changed? postfix' do
17
+ change_set.add_change(:name, nil, 'bar')
18
+ expect(change_set.name_changed?).to eq true
19
+ end
20
+
21
+ it 'calls super for non-matched calls' do
22
+ expect { change_set.foobar }.to raise_error
23
+ end
24
+ end
25
+
26
+ describe '#respond_to?' do
27
+ it 'returns true for _changed? postfix' do
28
+ expect(change_set.respond_to?(:name_changed?)).to eq true
29
+ end
30
+
31
+ it 'calls super for non-matched calls' do
32
+ expect(change_set.respond_to?(:foobar)).to eq false
33
+ end
34
+ end
35
+
36
+ describe '#add_change' do
37
+ it 'adds a new change' do
38
+ change_set.add_change(:name, 'foo', 'bar')
39
+ expect(change_set.get_change(:name)).to_not be_nil
40
+ expect(change_set.get_change(:name).current).to eq 'foo'
41
+ expect(change_set.get_change(:name).previous).to eq 'bar'
42
+ end
43
+
44
+ it 'allows nil for current' do
45
+ change_set.add_change(:name, nil, 'bar')
46
+ expect(change_set.get_change(:name)).to_not be_nil
47
+ end
48
+
49
+ it 'allows nil for previous' do
50
+ change_set.add_change(:name, 'foo', nil)
51
+ expect(change_set.get_change(:name)).to_not be_nil
52
+ end
53
+
54
+ it 'defaults previous to nil' do
55
+ change_set.add_change(:name, 'foo')
56
+ expect(change_set.get_change(:name)).to_not be_nil
57
+ end
58
+
59
+ it 'requires a name' do
60
+ expect do
61
+ change_set.add_change(nil, 'foo', 'bar')
62
+ end.to raise_error
63
+ end
64
+
65
+ it 'requires current as an argument' do
66
+ expect do
67
+ change_set.add_change(:name)
68
+ end.to raise_error
69
+ end
70
+
71
+ it 'overrides a change' do
72
+ change_set.add_change(:name, 'foo', 'bar')
73
+ change_set.add_change(:name, 'bar', 'baz')
74
+ expect(change_set.get_change(:name).current).to eq 'bar'
75
+ end
76
+ end
77
+
78
+ describe '#remove_change' do
79
+ before(:each) { change_set.add_change(:name, 'foo') }
80
+
81
+ it 'deletes an existing change' do
82
+ change_set.remove_change(:name)
83
+ expect(change_set.changed?(:name)).to be_falsey
84
+ end
85
+
86
+ it 'returns truthy when removing an existing change' do
87
+ expect(change_set.remove_change(:name)).to be_truthy
88
+ end
89
+
90
+ it 'returns falsey when removing an non-existing change' do
91
+ expect(change_set.remove_change(:foo)).to be_falsey
92
+ end
93
+ end
94
+
95
+ describe '#changed?' do
96
+ before(:each) do
97
+ change_set.add_change(:name, 'foo')
98
+ change_set.add_change(:age, 12)
99
+ change_set.add_change(:email, 'bar@biz.com')
100
+ end
101
+
102
+ it 'finds a single change' do
103
+ expect(change_set.changed?(:name)).to eq true
104
+ end
105
+
106
+ it 'matches mutliple changes' do
107
+ expect(change_set.changed?([:name, :age])).to eq true
108
+ end
109
+
110
+ it 'requires one change to match' do
111
+ expect(change_set.changed?([:age, :phone])).to eq true
112
+ end
113
+
114
+ it 'requires all changes to match' do
115
+ expect(change_set.changed?([:name, :age, :phone], :all)).to eq false
116
+ end
117
+ end
118
+
119
+ describe '#get_change' do
120
+ before(:each) { change_set.add_change(:name, 'foo') }
121
+
122
+ it 'returns the change when found' do
123
+ expect(change_set.get_change(:name).current).to eq 'foo'
124
+ end
125
+
126
+ it 'returns the Diffr::Change object' do
127
+ expect(change_set.get_change(:name)).to be_instance_of(Pedanco::Diffr::Change)
128
+ end
129
+
130
+ it 'returns empty Diffr::Change if not found' do
131
+ expect(change_set.get_change(:foo).current).to be_nil
132
+ expect(change_set.get_change(:foo).previous).to be_nil
133
+ end
134
+ end
135
+
136
+ describe '_changed? matchers' do
137
+ before(:each) { change_set.add_change(:name, 'foo') }
138
+
139
+ it 'returns true for changes that match' do
140
+ expect(change_set.name_changed?).to be_truthy
141
+ end
142
+
143
+ it 'returns false for changes that do not exists' do
144
+ expect(change_set.foo_changed?).to be_falsey
145
+ end
146
+ end
147
+
148
+ describe '#to_a' do
149
+ before(:each) do
150
+ change_set.add_change(:name, 'foo')
151
+ change_set.add_change(:age, 33, 32)
152
+ end
153
+
154
+ it 'returns the change when found' do
155
+ expect(change_set.to_a(:name)).to eq [nil, 'foo']
156
+ end
157
+
158
+ it 'returns nil if not found' do
159
+ expect(change_set.to_a(:foo)).to eq []
160
+ end
161
+
162
+ it 'maps out all the changes with no arguments' do
163
+ expect(change_set.to_a).to eq [[:name, [nil, "foo"]], [:age, [32, 33]]]
164
+ end
165
+ end
166
+
167
+ describe '#to_hash' do
168
+ before(:each) { change_set.add_change(:name, 'foo') }
169
+
170
+ it 'returns the change when found' do
171
+ expect(change_set.to_hash).to include(name: [nil, 'foo'])
172
+ end
173
+
174
+ end
175
+
176
+ describe '#parse_changes' do
177
+ before(:each) { change_set.parse_changes('foo' => %w(baz bar), 'biff' => [10, 1]) }
178
+
179
+ it 'extracts the changes' do
180
+ expect(change_set.get_change(:foo).current).to eq 'bar'
181
+ expect(change_set.get_change('biff').previous).to eq 10
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pedanco::Diffr::Change do
4
+ context '#initialize' do
5
+ it 'requires a name' do
6
+ expect { Pedanco::Diffr::Change.new }.to raise_error
7
+ end
8
+
9
+ context 'default values' do
10
+ subject { Pedanco::Diffr::Change.new(:foo) }
11
+
12
+ it 'defaults current to nil' do
13
+ expect(subject.current).to be_nil
14
+ end
15
+
16
+ it 'defaults previous to nil' do
17
+ expect(subject.previous).to be_nil
18
+ end
19
+ end
20
+
21
+ context 'passing values' do
22
+ subject { Pedanco::Diffr::Change.new(:bar, 'baz', 'biff') }
23
+
24
+ it 'sets the name' do
25
+ expect(subject.name).to eq :bar
26
+ end
27
+
28
+ it 'sets the current' do
29
+ expect(subject.current).to eq 'baz'
30
+ end
31
+
32
+ it 'sets the previous' do
33
+ expect(subject.previous).to eq 'biff'
34
+ end
35
+ end
36
+ end
37
+
38
+ context '#to_a' do
39
+ subject { Pedanco::Diffr::Change.new(:bar, 'baz', 'biff') }
40
+
41
+ it 'orders the array correctly' do
42
+ expect(subject.to_a).to eq %w(biff baz)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,14 @@
1
+ require 'codeclimate-test-reporter'
2
+ CodeClimate::TestReporter.start
3
+
4
+ require 'coveralls'
5
+ Coveralls.wear!
6
+
7
+ require 'bundler/setup'
8
+ Bundler.setup
9
+
10
+ require 'pedanco/diffr' # and any other gems you need
11
+
12
+ RSpec.configure do |_config|
13
+ # some (optional) config here
14
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pedanco-diffr
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - James Polanco
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "<="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: '3.2'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "<="
28
+ - !ruby/object:Gem::Version
29
+ version: '4.2'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '3.2'
33
+ - !ruby/object:Gem::Dependency
34
+ name: bundler
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.7'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.7'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '10.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '10.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rspec
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.2'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '3.2'
75
+ description: |2
76
+ Pedanco::Diffr provides a change set management system for tracking changes
77
+ anywhere in the system. Diffr works with both ActiveRecord style changes and
78
+ provides a custom system so that it kind be used in a wider syntax without
79
+ requiring ActiveRecord or ActiveModel.
80
+ email:
81
+ - james@developmentarc.com
82
+ executables: []
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - ".gitignore"
87
+ - ".rspec"
88
+ - ".rubocop.yml"
89
+ - CHANGELOG.md
90
+ - Gemfile
91
+ - Gemfile.lock
92
+ - LICENSE
93
+ - README.md
94
+ - Rakefile
95
+ - lib/pedanco/diffr.rb
96
+ - lib/pedanco/diffr/change.rb
97
+ - lib/pedanco/diffr/change_set.rb
98
+ - lib/pedanco/diffr/version.rb
99
+ - pedanco-diffr.gemspec
100
+ - spec/lib/pedanco/diffr/change_set_spec.rb
101
+ - spec/lib/pedanco/diffr/change_spec.rb
102
+ - spec/spec_helper.rb
103
+ homepage: https://github.com/DevelopmentArc/pedanco-diffr
104
+ licenses:
105
+ - MIT
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 2.4.2
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Provides a change library for managing system changes.
127
+ test_files: []