hash-proxy 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.
- 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
|