depth 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4571967f84027109674890d8d99993df6515740d
4
- data.tar.gz: 031c3763b9bf00568b76d70b6ef59f5bfcccfe4d
3
+ metadata.gz: 0e40cab4a65c17f79c8dd51b99c7a5abd34c254a
4
+ data.tar.gz: 7e39b1a02e7ccbe5e2c40a89b489b30b5bc3bc8a
5
5
  SHA512:
6
- metadata.gz: 18b2fc8ca9c8b8145366ad48e1e7df6c9c36696c7ab2de5eb3728641d5f778eb5eb70f8dbeccf79003e8fd166e964c983449c24e1a0193930d6f8cb53c3af535
7
- data.tar.gz: 2c9e9dd29645a4a35bcc4531bf46392318586a47502d631e01a849f0c598ad32612e939bf41255a65aac545eaa69e76d9b868b86ee352e7972cb242739217f6d
6
+ metadata.gz: dd73d5e07c490e333b57e2206b01619c341d9702a0dd9139d9d911a7bc306bf43062a60e0e85310adf025ecf5fc939cda514e1051dc069a5545af1a235baf395
7
+ data.tar.gz: f090c3154f0fb9c2846ee0e7ffb1333be42a2902cf65bcca4e9e2d6fda61d870ed08c4c9d94bd0d856d24ea66f58a6b1cfeba621262c4c5835a774fdf5ff2773
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/README.md CHANGED
@@ -1,3 +1,242 @@
1
1
  # Depth
2
2
 
3
+ Depth is a utility gem for deep manipulation of complex hashes, that
4
+ is nested hash and array structures. As you have probably guessed it
5
+ was originally created to deal with a JSON like document structure.
6
+ Importantly it uses a non-recursive approach to its enumeration.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'depth'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install depth
23
+
24
+ ## Usage
25
+
26
+ ### The complex hash
27
+
28
+ ```ruby
29
+ hash = { '$and' => [
30
+ { '#weather' => { 'something' => [], 'thisguy' => 4 } },
31
+ { '$or' => [
32
+ { '#otherfeed' => {'thing' => [] } },
33
+ ]}
34
+ ]}
35
+ ```
36
+ _Nicked from a query engine we're using on Driftrock_
37
+
38
+ The above is a sample complex hash, to use the gem to
39
+ start manipulating it is pretty simple:
40
+
41
+ ```ruby
42
+ complex_hash = Depth::ComplexHash.new(hash)
43
+ ```
44
+
45
+ Not exactly rocket science (not even data science). You
46
+ can retrieve the hash with either `base` or `to_h`.
47
+
48
+ ### Manipulation
49
+
50
+ Manipulation of the hash is done using routes. A route
51
+ being a description of how to traverse the hash to
52
+ get to this point.
53
+
54
+ The messages signatures relating to manipulation are:
55
+
56
+ * `set(route, value)` = Set a value
57
+ * `find(route)` = Find a value
58
+ * `alter(route, key:)` = Alter a key (the last key in route)
59
+ * `alter(route, value:)` = Alter a value, identical to `set`
60
+ * `alter(route, key: value:)` = Alter a key and value, identical to a `set` and then `delete`
61
+ * `delete(route)` = Delete a value
62
+
63
+ Routes can be defined as an array of keys or indeces:
64
+
65
+ ```ruby
66
+ hash = { '$and' => [
67
+ { '#weather' => { 'something' => [], 'thisguy' => 4 } },
68
+ { '$or' => [
69
+ { '#otherfeed' => {'thing' => [] } },
70
+ ]}
71
+ ]}
72
+ route = ['$and', 1, '$or', 0, '#otherfeed', 'thing']
73
+ Depth::ComplexHash.new(hash).find(route) # => []
74
+ ```
75
+
76
+ But there's something cool hidden in the `set` message,
77
+ if part of the structure is missing, it'll fill it in as it
78
+ goes, e.g.:
79
+
80
+ ```ruby
81
+ hash = { '$and' => [
82
+ { '#weather' => { 'something' => [], 'thisguy' => 4 } },
83
+ { '$or' => [
84
+ { '#otherfeed' => {'thing' => [] } },
85
+ ]}
86
+ ]}
87
+ route = ['$and', 1, '$or', 0, '#sup', 'thisthing']
88
+ Depth::ComplexHash.new(hash).set(route, 'hello')
89
+ puts hash.inspect #=>
90
+ # hash = { '$and' => [
91
+ # { '#weather' => { 'something' => [], 'thisguy' => 4 } },
92
+ # { '$or' => [
93
+ # { '#otherfeed' => {'thing' => [] } },
94
+ # { '#sup' => {'thisthing' => 'hello' } },
95
+ # ]}
96
+ # ]}
97
+ ```
98
+
99
+ Great if you want it to be a hash, but what if you want to add
100
+ an array, no worries, just say so in the route:
101
+
102
+ ```ruby
103
+ route = ['$and', 1, '$or', 0, ['#sup', :array], 0]
104
+ # Routes can also be defined in other ways
105
+ route = ['$and', 1, '$or', 0, { key: '#sup', type: :array }, 0]
106
+ route = ['$and', 1, '$or', 0, RouteElement.new('#sup', type: :array), 0]
107
+ ```
108
+
109
+ ### Enumeration
110
+
111
+ The messages signatures relating to enumeration are:
112
+
113
+ * `each` = yields `key_or_index` and `fragment`, returns the complex hash
114
+ * `map` = yields `key_or_index`, `fragment` and `parent_type`, returns a new complex hash
115
+ * `map_values` = yields `fragment`, returns a new complex hash
116
+ * `map_keys` = yields `key_or_index`, returns a new complex hash
117
+ * `map!`, `map_keys!` and `map_keys_and_values!`, returns a new complex hash
118
+ * `reduce(memo)` = yields `memo`, `key` and `fragment`, returns memo
119
+ * `each_with_object(obj)` = yields `key`, `fragment` and `object`, returns object
120
+
121
+ _Fragment refers to a chunk of the original hash_
122
+
123
+ These, perhaps, require a bit more explanation:
124
+
125
+ #### each
126
+
127
+ The staple, and arguably the most important, of all the enumeration methods,
128
+
129
+ ```ruby
130
+ hash = { ... }
131
+ Depth::ComplexHash.new(hash).each { |key, fragment| }
132
+ ```
133
+
134
+ Each yields keys and associated fragments from the leaf nodes
135
+ backwards. For example, the hash:
136
+
137
+ ```ruby
138
+ { '$and' => [{ 'something' => { 'x' => 4 } }] }
139
+ ```
140
+
141
+ would yield:
142
+
143
+ 1. `x, 4`
144
+ 2. `something, { "x" => 4 }`
145
+ 3. `0, { "something" => { "x" => 4 } }`
146
+ 4. `$and, [{ "something" => { "x" => 4 } }]`
147
+
148
+
149
+ #### map
150
+
151
+ Map yields both the current key/index and the current fragment,
152
+ expecting both returned in an array. I've yet to decide if
153
+ there should be a third argument that tells you whether or not
154
+ the key/index is for an array or a hash. I've not needed it
155
+ but I suspect it might be useful. If it comes up I'll add it.
156
+
157
+
158
+ ```ruby
159
+ hash = { '$and' => [{ 'something' => { 'x' => 4 } }] }
160
+ Depth::ComplexHash.new(hash).map do |key, fragment|
161
+ [key, fragment]
162
+ end
163
+ ```
164
+
165
+ like `each` the above would yield:
166
+
167
+ 1. `x, 4`
168
+ 2. `something, { "x" => 4 }`
169
+ 3. `0, { "something" => { "x" => 4 } }`
170
+ 4. `$and, [{ "something" => { "x" => 4 } }]`
171
+
172
+ and with the contents being unchanged it would return a
173
+ new complex hash with equal contents to the current one.
174
+
175
+ #### map_values
176
+
177
+ ```ruby
178
+ hash = { '$and' => [{ 'something' => { 'x' => 4 } }] }
179
+ Depth::ComplexHash.new(hash).map_values do |fragment|
180
+ fragment
181
+ end
182
+ ```
183
+
184
+ This will yield only the fragments from `map`, useful if
185
+ you only wish to alter the value parts of the hash.
186
+
187
+ #### map_keys
188
+
189
+ ```ruby
190
+ hash = { '$and' => [{ 'something' => { 'x' => 4 } }] }
191
+ Depth::ComplexHash.new(hash).map_keys do |key|
192
+ key
193
+ end
194
+ ```
195
+
196
+ This will yield only the keys from `map`, useful if
197
+ you only wish to alter the keys.
198
+
199
+ #### map!, map_keys!, map_values!
200
+
201
+ The same as their non-exclamation marked siblings save that
202
+ they will cause the complex hash on which they operate to change.
203
+
204
+ #### reduce and each_with_object
205
+
206
+ Operate as you would expect. Can I take a moment to point out how
207
+ irritating it is that `each_with_object` yields the object you pass
208
+ in as its last argument while `reduce` yields it as its first O_o?
209
+
210
+ ```ruby
211
+ hash = { '$and' => [{ 'something' => { 'x' => 4 } }] }
212
+ Depth::ComplexHash.new(hash).reduce(0) do |memo, key, fragment|
213
+ memo += 1
214
+ end
215
+
216
+ Depth::ComplexHash.new(hash).each_with_object([]) do |key, fragment, obj|
217
+ obj << key
218
+ end
219
+ ```
220
+
221
+ ## Why?
222
+
223
+ Alright, we needed to be able to find certain keys
224
+ from all the keys contained within the complex hash as said keys
225
+ were the instructions as to what data the hash would be able to match
226
+ against. This peice of code was originally recursive. We were adding
227
+ a feature that required us to also be able to edit these keys, mark
228
+ them with a unique identifier. As I was writing this I decided I wasn't
229
+ happy with the recursive nature of the key search as we have no guarantees
230
+ about how nested the hash could be. As I refactored the find and built
231
+ the edit it became obvious that the code wasn't tied to the project at
232
+ hand so I refactored it out to here.
233
+
234
+
235
+ ## Contributing
236
+
237
+ 1. Fork it ( https://github.com/[my-github-username]/depth/fork )
238
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
239
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
240
+ 4. Push to the branch (`git push origin my-new-feature`)
241
+ 5. Create a new Pull Request
3
242
 
data/lib/depth/actions.rb CHANGED
@@ -1,8 +1,14 @@
1
1
  module Depth
2
2
  module Actions
3
+ #:nocov:
4
+ def base
5
+ raise NoMethodError.new('should be overridden')
6
+ end
7
+ #:nocov:
8
+
3
9
  def set(route, value)
4
10
  route = RouteElement.convert_route(route)
5
- object = route[0 ... -1].reduce(Traverser.new(hsh)) { |t, route_el|
11
+ object = route[0 ... -1].reduce(Traverser.new(base)) { |t, route_el|
6
12
  t.next_or_create(route_el.key) { route_el.create }
7
13
  }.object
8
14
  object[route.last.key] = value
@@ -10,7 +16,7 @@ module Depth
10
16
 
11
17
  def find(route)
12
18
  route = RouteElement.convert_route(route)
13
- route.reduce(Traverser.new(hsh)) { |t, route_el|
19
+ route.reduce(Traverser.new(base)) { |t, route_el|
14
20
  t.next(route_el.key)
15
21
  }.object
16
22
  end
@@ -19,16 +25,16 @@ module Depth
19
25
  return set(route, value) if key == nil
20
26
  route = RouteElement.convert_route(route)
21
27
  value = find(route) unless value
22
- new_route = (route[0 ... -1] << RouteElement.convert(new_key))
28
+ new_route = (route[0 ... -1] << RouteElement.convert(key))
23
29
  set(new_route, value) # ensure it exists
24
30
  old_key = route.last.key
25
- return unless old_key != new_key
31
+ return unless old_key != key
26
32
  delete(route)
27
33
  end
28
34
 
29
35
  def delete(route)
30
36
  route = RouteElement.convert_route(route)
31
- traverser = route[0...-1].reduce(Traverser.new(hsh)) do |t, route_el|
37
+ traverser = route[0...-1].reduce(Traverser.new(base)) do |t, route_el|
32
38
  t.next(route_el.key)
33
39
  end
34
40
  if traverser.array?
@@ -1,8 +1,9 @@
1
1
  module Depth
2
2
  class ComplexHash
3
3
  include Depth::Actions
4
- include Depth::Enumerable
4
+ include Depth::Enumeration::Enumerable
5
5
  attr_reader :base
6
+ alias_method :to_h, :base
6
7
  def initialize(base = {})
7
8
  @base = base
8
9
  end
@@ -1,82 +1,92 @@
1
1
  module Depth
2
- module Enumerable
2
+ module Enumeration
3
+ module Enumerable
4
+ #:nocov:
5
+ def base
6
+ raise NoMethodError.new('should be overridden')
7
+ end
8
+ #:nocov:
3
9
 
4
- def each_with_object(object, &block)
5
- each do |key, fragment|
6
- block.call(key, fragment, object)
10
+ def each_with_object(object, &block)
11
+ object.tap do |o|
12
+ each do |key, fragment|
13
+ block.call(key, fragment, o)
14
+ end
15
+ end
7
16
  end
8
- object
9
- end
10
17
 
11
- def reduce(memo, &block)
12
- each do |key, fragment|
13
- memo = block.call(memo, key, fragment)
18
+ def reduce(memo, &block)
19
+ each do |key, fragment|
20
+ memo = block.call(memo, key, fragment)
21
+ end
22
+ memo
14
23
  end
15
- memo
16
- end
17
24
 
18
- def map_keys!(&block)
19
- @base = map_keys(&block).base
20
- end
25
+ def map_keys!(&block)
26
+ @base = map_keys(&block).base
27
+ self
28
+ end
21
29
 
22
- def map!(&block)
23
- @base = map(&block).base
24
- end
30
+ def map_values!(&block)
31
+ @base = map_values(&block).base
32
+ self
33
+ end
25
34
 
26
- def map_keys_and_values!(&block)
27
- @base = map_keys_and_values(&block).base
28
- end
35
+ def map!(&block)
36
+ @base = map(&block).base
37
+ self
38
+ end
39
+
40
+ def map_keys(&block)
41
+ map do |key, fragment|
42
+ [block.call(key), fragment]
43
+ end
44
+ end
29
45
 
30
- def map_keys(&block)
31
- map_keys_and_values do |key, fragment|
32
- [block.call(key), fragment]
46
+ def map_values(&block)
47
+ map do |key, fragment, parent_type|
48
+ [key, block.call(fragment)]
49
+ end
33
50
  end
34
- end
35
51
 
36
- # Convention is that only values are mapped
37
- def map(&block)
38
- map_keys_and_values do |key, fragment|
39
- [key, block.call(fragment)]
52
+ def map(&block)
53
+ node_map do |node, new_q|
54
+ orig_key = node.parent_key
55
+ existing = new_q.find(node.route)
56
+ orig_fragment = existing.nil? ? node.fragment : existing
57
+ block.call(orig_key, orig_fragment)
58
+ end
40
59
  end
41
- end
42
60
 
43
- def map_keys_and_values(&block)
44
- node_map do |node, new_q|
45
- orig_key = node.parent_key
46
- existing = new_q.find(node.route)
47
- orig_fragment = existing.nil? ? node.fragment : existing
48
- next [orig_key, orig_fragment] unless node.parent.hash?
49
- block.call(orig_key, orig_fragment)
61
+ def each(&block)
62
+ enumerate { |node| block.call(node.parent_key, node.fragment) }
50
63
  end
51
- end
52
64
 
53
- def each(&block)
54
- enumerate { |node| block.call(node.parent_key, node.fragment) }
55
- end
65
+ private
56
66
 
57
- private
67
+ def node_map(&block)
68
+ new_q = self.class.new(base.class.new)
69
+ enumerate do |node|
70
+ key, val = block.call(node, new_q)
71
+ new_q.alter(node.route, key: key, value: val)
72
+ end
73
+ new_q
74
+ end
58
75
 
59
- def node_map(&block)
60
- new_q = ComplexHash.new(base.class.new)
61
- enumerate do |node|
62
- key, val = block.call(node, new_q)
63
- new_q.alter(node.route, key: key, value: val)
76
+ def enumerate
77
+ root = Node.new(nil, nil, base)
78
+ current = root
79
+ begin
80
+ if current.next?
81
+ current = current.next
82
+ elsif !current.root?
83
+ yield(current)
84
+ current = current.parent
85
+ end
86
+ end while !current.root? || current.next?
87
+ self
64
88
  end
65
- new_q
66
- end
67
89
 
68
- def enumerate
69
- root = Node.new(nil, nil, query)
70
- current = root
71
- begin
72
- if current.next?
73
- current = current.next
74
- elsif !current.root?
75
- yield(current)
76
- current = current.parent
77
- end
78
- end while !current.root? || current.next?
79
- root.fragment
80
90
  end
81
91
  end
82
92
  end
@@ -9,7 +9,7 @@ module Depth
9
9
  route = []
10
10
  current = self
11
11
  while(!current.root?)
12
- route << RouteElement.new(current.parent_key, fragment_type)
12
+ route << RouteElement.new(current.parent_key, type: current.fragment_type)
13
13
  current = current.parent
14
14
  end
15
15
  route.reverse
@@ -22,9 +22,9 @@ module Depth
22
22
  case el
23
23
  when Array
24
24
  type = el.count > 1 ? el[1] : :hash
25
- RouteElement.new(route_el[0], type: type)
25
+ RouteElement.new(el[0], type: type)
26
26
  when Hash
27
- key_or_index = el.fetch(:key, el.fetch(:index))
27
+ key_or_index = el.fetch(:key) { el.fetch(:index) }
28
28
  RouteElement.new(key_or_index, type: el.fetch(:type, :hash))
29
29
  else
30
30
  RouteElement.new(el)
data/lib/depth/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Depth
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
data/lib/depth.rb CHANGED
@@ -1,4 +1,11 @@
1
- require 'depth/version'
1
+ require_relative './depth/version'
2
2
 
3
3
  module Depth
4
4
  end
5
+
6
+ require_relative './depth/enumeration/enumerable'
7
+ require_relative './depth/enumeration/node'
8
+ require_relative './depth/actions'
9
+ require_relative './depth/traverser'
10
+ require_relative './depth/route_element'
11
+ require_relative './depth/complex_hash'
@@ -0,0 +1,118 @@
1
+ require 'spec_helper'
2
+
3
+ module Depth
4
+ RSpec.describe Actions do
5
+
6
+ let(:actions_class) do
7
+ Class.new do
8
+ include Actions
9
+ attr_reader :base
10
+ def initialize(base)
11
+ @base = base
12
+ end
13
+ end
14
+ end
15
+
16
+ let(:hash) do
17
+ { '$and' => [
18
+ { '#weather' => { 'something' => [] } },
19
+ { '#weather' => { 'something' => [] } },
20
+ { '#weather' => { 'something' => [] } },
21
+ { '$or' => [
22
+ { '#otherfeed' => {'thing' => [] } },
23
+ ]}
24
+ ]}
25
+ end
26
+
27
+ subject { actions_class.new(hash) }
28
+
29
+ describe '#set' do
30
+ it 'should let me set an existing value' do
31
+ route = [['$and', :array], [0, :hash],
32
+ ['#weather', :hash], ['something', :array]]
33
+ expect do
34
+ subject.set(route, :test)
35
+ end.to change { hash['$and'][0]['#weather']['something'] }.to(:test)
36
+ end
37
+
38
+ it 'should let me set a new value' do
39
+ route = [['$rargh', :array], [0, :hash],
40
+ ['#weather', :hash], ['something', :array]]
41
+ subject.set(route, :test)
42
+ expect(hash['$rargh'][0]['#weather']['something']).to eq :test
43
+ end
44
+ end
45
+
46
+ describe '#delete' do
47
+ context 'when in a hash' do
48
+ let(:route) do
49
+ ['$and', 1, '#weather']
50
+ end
51
+
52
+ it 'should let me delete a route endpoint' do
53
+ expect do
54
+ subject.delete(route)
55
+ end.to change { hash['$and'][1].empty? }.to(true)
56
+ end
57
+ end
58
+
59
+ context 'when in an array' do
60
+ let(:route) do
61
+ ['$and', 1]
62
+ end
63
+
64
+ it 'should let me delete a route endpoint' do
65
+ expect do
66
+ subject.delete(route)
67
+ end.to change { hash['$and'].count }.by(-1)
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '#alter' do
73
+ let(:route) do
74
+ [['$and', :array], [0, :hash], ['#weather', :hash], ['something', :array]]
75
+ end
76
+
77
+ it 'should let me change a key' do
78
+ expect do
79
+ subject.alter(route, key: 'blah')
80
+ end.to change { hash['$and'][0]['#weather'].keys }.to ['blah']
81
+ end
82
+
83
+ it 'should let me change a value' do
84
+ expect do
85
+ subject.alter(route, value: 'rargh')
86
+ end.to change { hash['$and'][0]['#weather']['something'] }.to 'rargh'
87
+ end
88
+
89
+ context 'when changing key and value' do
90
+ it 'should set the new value' do
91
+ expect do
92
+ subject.alter(route, key: 'blah', value: 'rargh')
93
+ end.to change { hash['$and'][0]['#weather']['blah'] }.to 'rargh'
94
+ end
95
+
96
+ it 'should delete the old key' do
97
+ expect do
98
+ subject.alter(route, key: 'blah', value: 'rargh')
99
+ end.to change { hash['$and'][0]['#weather'].key?('something') }.to false
100
+ end
101
+ end
102
+ end
103
+
104
+ describe '#find' do
105
+ it 'should let me find an existing value' do
106
+ route = [['$and', :array], [0, :hash],
107
+ ['#weather', :hash], ['something', :array]]
108
+ expect(subject.find(route)).to eq []
109
+ end
110
+
111
+ it 'should return nil if element does not exist' do
112
+ route = [['$rargh', :array], [0, :hash],
113
+ ['#weather', :hash], ['something', :array]]
114
+ expect(subject.find(route)).to be_nil
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ module Depth
4
+ RSpec.describe ComplexHash do
5
+ let(:hash) do
6
+ { '$and' => [
7
+ { '#weather' => { 'something' => [] } },
8
+ { '#weather' => { 'something' => [] } },
9
+ { '#weather' => { 'something' => [] } },
10
+ { '$or' => [
11
+ { '#otherfeed' => {'thing' => [] } },
12
+ ]}
13
+ ]}
14
+ end
15
+
16
+ subject { described_class.new(hash) }
17
+
18
+ describe '#base' do
19
+ it 'should return the underlying hash' do
20
+ expect(subject.base).to eq hash
21
+ end
22
+ end
23
+
24
+ describe '#to_h' do
25
+ it 'should be aliased to base' do
26
+ expect(subject.to_h).to be subject.base
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,217 @@
1
+
2
+ require 'spec_helper'
3
+ require 'pry-byebug'
4
+
5
+ module Depth::Enumeration
6
+ RSpec.describe Enumerable do
7
+
8
+ let(:enumerable_class) do
9
+ Class.new do
10
+ include Depth::Actions
11
+ include Depth::Enumeration::Enumerable
12
+ attr_reader :base
13
+ def initialize(base)
14
+ @base = base
15
+ end
16
+ end
17
+ end
18
+
19
+ let(:hash) do
20
+ { '$and' => [
21
+ { '#weather' => { 'something' => [] } },
22
+ { '$or' => [
23
+ { '#otherfeed' => {'thing' => [] } },
24
+ ]}
25
+ ]}
26
+ end
27
+
28
+ subject { enumerable_class.new(hash) }
29
+
30
+ describe '#each_with_object' do
31
+ it "performs as you'd expect reduce to" do
32
+ keys = subject.each_with_object([]) do |key, fragment, obj|
33
+ obj << key if key.is_a?(String)
34
+ end
35
+ expected = ['something', '#weather', 'thing', '#otherfeed', '$or', '$and']
36
+ expect(keys).to eq expected
37
+ end
38
+ end
39
+
40
+ describe '#reduce' do
41
+ it "performs as you'd expect reduce to" do
42
+ keys = subject.reduce(0) do |sum, key, fragment|
43
+ sum += (key.is_a?(String) ? 1 : 0)
44
+ end
45
+ expect(keys).to eq 6
46
+ end
47
+ end
48
+
49
+ shared_examples 'it maps changing self' do
50
+ let(:map_block) { proc { |x| x } }
51
+ let(:alter_map_block) { proc { |x| 'rarg' } }
52
+
53
+ it 'should return self' do
54
+ result = subject.send(map_message, &map_block)
55
+ expect(result).to be subject
56
+ end
57
+
58
+ context 'with alteration' do
59
+ it 'should change base' do
60
+ expect do
61
+ subject.send(map_message, &alter_map_block)
62
+ end.to change { subject.base }
63
+ end
64
+ end
65
+
66
+ context 'without alteration' do
67
+ it 'should not change contents' do
68
+ expect do
69
+ subject.send(map_message, &map_block)
70
+ end.to_not change { subject.base }
71
+ end
72
+ end
73
+ end
74
+
75
+ describe '#map!' do
76
+ it_behaves_like 'it maps changing self' do
77
+ let(:map_message) { 'map!' }
78
+ let(:map_block) { proc { |x, y| [x, y] } }
79
+ let(:alter_map_block) do
80
+ proc { |k, v|
81
+ next [k, v] unless k.is_a?(String)
82
+ ["#{k}rargh", v]
83
+ }
84
+ end
85
+ end
86
+ end
87
+
88
+ describe '#map_keys!' do
89
+ it_behaves_like 'it maps changing self' do
90
+ let(:map_message) { 'map_keys!' }
91
+ let(:alter_map_block) do
92
+ proc { |k|
93
+ next k unless k.is_a?(String)
94
+ "#{k}Altered"
95
+ }
96
+ end
97
+ end
98
+ end
99
+
100
+ describe '#map_values!' do
101
+ it_behaves_like 'it maps changing self' do
102
+ let(:map_message) { 'map_values!' }
103
+ end
104
+ end
105
+
106
+ shared_examples 'it maps to a new object' do
107
+ let(:map_block) { proc { |x| x } }
108
+ it 'should return a new object' do
109
+ result = subject.send(map_message, &map_block)
110
+ expect(result).to_not be subject
111
+ end
112
+
113
+ it 'should return an object of the same class' do
114
+ result = subject.send(map_message, &map_block)
115
+ expect(result.class).to be subject.class
116
+ end
117
+
118
+ context 'without alteration' do
119
+ it 'should return an object with the same contents' do
120
+ result = subject.send(map_message, &map_block)
121
+ expect(result.base).to eq subject.base
122
+ end
123
+ end
124
+ end
125
+
126
+ describe '#map' do
127
+ it_behaves_like 'it maps to a new object' do
128
+ let(:map_message) { :map }
129
+ let(:map_block) { proc { |x, y| [x, y] } }
130
+ end
131
+
132
+ context 'with alteration' do
133
+ let(:result) do
134
+ subject.map do |k, v|
135
+ next [k, v] unless k.is_a?(String)
136
+ ["#{k}Altered", 'redacted']
137
+ end
138
+ end
139
+
140
+ it 'should differ in the expected fashion' do
141
+ expected = { "$andAltered" => 'redacted' }
142
+ expect(result.base).to eq expected
143
+ end
144
+ end
145
+ end
146
+
147
+ describe '#map_keys' do
148
+ it_behaves_like 'it maps to a new object' do
149
+ let(:map_message) { :map_keys }
150
+ end
151
+
152
+ context 'with alteration' do
153
+ let(:result) do
154
+ subject.map_keys do |k|
155
+ next k unless k.is_a?(String)
156
+ "#{k}Altered"
157
+ end
158
+ end
159
+
160
+ it 'should differ in the expected fashion' do
161
+ expected = { "$andAltered" => [
162
+ { "#weatherAltered" => { "somethingAltered" => [] } },
163
+ { "$orAltered" => [ { "#otherfeedAltered" => { "thingAltered" => [] } } ] } ]
164
+ }
165
+ expect(result.base).to eq expected
166
+ end
167
+ end
168
+ end
169
+
170
+ describe '#map_values' do
171
+ it_behaves_like 'it maps to a new object' do
172
+ let(:map_message) { :map_values }
173
+ end
174
+
175
+ context 'with alteration' do
176
+ let(:result) do
177
+ subject.map_values do |f|
178
+ 'altered' if f.is_a?(Hash)
179
+ end
180
+ end
181
+
182
+ it 'should differ in the expected fashion' do
183
+ expected = { "$and" => [ 'altered', 'altered' ] }
184
+ expect(result.base).to eq expected
185
+ end
186
+
187
+ it 'should return an object with the different contents' do
188
+ expect(result.base).to_not eq subject.base
189
+ end
190
+ end
191
+
192
+ end
193
+
194
+ describe '#each' do
195
+ it 'should sort through all keys and values' do
196
+ enumerated = []
197
+ subject.each do |key, fragment|
198
+ enumerated << [key, fragment]
199
+ end
200
+ expected = [
201
+ ['something', []],
202
+ ['#weather', { 'something' => [] }],
203
+ [0, { '#weather' => { 'something' => [] }}],
204
+ ['thing', []],
205
+ ['#otherfeed', { 'thing' => [] }],
206
+ [0, { '#otherfeed' => { 'thing' => [] } }],
207
+ ['$or', [{ '#otherfeed' => { 'thing' => [] } }]],
208
+ [1, { '$or' => [{ '#otherfeed' => { 'thing' => [] } }] }],
209
+ ['$and', [{ '#weather' => { 'something' => [] }},
210
+ { '$or' => [{ '#otherfeed' => { 'thing' => [] } }] }]]
211
+ ]
212
+ expect(enumerated).to eq expected
213
+ end
214
+ end
215
+
216
+ end
217
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ module Depth
4
+ RSpec.describe RouteElement do
5
+ describe '::convert' do
6
+ let(:el) { nil }
7
+ let(:result) { described_class.convert(el) }
8
+ context 'with any random thing' do
9
+ let(:el) { 4 }
10
+ it 'should set the type hash' do
11
+ expect(result.type).to eq :hash
12
+ end
13
+
14
+ it 'should set the key as the element' do
15
+ expect(result.key).to eq el
16
+ end
17
+ end
18
+
19
+ context 'with a hash' do
20
+ let(:el) { { key: 'x', type: :array } }
21
+
22
+ it 'should set the type as the passed type' do
23
+ expect(result.type).to eq el.fetch(:type)
24
+ end
25
+
26
+ it 'should set the key as the passed key' do
27
+ expect(result.key).to eq el.fetch(:key)
28
+ end
29
+
30
+ context 'with no type' do
31
+ let(:el) { { key: 'x' } }
32
+
33
+ it 'should set the type hash' do
34
+ expect(result.type).to eq :hash
35
+ end
36
+ end
37
+
38
+ context 'with index instead of key' do
39
+ let(:el) { { index: 'x', type: :array } }
40
+
41
+ it 'should set the key as the passed index' do
42
+ expect(result.key).to eq el.fetch(:index)
43
+ end
44
+ end
45
+ end
46
+
47
+ context 'with an array with' do
48
+ context 'with two elements' do
49
+ let(:el) { ['x', :array] }
50
+
51
+ it 'should set the type as the second element' do
52
+ expect(result.type).to eq el[1]
53
+ end
54
+
55
+ it 'should set the key as the first element' do
56
+ expect(result.key).to eq el[0]
57
+ end
58
+ end
59
+
60
+ context 'with one element' do
61
+ let(:el) { ['x'] }
62
+
63
+ it 'should set the type hash' do
64
+ expect(result.type).to eq :hash
65
+ end
66
+
67
+ it 'should set the key as the element' do
68
+ expect(result.key).to eq el[0]
69
+ end
70
+ end
71
+ end
72
+
73
+ context 'with a route element' do
74
+ let(:el) { RouteElement.new('x') }
75
+
76
+ it 'should return the element' do
77
+ expect(result).to be el
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,72 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+ require './lib/depth'
4
+ RSpec.configure do |config|
5
+ # rspec-expectations config goes here. You can use an alternate
6
+ # assertion/expectation library such as wrong or the stdlib/minitest
7
+ # assertions if you prefer.
8
+ config.expect_with :rspec do |expectations|
9
+ # This option will default to `true` in RSpec 4. It makes the `description`
10
+ # and `failure_message` of custom matchers include text for helper methods
11
+ # defined using `chain`, e.g.:
12
+ # be_bigger_than(2).and_smaller_than(4).description
13
+ # # => "be bigger than 2 and smaller than 4"
14
+ # ...rather than:
15
+ # # => "be bigger than 2"
16
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
17
+ end
18
+
19
+ # rspec-mocks config goes here. You can use an alternate test double
20
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
21
+ config.mock_with :rspec do |mocks|
22
+ # Prevents you from mocking or stubbing a method that does not exist on
23
+ # a real object. This is generally recommended, and will default to
24
+ # `true` in RSpec 4.
25
+ mocks.verify_partial_doubles = true
26
+ end
27
+
28
+ # The settings below are suggested to provide a good initial experience
29
+ # with RSpec, but feel free to customize to your heart's content.
30
+ =begin
31
+ # These two settings work together to allow you to limit a spec run
32
+ # to individual examples or groups you care about by tagging them with
33
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
34
+ # get run.
35
+ config.filter_run :focus
36
+ config.run_all_when_everything_filtered = true
37
+
38
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
39
+ # For more details, see:
40
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
41
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
42
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
43
+ config.disable_monkey_patching!
44
+
45
+ # Many RSpec users commonly either run the entire suite or an individual
46
+ # file, and it's useful to allow more verbose output when running an
47
+ # individual spec file.
48
+ if config.files_to_run.one?
49
+ # Use the documentation formatter for detailed output,
50
+ # unless a formatter has already been configured
51
+ # (e.g. via a command-line flag).
52
+ config.default_formatter = 'doc'
53
+ end
54
+
55
+ # Print the 10 slowest examples and example groups at the
56
+ # end of the spec run, to help surface which specs are running
57
+ # particularly slow.
58
+ config.profile_examples = 10
59
+
60
+ # Run specs in random order to surface order dependencies. If you find an
61
+ # order dependency and want to debug it, you can fix the order by providing
62
+ # the seed, which is printed after each run.
63
+ # --seed 1234
64
+ config.order = :random
65
+
66
+ # Seed global randomization in this process using the `--seed` CLI option.
67
+ # Setting this allows you to use `--seed` to deterministically reproduce
68
+ # test failures related to randomization by passing the same `--seed` value
69
+ # as the one that triggered the failure.
70
+ Kernel.srand config.seed
71
+ =end
72
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: depth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-18 00:00:00.000000000 Z
11
+ date: 2015-03-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -88,6 +88,7 @@ extensions: []
88
88
  extra_rdoc_files: []
89
89
  files:
90
90
  - ".gitignore"
91
+ - ".rspec"
91
92
  - Gemfile
92
93
  - LICENSE.txt
93
94
  - README.md
@@ -101,6 +102,11 @@ files:
101
102
  - lib/depth/route_element.rb
102
103
  - lib/depth/traverser.rb
103
104
  - lib/depth/version.rb
105
+ - spec/depth/actions_spec.rb
106
+ - spec/depth/complex_hash_spec.rb
107
+ - spec/depth/enumerable_spec.rb
108
+ - spec/depth/route_element_spec.rb
109
+ - spec/spec_helper.rb
104
110
  homepage: https://github.com/maxdupenois/depth
105
111
  licenses:
106
112
  - MIT
@@ -125,4 +131,9 @@ rubygems_version: 2.2.2
125
131
  signing_key:
126
132
  specification_version: 4
127
133
  summary: Depth is a utility gem for dealing with nested hashes and arrays
128
- test_files: []
134
+ test_files:
135
+ - spec/depth/actions_spec.rb
136
+ - spec/depth/complex_hash_spec.rb
137
+ - spec/depth/enumerable_spec.rb
138
+ - spec/depth/route_element_spec.rb
139
+ - spec/spec_helper.rb