depth 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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