diff --git a/lib/hashie/extensions/method_access.rb b/lib/hashie/extensions/method_access.rb index 75374d7f..50ee46c4 100644 --- a/lib/hashie/extensions/method_access.rb +++ b/lib/hashie/extensions/method_access.rb @@ -31,6 +31,7 @@ def respond_to?(name) return true if key?(name.to_s) || key?(name.to_sym) super end + def method_missing(name, *args) return self[name.to_s] if key?(name.to_s) return self[name.to_sym] if key?(name.to_sym) @@ -38,18 +39,80 @@ def method_missing(name, *args) end end + # MethodWriter gives you #key_name= shortcuts for + # writing to your hash. Keys are written as strings, + # override #convert_key if you would like to have symbols + # or something else. + # + # Note that MethodWriter also overrides #respond_to such + # that any #method_name= will respond appropriately as true. + # + # @example + # class MyHash < Hash + # include Hashie::Extensions::MethodWriter + # end + # + # h = MyHash.new + # h.awesome = 'sauce' + # h['awesome'] # => 'sauce' + # module MethodWriter + def respond_to?(name) + return true if name.to_s =~ /=$/ + super + end + def method_missing(name, *args) + if args.size == 1 && name.to_s =~ /(.*)=$/ + return self[convert_key($1)] = args.first + end + super + end + + def convert_key(key) + key.to_s end end + # MethodQuery gives you the ability to check for the truthiness + # of a key via method calls. Note that it will return false if + # the key is set to a non-truthful value, not if the key isn't + # set at all. Use #key? for checking if a key has been set. + # + # MethodQuery will check against both string and symbol names + # of the method for existing keys. It also patches #respond_to + # to appropriately detect the query methods. + # + # @example + # class MyHash < Hash + # include Hashie::Extensions::MethodQuery + # end + # + # h = MyHash.new + # h['abc'] = 123 + # h.abc? # => true + # h['def'] = nil + # h.def? # => false + # h.hji? # => NoMethodError module MethodQuery + def respond_to?(name) + return true if name.to_s =~ /(.*)\?$/ && (key?($1) || key?($1.to_sym)) + super + end + def method_missing(name, *args) + if args.empty? && name.to_s =~ /(.*)\?$/ && (key?($1) || key?($1.to_sym)) + return self[$1] || self[$1.to_sym] + end + super end end + # A macro module that will automatically include MethodReader, + # MethodWriter, and MethodQuery, giving you the ability to read, + # write, and query keys in a hash using method call shortcuts. module MethodAccess def self.included(base) [MethodReader, MethodWriter, MethodQuery].each do |mod| diff --git a/spec/hashie/extensions/method_access_spec.rb b/spec/hashie/extensions/method_access_spec.rb index 9b3b5426..f77fa002 100644 --- a/spec/hashie/extensions/method_access_spec.rb +++ b/spec/hashie/extensions/method_access_spec.rb @@ -39,3 +39,74 @@ def initialize(hash = {}); self.update(hash) end end end end + +describe Hashie::Extensions::MethodWriter do + class WriterHash < Hash + include Hashie::Extensions::MethodWriter + end + subject{ WriterHash.new } + + it 'should write from a method call' do + subject.awesome = 'sauce' + subject['awesome'].should == 'sauce' + end + + it 'should convert the key using the #convert_key method' do + subject.stub!(:convert_key).and_return(:awesome) + subject.awesome = 'sauce' + subject[:awesome].should == 'sauce' + end + + it 'should still NoMethodError on non equals-ending methods' do + lambda{ subject.awesome }.should raise_error(NoMethodError) + end + + it 'should #respond_to? properly' do + subject.should be_respond_to(:abc=) + subject.should_not be_respond_to(:abc) + end +end + +describe Hashie::Extensions::MethodQuery do + class QueryHash < Hash + def initialize(hash = {}); self.update(hash) end + include Hashie::Extensions::MethodQuery + end + subject{ QueryHash } + + it 'should be true for non-nil string key values' do + subject.new('abc' => 123).should be_abc + end + + it 'should be true for non-nil symbol key values' do + subject.new(:abc => 123).should be_abc + end + + it 'should be false for nil key values' do + subject.new(:abc => false).should_not be_abc + end + + it 'should raise a NoMethodError for non-set keys' do + lambda{ subject.new.abc? }.should raise_error(NoMethodError) + end + + it 'should respond_to? for existing string keys' do + subject.new('abc' => 'def').should be_respond_to('abc?') + end + + it 'should respond_to? for existing symbol keys' do + subject.new(:abc => 'def').should be_respond_to(:abc?) + end + + it 'should not respond_to? for non-existent keys' do + subject.new.should_not be_respond_to('abc?') + end +end + +describe Hashie::Extensions::MethodAccess do + it 'should include all of the other method mixins' do + klass = Class.new(Hash) + klass.send :include, Hashie::Extensions::MethodAccess + (klass.ancestors & [Hashie::Extensions::MethodReader, Hashie::Extensions::MethodWriter, Hashie::Extensions::MethodQuery]).size.should == 3 + end +end