hash-proxy 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +1 -1
- data/Gemfile +7 -0
- data/Gemfile.lock +40 -0
- data/History.txt +6 -3
- data/README.md +22 -3
- data/Rakefile +6 -1
- data/lib/hash_proxy.rb +15 -1
- data/lib/hash_proxy/null_object.rb +19 -0
- data/lib/hash_proxy/proxy.rb +51 -1
- data/spec/hash_proxy_spec.rb +25 -1
- data/version.txt +1 -1
- metadata +35 -1
data/.rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm gemset use hash-proxy
|
1
|
+
rvm gemset use hash-proxy --create
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
bones (3.8.0)
|
5
|
+
little-plugger (~> 1.1.3)
|
6
|
+
loquacious (~> 1.9.1)
|
7
|
+
rake (>= 0.8.7)
|
8
|
+
bones-git (1.3.0)
|
9
|
+
bones (>= 3.6)
|
10
|
+
git (>= 1.2.5)
|
11
|
+
bones-rspec (2.0.1)
|
12
|
+
bones (>= 3.6)
|
13
|
+
rspec (>= 1.3)
|
14
|
+
bones-yard (1.0.0)
|
15
|
+
yard (>= 0.5.8)
|
16
|
+
diff-lcs (1.1.3)
|
17
|
+
git (1.2.5)
|
18
|
+
little-plugger (1.1.3)
|
19
|
+
loquacious (1.9.1)
|
20
|
+
rake (0.9.2.2)
|
21
|
+
redcarpet (2.1.1)
|
22
|
+
rspec (2.10.0)
|
23
|
+
rspec-core (~> 2.10.0)
|
24
|
+
rspec-expectations (~> 2.10.0)
|
25
|
+
rspec-mocks (~> 2.10.0)
|
26
|
+
rspec-core (2.10.1)
|
27
|
+
rspec-expectations (2.10.0)
|
28
|
+
diff-lcs (~> 1.1.3)
|
29
|
+
rspec-mocks (2.10.1)
|
30
|
+
yard (0.8.2)
|
31
|
+
|
32
|
+
PLATFORMS
|
33
|
+
ruby
|
34
|
+
|
35
|
+
DEPENDENCIES
|
36
|
+
bones
|
37
|
+
bones-git
|
38
|
+
bones-rspec
|
39
|
+
bones-yard
|
40
|
+
redcarpet
|
data/History.txt
CHANGED
data/README.md
CHANGED
@@ -8,25 +8,44 @@ Features
|
|
8
8
|
--------
|
9
9
|
|
10
10
|
* Treat Hash as an object
|
11
|
+
* Lazily turns nested Hash objects into proxies as they are referenced
|
12
|
+
* TODO: Lazily turn array elements into proxies. Currently, if an array
|
13
|
+
is referenced, all of it's elements that are Hashes are turned into
|
14
|
+
proxies and what's worse, if the Array contains sub-arrays, all the
|
15
|
+
nested sub Array objects recursively have their own elements converted
|
16
|
+
to proxies. This can have bad performance implications:
|
17
|
+
|
18
|
+
h = {:foo => [{:key1 => 'val1'}, {:key1 => 'val2'}, ... MANY MORE ..., {:key1 => 'val2'}]}
|
19
|
+
p = HashProxy.create_from(h)
|
20
|
+
# Even though we only look at the first two objects in the Array,
|
21
|
+
# we silently convert all of the Array's contained Hash objects to
|
22
|
+
# HashProxy::Proxy instances just by referencing the foo key.
|
23
|
+
p.foo[0..1]
|
11
24
|
|
12
25
|
Examples
|
13
26
|
--------
|
14
27
|
|
15
28
|
require 'hash-proxy'
|
16
29
|
hash = {foo: 'bar', baz: [{key: 'value', key2: 2, key3: [1,2,3]}, {key: 'value2', key2: 22, key3: [4,5,6]}], bip: 'bop'}
|
17
|
-
proxy = HashProxy
|
18
|
-
proxy.baz.key3.should == [4,5,6]
|
30
|
+
proxy = HashProxy.create_from(hash)
|
31
|
+
proxy.baz.last.key3.should == [4,5,6]
|
19
32
|
|
20
33
|
Requirements
|
21
34
|
------------
|
22
35
|
|
23
|
-
* No dependencies
|
36
|
+
* No runtime dependencies
|
37
|
+
* To build and test, run bundle install after cloning from github
|
24
38
|
|
25
39
|
Install
|
26
40
|
-------
|
27
41
|
|
28
42
|
* gem install hash-proxy
|
29
43
|
|
44
|
+
Source
|
45
|
+
------
|
46
|
+
|
47
|
+
https://github.com/seifertd/hash-proxy
|
48
|
+
|
30
49
|
Motivation
|
31
50
|
----------
|
32
51
|
|
data/Rakefile
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
begin
|
3
3
|
require 'bones'
|
4
4
|
rescue LoadError
|
5
|
-
abort '### Please install the "bones" gem ###'
|
5
|
+
abort '### Please install the "bones" gem by doing a "bundle install" ###'
|
6
6
|
end
|
7
7
|
|
8
8
|
task :default => 'spec:run'
|
@@ -16,7 +16,12 @@ Bones {
|
|
16
16
|
depend_on 'bones', :development => true
|
17
17
|
depend_on 'bones-rspec', :development => true
|
18
18
|
depend_on 'bones-git', :development => true
|
19
|
+
depend_on 'bones-yard', :development => true
|
20
|
+
depend_on 'redcarpet', :development => true
|
21
|
+
|
22
|
+
yard.exclude ['version.txt']
|
19
23
|
}
|
20
24
|
|
21
25
|
require 'bones/plugins/rspec'
|
22
26
|
require 'bones/plugins/git'
|
27
|
+
require 'bones/plugins/yard'
|
data/lib/hash_proxy.rb
CHANGED
@@ -1,4 +1,12 @@
|
|
1
|
-
|
1
|
+
# Wrapper of Hash that allows method call semantics to hash keys and values.
|
2
|
+
# Returns a NullObject if a key does not exist that responds to any method call.
|
3
|
+
# Allows convenience like this
|
4
|
+
#
|
5
|
+
# require 'hash_proxy'
|
6
|
+
# proxy = HashProxy.create_from({:foo => :bar, :baz => :bip, :stuff => {:nested => :more}})
|
7
|
+
# puts proxy.stuff.nested
|
8
|
+
# puts proxy.this.does.not.exist (to_s implemented to return empty string here)
|
9
|
+
#
|
2
10
|
module HashProxy
|
3
11
|
|
4
12
|
# :stopdoc:
|
@@ -6,6 +14,12 @@ module HashProxy
|
|
6
14
|
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
7
15
|
VERSION = ::File.read(PATH + 'version.txt').strip
|
8
16
|
# :startdoc:
|
17
|
+
|
18
|
+
# Creates a new instance of a HashProxy::Proxy from the provided hash.
|
19
|
+
# Convenience factory method
|
20
|
+
def self.create_from(hash)
|
21
|
+
HashProxy::Proxy.new(hash)
|
22
|
+
end
|
9
23
|
|
10
24
|
# Returns the library path for the module. If any arguments are given,
|
11
25
|
# they will be joined to the end of the libray path using
|
@@ -1,12 +1,31 @@
|
|
1
1
|
module HashProxy
|
2
|
+
# Class representing a non-existing key in a proxied
|
3
|
+
# hash. Returns self in response to any message. Returns
|
4
|
+
# sensible values for various to_* methods. But it
|
5
|
+
# is not 'false-y' ... ;)
|
2
6
|
class NullObject
|
7
|
+
# Returns an empty array
|
3
8
|
def to_a; []; end
|
9
|
+
|
10
|
+
# Returns an empty string
|
4
11
|
def to_s; ""; end
|
12
|
+
|
13
|
+
# Returns 0.0
|
5
14
|
def to_f; 0.0; end
|
15
|
+
|
16
|
+
# Returns 0
|
6
17
|
def to_i; 0; end
|
18
|
+
|
19
|
+
# Always true
|
7
20
|
def nil?; true; end
|
21
|
+
|
22
|
+
# Always true
|
8
23
|
def blank?; true; end
|
24
|
+
|
25
|
+
#Always true
|
9
26
|
def empty?; true; end
|
27
|
+
|
28
|
+
# Returns self in response to any message
|
10
29
|
def method_missing(*args)
|
11
30
|
self
|
12
31
|
end
|
data/lib/hash_proxy/proxy.rb
CHANGED
@@ -1,13 +1,31 @@
|
|
1
1
|
module HashProxy
|
2
|
+
# Class that wraps a hash and converts message sends to
|
3
|
+
# hash lookups and sets. If sent a message ending with '=',
|
4
|
+
# sets a value. Otherwise, returns the value in the
|
5
|
+
# hash at the key corresponding to the message. If the
|
6
|
+
# key does not exist, returns a NullObject instance.
|
2
7
|
class Proxy
|
8
|
+
# The one and only NullObject instance
|
3
9
|
NO_OBJECT = NullObject.new
|
10
|
+
|
11
|
+
# Used to check if a setter is being called
|
4
12
|
EQUALS = '='.freeze
|
5
13
|
|
14
|
+
# Create a hashProxy::Proxy object from the provided Hash.
|
15
|
+
#
|
16
|
+
# @param [Hash] hash The hash we are proxying
|
17
|
+
# @option options [Boolean] :lazy (true) If true, values in the hash are converted
|
18
|
+
# to hash proxies only when requested via
|
19
|
+
# a method call. If false, all nested Hash
|
20
|
+
# and Array objects are converted to Proxy
|
21
|
+
# objects at creation time
|
6
22
|
def initialize(hash, options = {})
|
7
23
|
@hash = hash
|
8
24
|
@converted = {}
|
9
25
|
end
|
10
26
|
|
27
|
+
# The magic. Turns arbitrary method invocations into
|
28
|
+
# lookups or sets on the contained hash
|
11
29
|
def method_missing(name, *args)
|
12
30
|
name_str = name.to_s
|
13
31
|
if name_str.end_with?(EQUALS)
|
@@ -15,10 +33,42 @@ module HashProxy
|
|
15
33
|
else
|
16
34
|
# Move the value from the original hash to the converted hash.
|
17
35
|
# Support both symbol or string keys
|
18
|
-
@converted
|
36
|
+
if @converted.has_key?(name_str)
|
37
|
+
@converted[name_str]
|
38
|
+
else
|
39
|
+
unconverted = @hash.delete(name) || @hash.delete(name_str)
|
40
|
+
converted = convert_value(unconverted)
|
41
|
+
@converted[name_str] = converted
|
42
|
+
end
|
19
43
|
end
|
20
44
|
end
|
21
45
|
|
46
|
+
# Try to do the right thing. Indicate that this object
|
47
|
+
# responds to any getter or setter method with a corresponding
|
48
|
+
# key in either the original or converted hash. This can lead to
|
49
|
+
# possibly confusing behavior:
|
50
|
+
#
|
51
|
+
# p = Proxy.new(:foo => :bar)
|
52
|
+
# p.respond_to?(:stuff) => false
|
53
|
+
# p.stuff => NullObject
|
54
|
+
# p.respond_to?(:stuff) => true
|
55
|
+
#
|
56
|
+
def respond_to?(method)
|
57
|
+
method_name = method.to_s
|
58
|
+
method_name.gsub(/=$/, '')
|
59
|
+
(@hash.keys.map(&:to_s) + @converted.keys.map(&:to_s)).include?(method_name) || super
|
60
|
+
end
|
61
|
+
|
62
|
+
# Converts an arbitrary object as follows:
|
63
|
+
#
|
64
|
+
# 1. A Proxy instance if the object is an instance of Hash
|
65
|
+
# 2. If the object is an array, all elements of the array
|
66
|
+
# that are hashes are converted to Proxy objects.
|
67
|
+
# Nested arrays are handled recursivley.
|
68
|
+
# 3. Non array or hash objects are left alone
|
69
|
+
# 4. nil is replaced by Proxy::NO_VALUE
|
70
|
+
#
|
71
|
+
# @param [Object] value The value to convert
|
22
72
|
def convert_value(value)
|
23
73
|
case value
|
24
74
|
when Array
|
data/spec/hash_proxy_spec.rb
CHANGED
@@ -41,7 +41,8 @@ describe HashProxy do
|
|
41
41
|
it "should not convert values if nothing is referenced" do
|
42
42
|
hash = {'foo' => 'bar', 'arr1' => [1,2,3], 'baz' => 'bip'}
|
43
43
|
proxy = HashProxy::Proxy.new(hash)
|
44
|
-
|
44
|
+
HashProxy::Proxy.should_not_receive(:convert_value)
|
45
|
+
proxy.to_s
|
45
46
|
end
|
46
47
|
|
47
48
|
it "should convert values if they are referenced" do
|
@@ -51,4 +52,27 @@ describe HashProxy do
|
|
51
52
|
proxy.foo.should eq('bar')
|
52
53
|
end
|
53
54
|
|
55
|
+
it "can be created using the factory method" do
|
56
|
+
hash = {foo: 'bar', baz: 'bip'}
|
57
|
+
proxy = HashProxy.create_from(hash)
|
58
|
+
proxy.foo.should eq('bar')
|
59
|
+
end
|
60
|
+
|
61
|
+
it "handles keys that do not exist" do
|
62
|
+
hash = {foo: 'bar', baz: 'bip'}
|
63
|
+
proxy = HashProxy.create_from(hash)
|
64
|
+
proxy.key.does.not.exist.to_s.should eq('')
|
65
|
+
proxy.key.does.not.exist.nil?.should eq(true)
|
66
|
+
proxy.key.does.not.exist.blank?.should eq(true)
|
67
|
+
proxy.key.does.not.exist.empty?.should eq(true)
|
68
|
+
proxy.key.does.not.exist.to_a.should eq([])
|
69
|
+
end
|
70
|
+
|
71
|
+
# TODO: Am I itching badly enough to make this pass? ....
|
72
|
+
#it "should lazily handle nested arrays" do
|
73
|
+
# hash = {:foo => [{:key1 => 'val11', :key2 => 'val12'}, {:key1 => 'val21', :key2 => 'val22'}], :bar => :baz}
|
74
|
+
# proxy = HashProxy.create_from(hash)
|
75
|
+
# proxy.foo.should eq([{:key1 => 'val11', :key2 => 'val12'}, {:key1 => 'val21', :key2 => 'val22'}])
|
76
|
+
#end
|
77
|
+
|
54
78
|
end
|
data/version.txt
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.2
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hash-proxy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -59,6 +59,38 @@ dependencies:
|
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: 1.3.0
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: bones-yard
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.0.0
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.0.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: redcarpet
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 2.1.1
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 2.1.1
|
62
94
|
- !ruby/object:Gem::Dependency
|
63
95
|
name: bones
|
64
96
|
requirement: !ruby/object:Gem::Requirement
|
@@ -87,6 +119,8 @@ files:
|
|
87
119
|
- .bnsignore
|
88
120
|
- .gitignore
|
89
121
|
- .rvmrc
|
122
|
+
- Gemfile
|
123
|
+
- Gemfile.lock
|
90
124
|
- History.txt
|
91
125
|
- README.md
|
92
126
|
- Rakefile
|