deep_tree 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 28279e6c4a07b1a3884589b8f9129676c542ed70
4
+ data.tar.gz: 390b491664e74b1b1c071e66e41a1bb49f7dc169
5
+ SHA512:
6
+ metadata.gz: 4dc3c17bd5e7655e65f9a3b4e91f6696b1c702398215db52b6aeff98fd230620db98299309aaadc7a584317edad32ef77e2b766f03948c4a158c1b1d167912e1
7
+ data.tar.gz: 99157c2776fb9b21ffd341986471c4559ec36cdf332d03e4e5df80c65ace140836877dab38b9ed13fd14d76479d35fecdea4b9b4ba276beeba81ff4de4d2bc8d
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/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in deep_tree.gemspec
4
+ gemspec
5
+
6
+ group :benchmarks do
7
+ gem 'hashie'
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Patrick Tulskie
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,84 @@
1
+ # DeepTree
2
+
3
+ DeepTree is a simple class that loops through nodes in nested hashes to find a node that you're looking for. It's built to alleviate this:
4
+
5
+ tons_of_hashes['first_node']['second_node']['third_node'] rescue nil
6
+
7
+ Not only does this mask other issues in your code, but it is also super slow especially if you're digging through the same tree structure several times in a given app.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'deep_tree'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install deep_tree
22
+
23
+ ## Usage
24
+
25
+ There are three ways to use DeepTree depending on your needs. The first way is to simply use an instance:
26
+
27
+ tree = DeepTree.new(tons_of_hashes)
28
+ tree.get_leaf('first_node', 'second_node', 'third_node')
29
+
30
+ Another way to use it is by extending the `Hash` class.
31
+
32
+ require 'deep_tree/hash'
33
+ tons_of_hashes.get_leaf('first_node', 'second_node', 'third_node')
34
+
35
+ Many people try not to reopen Ruby's base classes. If you want this functionality on the `Hash` class then you can load it up. If you don't then you don't have to.
36
+
37
+ Finally, you can just use the class method like this:
38
+
39
+ DeepTree.get_leaf(tons_of_hashes, 'first_node', 'second_node')
40
+
41
+ Additionally, `DeepTree::get_leaf` supports evaluating a block if the result is `nil`.
42
+
43
+ DeepTree.get_leaf(tons_of_hashes, 'first_node', 'second_node') do
44
+ logger.info('That leaf couldn't be found.')
45
+ nil
46
+ end
47
+
48
+ Both the instance and hash extension all use `DeepTree::get_leaf` so they support passing a block as well.
49
+
50
+ ## A Caveat
51
+
52
+ All nodes except for the final leaf must be hashes in order for this to work. DeepTree will return nil for anything else.
53
+
54
+ ## Benchmarks
55
+
56
+ These test were run on a MacBook Pro Retina 2.7ghz w/ 16 GB RAM. Each test was run 200k times. Benchmark files are in the benchmarks directory.
57
+
58
+ Good Path
59
+ user system total real
60
+ DeepTree::get_leaf 0.260000 0.110000 0.370000 ( 0.375624)
61
+ Hash#get_leaf 0.500000 0.130000 0.630000 ( 0.622437)
62
+ DeepTree#get_leaf 0.600000 0.030000 0.630000 ( 0.630710)
63
+ Hash[] w/ rescue 0.080000 0.010000 0.090000 ( 0.094365)
64
+ Hashie::Mash 4.390000 0.240000 4.630000 ( 4.617386)
65
+
66
+ Bad Path
67
+ user system total real
68
+ DeepTree::get_leaf 0.210000 0.120000 0.330000 ( 0.328618)
69
+ Hash#get_leaf 0.530000 0.150000 0.680000 ( 0.679037)
70
+ DeepTree#get_leaf 0.620000 0.030000 0.650000 ( 0.646792)
71
+ Hash[] w/ rescue 0.510000 0.050000 0.560000 ( 0.567455)
72
+ Hashie::Mash 6.260000 0.400000 6.660000 ( 6.744009)
73
+
74
+ So, in the case where the whole path is good, simply chaining together hash calls is the clear winner (but if that was always the case, you wouldn't be here right now). In the event that the path is broken somewhere along the line, DeepTree's solution is at least 2x faster for this extremely contrived example. Benchmarks are in the benchmarks directory for your viewing pleasure.
75
+
76
+ I recently added Hashie::Mash to the mix here since everyone I showed this to told me that I should try Hashie::Mash. I have had lots of performance and memory issues with Hashie::Mash in the past and as of the latest version (2.0.5) it doesn't seem to be any better.
77
+
78
+ ## Contributing
79
+
80
+ 1. Fork it
81
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
82
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
83
+ 4. Push to the branch (`git push origin my-new-feature`)
84
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.pattern = "spec/*_spec.rb"
6
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'benchmark_helper'
2
+
3
+ bad_path = %w(three_level nope nada)
4
+
5
+ puts "Bad Path"
6
+ Benchmark.bm do |x|
7
+ x.report('DeepTree::get_leaf') do
8
+ $iterations.times { DeepTree.get_leaf($hash, *bad_path) }
9
+ end
10
+ x.report('Hash#get_leaf ') do
11
+ $iterations.times { $hash.get_leaf($hash, *bad_path) }
12
+ end
13
+ x.report('DeepTree#get_leaf ') do
14
+ $iterations.times { tree = DeepTree.new($hash); tree.get_leaf($hash, *bad_path) }
15
+ end
16
+ x.report('Hash[] w/ rescue ') do
17
+ $iterations.times { $hash['three_level']['nope']['nada'] rescue nil }
18
+ end
19
+ x.report('Hashie::Mash ') do
20
+ $iterations.times { Hashie::Mash.new($hash).three_level!.nope!.nada }
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'benchmark'
4
+ require_relative '../lib/deep_tree'
5
+ require_relative '../lib/deep_tree/hash'
6
+
7
+ Bundler.require :benchmarks
8
+
9
+ $iterations = 200_000
10
+
11
+ $hash = {
12
+ 'one_level' => 'SUCCESS',
13
+ 'three_level' => {
14
+ 'level_two' => {
15
+ 'level_one' => 'HOORAY'
16
+ }
17
+ }
18
+ }
19
+
20
+ GC.disable
@@ -0,0 +1,22 @@
1
+ require_relative 'benchmark_helper'
2
+
3
+ good_path = %w(three_level level_two level_one)
4
+
5
+ puts "Good Path"
6
+ Benchmark.bm do |x|
7
+ x.report('DeepTree::get_leaf') do
8
+ $iterations.times { DeepTree.get_leaf($hash, *good_path) }
9
+ end
10
+ x.report('Hash#get_leaf ') do
11
+ $iterations.times { $hash.get_leaf($hash, *good_path) }
12
+ end
13
+ x.report('DeepTree#get_leaf ') do
14
+ $iterations.times { tree = DeepTree.new($hash); tree.get_leaf($hash, *good_path) }
15
+ end
16
+ x.report('Hash[] w/ rescue ') do
17
+ $iterations.times { $hash['three_level']['level_two']['level_one'] rescue nil }
18
+ end
19
+ x.report('Hashie::Mash ') do
20
+ $iterations.times { Hashie::Mash.new($hash).three_level.level_two.level_one }
21
+ end
22
+ end
data/deep_tree.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'deep_tree/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "deep_tree"
8
+ gem.version = DeepTree::VERSION
9
+ gem.authors = ["Patrick Tulskie"]
10
+ gem.email = ["patricktulskie@gmail.com"]
11
+ gem.description = %q{DeepTree simplifies fetching deeply nested nodes in Ruby hashes.}
12
+ gem.summary = %q{Aids with finding deeply nested nodes in Ruby hashes.}
13
+ gem.homepage = "https://github.com/patricktulskie/deep_tree"
14
+ gem.licenses = %w(MIT)
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ["lib"]
20
+
21
+ gem.add_development_dependency('minitest')
22
+ end
data/lib/deep_tree.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative 'deep_tree/deep_tree'
@@ -0,0 +1,41 @@
1
+ class DeepTree
2
+
3
+ Error = Class.new(StandardError)
4
+ InvalidTreeError = Class.new(Error)
5
+
6
+ attr_accessor :tree
7
+
8
+ def self.get_leaf(parent, *args)
9
+ args.each_with_index do |key, index|
10
+ result = case
11
+ when index == (args.length - 1)
12
+ return parent[key].nil? && block_given? ? yield : parent[key]
13
+ when parent[key].is_a?(Hash)
14
+ parent = parent[key]
15
+ else
16
+ return block_given? ? yield : nil
17
+ end
18
+ end
19
+ end
20
+
21
+ def initialize(tree)
22
+ if tree.kind_of?(Hash)
23
+ self.tree = tree
24
+ else
25
+ raise InvalidTreeError, "Expected a kind of Hash but got an instance of #{tree.class}"
26
+ end
27
+ end
28
+
29
+ def get_leaf(*args)
30
+ if block_given?
31
+ DeepTree.get_leaf(tree, *args) { yield }
32
+ else
33
+ DeepTree.get_leaf(tree, *args)
34
+ end
35
+ end
36
+
37
+ def method_missing(*args)
38
+ self.tree(args)
39
+ end
40
+
41
+ end
@@ -0,0 +1,11 @@
1
+ class Hash
2
+
3
+ def get_leaf(*args)
4
+ if block_given?
5
+ DeepTree.get_leaf(self, *args) { yield }
6
+ else
7
+ DeepTree.get_leaf(self, *args)
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,3 @@
1
+ class DeepTree
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,60 @@
1
+ require_relative "spec_helper"
2
+
3
+ describe DeepTree do
4
+
5
+ before do
6
+ @result_data = {
7
+ 'one_level' => 'SUCCESS',
8
+ 'three_level' => {
9
+ 'level_two' => {
10
+ 'level_one' => 'HOORAY'
11
+ }
12
+ }
13
+ }
14
+ end
15
+
16
+ describe :class do
17
+
18
+ it "raises an exception for invalid inputs" do
19
+ proc { DeepTree.new(Array.new) }.must_raise(DeepTree::InvalidTreeError)
20
+ end
21
+
22
+ it "gets a leaf node" do
23
+ DeepTree.get_leaf(@result_data, 'three_level', 'level_two', 'level_one').must_equal 'HOORAY'
24
+ end
25
+
26
+ end
27
+
28
+ describe :instance do
29
+
30
+ before(:each) do
31
+ @deep_tree = DeepTree.new(@result_data)
32
+ end
33
+
34
+ it "gets a single level's node" do
35
+ @deep_tree.get_leaf('one_level').must_equal 'SUCCESS'
36
+ end
37
+
38
+ it "gets a deeply nested node" do
39
+ @deep_tree.get_leaf('three_level', 'level_two', 'level_one').must_equal 'HOORAY'
40
+ end
41
+
42
+ it "responds with nil for a shallow missing node" do
43
+ @deep_tree.get_leaf('missing').must_be_nil
44
+ end
45
+
46
+ it "responds with nil for a deep missing node" do
47
+ @deep_tree.get_leaf('three_level', 'level_two', 'missing').must_be_nil
48
+ end
49
+
50
+ it "responds with nil when it encounters a non-hash in the path" do
51
+ @deep_tree.get_leaf('three_level', 'level_two', 'level_one', 'level_zero').must_be_nil
52
+ end
53
+
54
+ it "evaluates a block instead of returning nil if a block is given" do
55
+ @deep_tree.get_leaf('nope') { 'potato' }.must_equal 'potato'
56
+ end
57
+
58
+ end
59
+
60
+ end
data/spec/hash_spec.rb ADDED
@@ -0,0 +1,48 @@
1
+ require_relative "spec_helper"
2
+
3
+ describe Hash do
4
+
5
+ describe :instance do
6
+
7
+ before(:each) do
8
+ @result_data = {
9
+ 'one_level' => 'SUCCESS',
10
+ 'three_level' => {
11
+ 'level_two' => {
12
+ 'level_one' => 'HOORAY'
13
+ }
14
+ }
15
+ }
16
+ end
17
+
18
+ it "responds to get_leaf" do
19
+ @result_data.respond_to?(:get_leaf).must_equal true
20
+ end
21
+
22
+ it "gets a single level's node" do
23
+ @result_data.get_leaf('one_level').must_equal 'SUCCESS'
24
+ end
25
+
26
+ it "gets a deeply nested node" do
27
+ @result_data.get_leaf('three_level', 'level_two', 'level_one').must_equal 'HOORAY'
28
+ end
29
+
30
+ it "responds with nil for a shallow missing node" do
31
+ @result_data.get_leaf('missing').must_be_nil
32
+ end
33
+
34
+ it "responds with nil for a deep missing node" do
35
+ @result_data.get_leaf('three_level', 'level_two', 'missing').must_be_nil
36
+ end
37
+
38
+ it "responds with nil when it encounters a non-hash in the path" do
39
+ @result_data.get_leaf('three_level', 'level_two', 'level_one', 'level_zero').must_be_nil
40
+ end
41
+
42
+ it "evaluates a block instead of returning nil if a block is given" do
43
+ @result_data.get_leaf('nope') { 'potato' }.must_equal 'potato'
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'minitest/spec'
3
+ require 'minitest/autorun'
4
+ require_relative '../lib/deep_tree'
5
+ require_relative '../lib/deep_tree/hash'
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deep_tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Patrick Tulskie
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: DeepTree simplifies fetching deeply nested nodes in Ruby hashes.
28
+ email:
29
+ - patricktulskie@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - .gitignore
35
+ - Gemfile
36
+ - LICENSE.txt
37
+ - README.md
38
+ - Rakefile
39
+ - benchmarks/bad_path.rb
40
+ - benchmarks/benchmark_helper.rb
41
+ - benchmarks/good_path.rb
42
+ - deep_tree.gemspec
43
+ - lib/deep_tree.rb
44
+ - lib/deep_tree/deep_tree.rb
45
+ - lib/deep_tree/hash.rb
46
+ - lib/deep_tree/version.rb
47
+ - spec/deep_tree_spec.rb
48
+ - spec/hash_spec.rb
49
+ - spec/spec_helper.rb
50
+ homepage: https://github.com/patricktulskie/deep_tree
51
+ licenses:
52
+ - MIT
53
+ metadata: {}
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project:
70
+ rubygems_version: 2.0.3
71
+ signing_key:
72
+ specification_version: 4
73
+ summary: Aids with finding deeply nested nodes in Ruby hashes.
74
+ test_files:
75
+ - spec/deep_tree_spec.rb
76
+ - spec/hash_spec.rb
77
+ - spec/spec_helper.rb