key_path 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5bc45e3830cd4c5a9bbf20765cf382874a890927
4
+ data.tar.gz: fa82506141b7515dbc9b6f19cbf99f1dffb9f272
5
+ SHA512:
6
+ metadata.gz: 5f1817be54a72fce97109507cb259f1a19392efb7adc6dc4e492ee6c986126c869c6a387ac753b80ef3b604f8051813c19f372592f4c93f6f2827778d8daeeeb
7
+ data.tar.gz: 4d1aa2f3f87fb7973c6ac2f3296516d70bc38d6ddbbd9436d45a3e4dff60bccceed8c0f84af2e4e88121bdfe64e5998ca2c228bcfa5ac2300aa91c01422a2d31
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in keypath.gemspec
4
+ gemspec
5
+
6
+ gem 'rake'
7
+ gem 'coveralls', require: false
8
+
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Nick Charlton
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # keypath-based collection access extensions for Ruby.
2
+
3
+ [![Build Status](https://secure.travis-ci.org/nickcharlton/keypath-ruby.png?branch=master)](http://travis-ci.org/nickcharlton/keypath-ruby) [![Code Climate](https://codeclimate.com/github/nickcharlton/keypath-ruby.png)](https://codeclimate.com/github/nickcharlton/keypath-ruby) [![Test Coverage](https://coveralls.io/repos/nickcharlton/keypath-ruby/badge.png?branch=master)](https://coveralls.io/r/nickcharlton/keypath-ruby)
4
+
5
+ This gem allows you to access nested Ruby collections (`Hash`, `Array`, etc) using
6
+ keypaths.
7
+
8
+ For example, say you had a nested data structure like:
9
+
10
+ ```ruby
11
+ data = {
12
+ :item_one => {:id => 1, :url => 'http://nickcharlton.net/'},
13
+ :something_else => [
14
+ {
15
+ :id => 1,
16
+ :url => 'https://github.com/'
17
+ }
18
+ ]
19
+ }
20
+ ```
21
+
22
+ You could access "https://github.com/" through: `something_else.0.url`. Basically,
23
+ this is intended to allow you to manipulate/transform large nested structures that
24
+ you might get back from a JSON document.
25
+
26
+ ## Installation
27
+
28
+ Add this line to your application's Gemfile:
29
+
30
+ gem 'keypath'
31
+
32
+ And then execute:
33
+
34
+ $ bundle
35
+
36
+ Or install it yourself as:
37
+
38
+ $ gem install keypath
39
+
40
+ ## Usage
41
+
42
+ `KeyPath` is at least two things. First, it's a class (actually, `KeyPath::Path`)
43
+ which represents a path (this is just a string, and has methods to go back and
44
+ forth from it) and secondly a set of class extensions for `Enumerable`, `Hash` and
45
+ `String` which allow you to use the native collection classes with keypaths.
46
+
47
+ ```ruby
48
+ require 'key_path'
49
+
50
+ data = {
51
+ :item => {
52
+ :url => 'http://nickcharlton.net'
53
+ }
54
+ }
55
+
56
+ # fetching a path
57
+ path = KeyPath::Path.new 'item.url'
58
+ data.value_at_keypath(path) #=> 'http://nickcharlton.net'
59
+
60
+ # finding all `:url` paths in a collection
61
+ data.keypaths_for_nested_key(:url) #=> {item.url => 'http://nickcharlton.net'}
62
+
63
+ # going back and forth from a string
64
+ path.to_s #=> 'item.url'
65
+ 'item.url'.to_keypath #=> #<KeyPath:70096895112220 path=item.url>
66
+
67
+ # get the parent of a keypath (or nil, if there isn't one)
68
+ path.parent #=> #<KeyPath:70096895221110 path=item>
69
+
70
+ # setting a path
71
+ data.set_keypath(path, 'http://github.com/')
72
+ ```
73
+
74
+ ## Contributing
75
+
76
+ 1. Fork it
77
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
78
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
79
+ 4. Push to the branch (`git push origin my-new-feature`)
80
+ 5. Create new Pull Request
81
+
82
+ ## Author
83
+
84
+ Copyright (c) 2013 Nick Charlton (<nick@nickcharlton.net>)
85
+
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ ##
2
+ # Initialise Bundler, catch errors.
3
+ ##
4
+ require 'bundler'
5
+ require 'bundler/gem_tasks'
6
+
7
+ begin
8
+ Bundler.setup(:default, :development)
9
+ rescue Bundler::BundlerError => e
10
+ $stderr.puts e.message
11
+ $stderr.puts "Run `bundle install` to install missing gems."
12
+ exit e.status_code
13
+ end
14
+
15
+ ##
16
+ # Configure the test suite.
17
+ ##
18
+ require 'rake/testtask'
19
+
20
+ Rake::TestTask.new :spec do |t|
21
+ t.test_files = Dir['spec/*_spec.rb']
22
+ end
23
+
24
+ ##
25
+ # By default, just run the tests.
26
+ ##
27
+ task :default => :spec
28
+
data/key_path.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'key_path/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'key_path'
8
+ spec.version = KeyPath::VERSION
9
+ spec.authors = ['Nick Charlton']
10
+ spec.email = ['nick@nickcharlton.net']
11
+ spec.description = %q{Keypath-based collection access extensions for Ruby.}
12
+ spec.summary = %q{This gem allows you to access nested Ruby collections
13
+ (Hash, Array, etc) using keypaths. E.g.: 'something.item.0'}
14
+ spec.homepage = 'https://github.com/nickcharlton/keypath-ruby'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency 'activesupport', '~> 4.0'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.3'
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'pry'
27
+ end
@@ -0,0 +1,52 @@
1
+ module Enumerable
2
+ # see: http://stackoverflow.com/a/7139631/83386
3
+ def value_at_keypath(keypath)
4
+ if keypath.is_a?(KeyPath::Path)
5
+ keypath = keypath.to_s
6
+ end
7
+
8
+ parts = keypath.split '.', 2
9
+
10
+ # if it's an array, call the index
11
+ if self[parts[0].to_i]
12
+ match = self[parts[0].to_i]
13
+ else
14
+ match = self[parts[0]] || self[parts[0].to_sym]
15
+ end
16
+
17
+ if !parts[1] or match.nil?
18
+ return match
19
+ else
20
+ return match.value_at_keypath(parts[1])
21
+ end
22
+ end
23
+
24
+ def set_keypath(keypath, value)
25
+ # handle both string and KeyPath::Path forms
26
+ if keypath.is_a?(String)
27
+ keypath = keypath.to_keypath
28
+ end
29
+
30
+ # create a collection at the keypath
31
+ collection = keypath.to_collection
32
+
33
+ # set the value in the collection
34
+ depth = ''
35
+ keypath.to_a.each do |e|
36
+ # walk down set and make up the right place to assign
37
+ if e.is_number?
38
+ key = "#{e}"
39
+ else
40
+ key = ":#{e}"
41
+ end
42
+ depth << "[#{key}]"
43
+ end
44
+
45
+ # assign it
46
+ eval "collection#{depth} = #{value}"
47
+
48
+ # merge the new collection into self
49
+ self.deep_merge!(collection)
50
+ end
51
+ end
52
+
@@ -0,0 +1,73 @@
1
+ #
2
+ # = Hash Deep Merge
3
+ #
4
+ # Merges a Ruby Hash recursively, Also known as deep merge.
5
+ # Recursive version of Hash#merge and Hash#merge!.
6
+ #
7
+ # Category:: Ruby
8
+ # Package:: Hash
9
+ # Author:: Simone Carletti <weppos@weppos.net>
10
+ # Copyright:: 2007-2008 The Authors
11
+ # License:: MIT License
12
+ # Link:: http://www.simonecarletti.com/
13
+ # Source:: https://gist.github.com/weppos/6391
14
+ #
15
+ module KeyPath
16
+ module HashDeepMerge
17
+ #
18
+ # Recursive version of Hash#merge!
19
+ #
20
+ # Adds the contents of +other_hash+ to +hsh+,
21
+ # merging entries in +hsh+ with duplicate keys with those from +other_hash+.
22
+ #
23
+ # Compared with Hash#merge!, this method supports nested hashes.
24
+ # When both +hsh+ and +other_hash+ contains an entry with the same key,
25
+ # it merges and returns the values from both arrays.
26
+ #
27
+ # h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
28
+ # h2 = {"b" => 254, "c" => 300, "c" => {"c1" => 16, "c3" => 94}}
29
+ # h1.rmerge!(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
30
+ #
31
+ # Simply using Hash#merge! would return
32
+ #
33
+ # h1.merge!(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
34
+ #
35
+ def deep_merge!(other_hash)
36
+ merge!(other_hash) do |key, oldval, newval|
37
+ oldval.class == self.class ? oldval.deep_merge!(newval) : newval
38
+ end
39
+ end
40
+
41
+ #
42
+ # Recursive version of Hash#merge
43
+ #
44
+ # Compared with Hash#merge!, this method supports nested hashes.
45
+ # When both +hsh+ and +other_hash+ contains an entry with the same key,
46
+ # it merges and returns the values from both arrays.
47
+ #
48
+ # Compared with Hash#merge, this method provides a different approch
49
+ # for merging nasted hashes.
50
+ # If the value of a given key is an Hash and both +other_hash+ abd +hsh
51
+ # includes the same key, the value is merged instead replaced with
52
+ # +other_hash+ value.
53
+ #
54
+ # h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
55
+ # h2 = {"b" => 254, "c" => 300, "c" => {"c1" => 16, "c3" => 94}}
56
+ # h1.rmerge(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
57
+ #
58
+ # Simply using Hash#merge would return
59
+ #
60
+ # h1.merge(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
61
+ #
62
+ def deep_merge(other_hash)
63
+ r = {}
64
+ merge(other_hash) do |key, oldval, newval|
65
+ r[key] = oldval.class == self.class ? oldval.deep_merge(newval) : newval
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ class Hash
72
+ include KeyPath::HashDeepMerge
73
+ end
@@ -0,0 +1,30 @@
1
+ module KeyPath
2
+ module HashExtensions
3
+ def keypaths_for_nested_key(nested_key = '', nested_hash=self, path=[], all_values={})
4
+ nested_hash.each do |k, v|
5
+ path << k.to_s # assemble the path from the key
6
+ case v
7
+ when Array then
8
+ v.each_with_index do |item, i|
9
+ path << "#{i}" # add the array key
10
+ keypaths_for_nested_key(nested_key, item, path, all_values)
11
+ end
12
+ path.pop # remove the array key
13
+ when Hash then keypaths_for_nested_key(nested_key, v, path, all_values)
14
+ else
15
+ if k == nested_key
16
+ all_values.merge!({"#{path.join('.')}" => "#{v}"})
17
+ end
18
+ path.pop
19
+ end
20
+ end
21
+ path.pop
22
+
23
+ return all_values
24
+ end
25
+ end
26
+ end
27
+
28
+ class Hash
29
+ include KeyPath::HashExtensions
30
+ end
@@ -0,0 +1,59 @@
1
+ module KeyPath
2
+ class Path
3
+ def initialize(path='')
4
+ @path = path
5
+ end
6
+
7
+ def parent
8
+ s = self.to_a
9
+ s.pop
10
+
11
+ # there's no parent if it's empty
12
+ if s == []
13
+ return nil
14
+ end
15
+
16
+ # otherwise, join them back together and pass back a path
17
+ s.join('.').to_keypath
18
+ end
19
+
20
+ def to_s
21
+ @path
22
+ end
23
+
24
+ def to_a
25
+ @path.split('.')
26
+ end
27
+
28
+ def to_collection
29
+ collection = {}
30
+ s = self.to_a
31
+ depth = ''
32
+
33
+ s.each_with_index do |e, i|
34
+ # assemble the key
35
+ if e.is_number?
36
+ key = "#{e}"
37
+ else
38
+ key = ":#{e}"
39
+ end
40
+ depth << "[#{key}]"
41
+
42
+ # figure out the correct type to push
43
+ type = {}
44
+ if e.is_plural?
45
+ type = []
46
+ end
47
+
48
+ # evaluate this stage
49
+ eval "collection#{depth} = #{type}"
50
+ end
51
+
52
+ collection
53
+ end
54
+
55
+ def inspect
56
+ "#<#{self.class.name}:#{self.object_id} path=#{@path}>"
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,25 @@
1
+ require 'active_support/inflector'
2
+
3
+ module KeyPath
4
+ module StringExtensions
5
+ def to_keypath
6
+ KeyPath::Path.new self
7
+ end
8
+
9
+ def is_singular?
10
+ self.pluralize != self and self.singularize == self
11
+ end
12
+
13
+ def is_plural?
14
+ self.singularize != self and self.pluralize == self
15
+ end
16
+
17
+ def is_number?
18
+ true if Float(self) rescue false
19
+ end
20
+ end
21
+ end
22
+
23
+ class String
24
+ include KeyPath::StringExtensions
25
+ end
@@ -0,0 +1,3 @@
1
+ module KeyPath
2
+ VERSION = '1.0.0'
3
+ end
data/lib/key_path.rb ADDED
@@ -0,0 +1,14 @@
1
+ # gem bits
2
+ require 'key_path/version'
3
+ require 'key_path/path'
4
+
5
+ # extensions
6
+ require 'key_path/hash/deep_merge'
7
+ require 'key_path/hash/extensions'
8
+ require 'key_path/enumerable/extensions'
9
+ require 'key_path/string/extensions'
10
+
11
+
12
+ module KeyPath
13
+ # Your code goes here...
14
+ end
@@ -0,0 +1,88 @@
1
+ # test helpers
2
+ require File.expand_path 'spec_helper.rb', __dir__
3
+
4
+ describe 'EnumerableExtensions' do
5
+ it 'adds methods to collections' do
6
+ hash = {:id => 1}
7
+
8
+ hash.must_respond_to 'value_at_keypath'
9
+ hash.must_respond_to 'set_keypath'
10
+ end
11
+
12
+ it 'can fetch simple path values' do
13
+ hash = {:id => 1}
14
+
15
+ hash.value_at_keypath('id').must_equal 1
16
+ end
17
+
18
+ it 'can accept KeyPath::Path objects for keypaths' do
19
+ hash = {:id => 1}
20
+ keypath = KeyPath::Path.new('id')
21
+
22
+ hash.value_at_keypath(keypath).must_equal 1
23
+ end
24
+
25
+
26
+ it 'can fetch with a nested hash key path' do
27
+ hash = {:item => {:id => 1}}
28
+
29
+ hash.value_at_keypath('item.id').must_equal 1
30
+ end
31
+
32
+ it 'can fetch a nested array object' do
33
+ hash = {:items => [{:id => 1}]}
34
+
35
+ hash.value_at_keypath('items.0').must_equal({:id => 1})
36
+ end
37
+
38
+ it 'can set a simple value using a keypath string' do
39
+ source = {:item => {:id => {}}}
40
+ keypath = 'item.id'
41
+ value = 1
42
+
43
+ source.set_keypath(keypath, value)
44
+
45
+ source.value_at_keypath(keypath).must_equal(value)
46
+ end
47
+
48
+ it 'can set a simple value using a KeyPath::Path object' do
49
+ source = {:item => {:id => {}}}
50
+ keypath = KeyPath::Path.new('item.id')
51
+ value = 1
52
+
53
+ source.set_keypath(keypath, value)
54
+
55
+ source.value_at_keypath(keypath).must_equal(value)
56
+ end
57
+
58
+ it 'can set a string value using a KeyPath::Path object' do
59
+ source = {:item => {:id => {}}}
60
+ keypath = KeyPath::Path.new('item.id')
61
+ value = 'value'
62
+
63
+ source.set_keypath(keypath, value)
64
+
65
+ source.value_at_keypath(keypath).must_equal(value)
66
+ end
67
+
68
+
69
+ it 'can set a hash for a path' do
70
+ source = {:item => {:id => {}}}
71
+ keypath = KeyPath::Path.new('item')
72
+ value = {:id => 1}
73
+
74
+ source.set_keypath(keypath, value)
75
+
76
+ source.value_at_keypath(keypath).must_equal(value)
77
+ end
78
+
79
+ it 'can set a value in a nested array' do
80
+ source = {:items => [{:id => 1}]}
81
+ keypath = KeyPath::Path.new('items.1')
82
+ value = {:id => 2}
83
+
84
+ source.set_keypath(keypath, value)
85
+
86
+ source.value_at_keypath(keypath).must_equal(value)
87
+ end
88
+ end
@@ -0,0 +1,58 @@
1
+ # test helpers
2
+ require File.expand_path 'spec_helper.rb', __dir__
3
+
4
+ describe 'HashDeepMerge' do
5
+ it 'adds methods to the Hash class' do
6
+ hash = {:id => 1}
7
+
8
+ hash.must_respond_to 'deep_merge'
9
+ hash.must_respond_to 'deep_merge!'
10
+ end
11
+
12
+ it 'recursively adds two hashes' do
13
+ one = {:one => {:id => 1}}
14
+ two = {:one => {:url => 'http://nickcharlton.net'}}
15
+ three = {:one => {:id => 1, :url => 'http://nickcharlton.net'}}
16
+
17
+ output = one.deep_merge(two)
18
+
19
+ output.must_be_kind_of Hash
20
+ output.must_equal three
21
+ end
22
+
23
+ it 'recursively adds two hashes in place' do
24
+ one = {:one => {:id => 1}}
25
+ two = {:one => {:url => 'http://nickcharlton.net'}}
26
+ three = {:one => {:id => 1, :url => 'http://nickcharlton.net'}}
27
+
28
+ one.deep_merge!(two)
29
+
30
+ one.must_be_kind_of Hash
31
+ one.must_equal three
32
+ end
33
+ end
34
+
35
+ describe 'HashNormalMerge' do
36
+ it 'combines nested hashes without decending into them' do
37
+ one = {'a' => 100, 'b' => 200, 'c' => {'c1' => 12, 'c2' => 14}}
38
+ two = {'b' => 254, 'c' => 300, 'c' => {'c1' => 16, 'c3' => 94}}
39
+ expected = {'a' => 100, 'b' => 254, 'c' => {'c1' => 16, 'c3' => 94}}
40
+
41
+ output = one.merge(two)
42
+
43
+ output.must_be_kind_of Hash
44
+ output.must_equal expected
45
+ end
46
+
47
+ it 'combines nested hashes without decending into them in place' do
48
+ one = {'a' => 100, 'b' => 200, 'c' => {'c1' => 12, 'c2' => 14}}
49
+ two = {'b' => 254, 'c' => 300, 'c' => {'c1' => 16, 'c3' => 94}}
50
+ expected = {'a' => 100, 'b' => 254, 'c' => {'c1' => 16, 'c3' => 94}}
51
+
52
+ one.merge!(two)
53
+
54
+ one.must_be_kind_of Hash
55
+ one.must_equal expected
56
+ end
57
+
58
+ end
@@ -0,0 +1,64 @@
1
+ # test helpers
2
+ require File.expand_path 'spec_helper.rb', __dir__
3
+
4
+ describe 'HashExtensions' do
5
+ it 'adds methods to the Hash class' do
6
+ hash = {:id => 1}
7
+
8
+ hash.must_respond_to 'keypaths_for_nested_key'
9
+ end
10
+
11
+ it 'can find keys in a nested hash' do
12
+ data = {
13
+ :id => 1,
14
+ :item => {
15
+ :id => 2,
16
+ :name => 'an item'
17
+ }
18
+ }
19
+
20
+ keypaths = data.keypaths_for_nested_key(:id)
21
+
22
+ keypaths.wont_be_nil
23
+ keypaths.must_include 'id'
24
+ keypaths.must_include 'item.id'
25
+ end
26
+
27
+ it 'can find keys in nested array' do
28
+ data = {
29
+ :id => 1,
30
+ :items => [{
31
+ :id => 2,
32
+ :name => 'an item'
33
+ },
34
+ {
35
+ :id => 3,
36
+ :name => 'another item'
37
+ }]
38
+ }
39
+
40
+ keypaths = data.keypaths_for_nested_key(:id)
41
+
42
+ keypaths.wont_be_nil
43
+ keypaths.must_include 'id'
44
+ keypaths.must_include 'items.0.id'
45
+ keypaths.must_include 'items.1.id'
46
+ end
47
+
48
+ it 'can handle walking into an otherwise unknown object' do
49
+ class ExampleClass
50
+ def initialize
51
+ @name = 'Example Class'
52
+ end
53
+
54
+ example = ExampleClass.new
55
+
56
+ data = {:items => example}
57
+
58
+ keypaths = data.keypaths_for_nested_key(:items)
59
+
60
+ keypaths.wont_be_nil
61
+ keypaths.must_include 'items'
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,3 @@
1
+ # test helpers
2
+ require File.expand_path 'spec_helper.rb', __dir__
3
+
data/spec/path_spec.rb ADDED
@@ -0,0 +1,67 @@
1
+ # test helpers
2
+ require File.expand_path 'spec_helper.rb', __dir__
3
+
4
+ describe 'KeyPath::Path main methods' do
5
+ it 'creates a KeyPath instance from a string' do
6
+ path = KeyPath::Path.new('item.url')
7
+
8
+ path.wont_be_nil
9
+ path.to_s.must_be_kind_of String
10
+ path.to_s.must_equal 'item.url'
11
+ end
12
+
13
+ it 'outputs an array of items in the path' do
14
+ path = KeyPath::Path.new('item.url')
15
+
16
+ path.to_a.must_be_kind_of Array
17
+ path.to_a.must_equal ['item', 'url']
18
+ end
19
+
20
+ it 'spits out a useful inspect string' do
21
+ path = KeyPath::Path.new('item.url')
22
+
23
+ path.inspect.must_be_kind_of String
24
+ path.inspect.wont_be_nil
25
+ end
26
+
27
+ it 'returns a parent if one exists' do
28
+ path = KeyPath::Path.new('item.url')
29
+
30
+ path.parent.wont_be_nil
31
+ path.parent.must_be_kind_of KeyPath::Path
32
+ end
33
+
34
+ it 'returns nil if a parent doesnt exist' do
35
+ path = KeyPath::Path.new('item')
36
+
37
+ path.parent.must_be_nil
38
+ end
39
+ end
40
+
41
+ describe 'KeyPath::Path collections generation' do
42
+ it 'returns an empty hash with an empty path' do
43
+ path = KeyPath::Path.new('')
44
+ path.to_collection.must_equal({})
45
+ end
46
+
47
+ it 'returns a nested hash for a single path unit' do
48
+ path = KeyPath::Path.new('item')
49
+ path.to_collection.must_equal({:item => {}})
50
+ end
51
+
52
+ it 'returns a nested array when the key is plural' do
53
+ path = KeyPath::Path.new('items')
54
+ path.to_collection.must_equal({:items => []})
55
+ end
56
+
57
+ it 'returns a double nested array with a two set keypath' do
58
+ path = KeyPath::Path.new('item.id')
59
+ path.to_collection.must_equal({:item => {:id => {}}})
60
+ end
61
+
62
+ it 'returns a nested array with an item' do
63
+ path = KeyPath::Path.new('items.0.id')
64
+ path.to_collection.must_equal({:items => [{:id => {}}]})
65
+ end
66
+ end
67
+
@@ -0,0 +1,13 @@
1
+ # test coverage
2
+ require 'coveralls'
3
+
4
+ # enable coveralls
5
+ Coveralls.wear!
6
+
7
+ # test framework
8
+ require 'minitest/autorun'
9
+ require 'minitest/pride'
10
+
11
+ # pull in the library
12
+ require File.expand_path '../lib/key_path.rb', __dir__
13
+
@@ -0,0 +1,41 @@
1
+ # test helpers
2
+ require File.expand_path 'spec_helper.rb', __dir__
3
+
4
+ describe 'StringExtensions' do
5
+ it 'can create a KeyPath instance from itself' do
6
+ example_string = 'item.url'
7
+
8
+ example_string.to_keypath.wont_be_nil
9
+ example_string.to_keypath.must_be_kind_of KeyPath::Path
10
+
11
+ # around and around we go
12
+ example_string.to_keypath.to_s.must_equal example_string
13
+ end
14
+
15
+ it 'can make a set of strings plural' do
16
+ %w(word rail dress business).each do |v|
17
+ v.is_plural?.must_equal false
18
+ end
19
+
20
+ %w(words rails dresses businesses).each do |v|
21
+ v.is_plural?.must_equal true
22
+ end
23
+ end
24
+
25
+ it 'can make a set of string singular' do
26
+ %w(word rail dress business).each do |v|
27
+ v.is_singular?.must_equal true
28
+ end
29
+
30
+ %w(words rails dresses businesses).each do |v|
31
+ v.is_singular?.must_equal false
32
+ end
33
+ end
34
+
35
+ it 'can test if a string is actually a number' do
36
+ '0'.is_number?.must_equal true
37
+ '1234567890'.is_number?.must_equal true
38
+
39
+ 'item'.is_number?.must_equal false
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: key_path
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Nick Charlton
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-19 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.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Keypath-based collection access extensions for Ruby.
70
+ email:
71
+ - nick@nickcharlton.net
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .coveralls.yml
77
+ - .gitignore
78
+ - .travis.yml
79
+ - Gemfile
80
+ - LICENSE
81
+ - README.md
82
+ - Rakefile
83
+ - key_path.gemspec
84
+ - lib/key_path.rb
85
+ - lib/key_path/enumerable/extensions.rb
86
+ - lib/key_path/hash/deep_merge.rb
87
+ - lib/key_path/hash/extensions.rb
88
+ - lib/key_path/path.rb
89
+ - lib/key_path/string/extensions.rb
90
+ - lib/key_path/version.rb
91
+ - spec/enumerable_extensions_spec.rb
92
+ - spec/hash_deep_merge_spec.rb
93
+ - spec/hash_extensions_spec.rb
94
+ - spec/key_path_spec.rb
95
+ - spec/path_spec.rb
96
+ - spec/spec_helper.rb
97
+ - spec/string_extensions_spec.rb
98
+ homepage: https://github.com/nickcharlton/keypath-ruby
99
+ licenses:
100
+ - MIT
101
+ metadata: {}
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 2.0.14
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: 'This gem allows you to access nested Ruby collections (Hash, Array, etc)
122
+ using keypaths. E.g.: ''something.item.0'''
123
+ test_files:
124
+ - spec/enumerable_extensions_spec.rb
125
+ - spec/hash_deep_merge_spec.rb
126
+ - spec/hash_extensions_spec.rb
127
+ - spec/key_path_spec.rb
128
+ - spec/path_spec.rb
129
+ - spec/spec_helper.rb
130
+ - spec/string_extensions_spec.rb
131
+ has_rdoc: