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 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