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 CHANGED
@@ -1 +1 @@
1
- rvm gemset use hash-proxy
1
+ rvm gemset use hash-proxy --create
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'bones'
4
+ gem 'bones-rspec'
5
+ gem 'bones-git'
6
+ gem 'bones-yard'
7
+ gem 'redcarpet'
@@ -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
@@ -1,3 +1,6 @@
1
- == 0.0.1 / 2012-06-07
2
-
3
- * First release of hash-proxy
1
+ # == Version 0.0.1 / 2012-06-07
2
+ # * First release of hash-proxy
3
+ #
4
+ # == Version 0.0.2 / 2012-06-08
5
+ # * Convert to yard doc for "rake doc" task.
6
+ # * Handle respond_to? in a sensible fashion.
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::Proxy.new(hash)
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'
@@ -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
@@ -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[name_str] ||= convert_value(@hash.delete(name) || @hash.delete(name_str))
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
@@ -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
- proxy.should_not_receive(:convert_value)
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
@@ -1 +1 @@
1
- 0.0.1
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.1
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