Permalink
Browse files

Adds IndifferentAccess.

  • Loading branch information...
1 parent 82295cc commit 3bd79790bcfa51c7d39a71a644ef1b0e74ced591 Michael Bleigh committed Aug 2, 2011
Showing with 183 additions and 8 deletions.
  1. +1 −0 .yardopts
  2. +16 −6 README.markdown
  3. +104 −1 lib/hashie/extensions/indifferent_access.rb
  4. +62 −1 spec/hashie/extensions/indifferent_access_spec.rb
View
@@ -0,0 +1 @@
+-m markdown
View
@@ -65,6 +65,9 @@ counterparts. You can also include just stringify or just symbolize with
### MergeInitializer
+The MergeInitializer extension simply makes it possible to initialize a
+Hash subclass with another Hash, giving you a quick short-hand.
+
### MethodAccess
The MethodAccess extension allows you to quickly build method-based
@@ -81,17 +84,24 @@ included as individual modules, i.e. `Hashie::Extensions::MethodReader`,
h.abc # => 'def'
h.abc? # => true
+### IndifferentAccess
+
+This extension can be mixed in to instantly give you indifferent access
+to your Hash subclass. This works just like the params hash in Rails and
+other frameworks where whether you provide symbols or strings to access
+keys, you will get the same results.
+
+A unique feature of Hashie's IndifferentAccess mixin is that it will
+inject itself recursively into subhashes *without* reinitializing the
+hash in question. This means you can safely merge together indifferent
+and non-indifferent hashes arbitrarily deeply without worrying about
+whether you'll be able to `hash[:other][:another]` properly.
+
### DeepMerge (Unimplemented)
This extension *will* allow you to easily include a recursive merging
system to any Hash descendant.
-### IndifferentAccess (Unimplemented)
-
-This extension *will* allow you to easily give a hash rules for
-normalizing keys, for instance to allow symbol or string keys both to
-reach the intended value.
-
## Mash
Mash is an extended Hash that gives simple pseudo-object functionality
@@ -1,7 +1,110 @@
module Hashie
module Extensions
+ # IndifferentAccess gives you the ability to not care
+ # whether your hash has string or symbol keys. Made famous
+ # in Rails for accessing query and POST parameters, this
+ # is a handy tool for making sure your hash has maximum
+ # utility.
+ #
+ # One unique feature of this mixin is that it will recursively
+ # inject itself into sub-hash instances without modifying
+ # the actual class of the sub-hash.
+ #
+ # @example
+ # class MyHash < Hash
+ # include Hashie::Extensions::MergeInitializer
+ # include Hashie::Extensions::IndifferentAccess
+ # end
+ #
+ # h = MyHash.new(:foo => 'bar', 'baz' => 'blip')
+ # h['foo'] # => 'bar'
+ # h[:foo] # => 'bar'
+ # h[:baz] # => 'blip'
+ # h['baz'] # => 'blip'
+ #
module IndifferentAccess
- # TODO: Implement indifferent access.
+ def self.included(base)
+ base.class_eval do
+ alias_method :regular_writer, :[]=
+ alias_method :[]=, :indifferent_writer
+ %w(default update fetch delete key? values_at).each do |m|
+ alias_method "regular_#{m}", m
+ alias_method m, "indifferent_#{m}"
+ end
+ end
+ end
+
+ # This will inject indifferent access into an instance of
+ # a hash without modifying the actual class. This is what
+ # allows IndifferentAccess to spread to sub-hashes.
+ def self.inject!(hash)
+ hash.singleton_class.send :include, Hashie::Extensions::IndifferentAccess
+ hash.convert!
+ end
+
+ # Injects indifferent access into a duplicate of the hash
+ # provided. See #inject!
+ def self.inject(hash)
+ inject!(hash.dup)
+ end
+
+ def convert_key(key)
+ key.to_s
+ end
+
+ # Iterates through the keys and values, reconverting them to
+ # their proper indifferent state. Used when IndifferentAccess
+ # is injecting itself into member hashes.
+ def convert!
+ keys.each do |k|
+ regular_writer convert_key(k), convert_value(self.regular_delete(k))
+ end
+ self
+ end
+
+ def convert_value(value)
+ if hash_lacking_indifference?(value)
+ Hashie::Extensions::IndifferentAccess.inject(value.dup)
+ elsif value.is_a?(::Array)
+ value.dup.replace(value.map { |e| convert_value(e) })
+ else
+ value
+ end
+ end
+
+ def indifferent_default(key = nil)
+ return self[convert_key(key)] if key?(key)
+ regular_default(key)
+ end
+
+ def indifferent_update(other_hash)
+ return regular_update(other_hash) if hash_with_indifference?(other_hash)
+ other_hash.each_pair do |k,v|
+ self[k] = v
+ end
+ end
+
+ def indifferent_writer(key, value); regular_writer convert_key(key), convert_value(value) end
+ def indifferent_fetch(key, *args); regular_fetch convert_key(key), *args end
+ def indifferent_delete(key); regular_delete convert_key(key) end
+ def indifferent_key?(key); regular_key? convert_key(key) end
+ def indifferent_values_at(*indices); indices.map{|i| self[i] } end
+
+ def indifferent_access?; true end
+
+ protected
+
+ def hash_lacking_indifference?(other)
+ other.is_a?(::Hash) &&
+ !(other.respond_to?(:indifferent_access?) &&
+ other.indifferent_access?)
+ end
+
+ def hash_with_indifference?(other)
+ other.is_a?(::Hash) &&
+ other.respond_to?(:indifferent_access?) &&
+ other.indifference_access?
+ end
end
end
end
@@ -1,5 +1,66 @@
require 'spec_helper'
describe Hashie::Extensions::IndifferentAccess do
-
+ class IndifferentHash < Hash
+ include Hashie::Extensions::MergeInitializer
+ include Hashie::Extensions::IndifferentAccess
+ end
+ subject{ IndifferentHash }
+
+ it 'should be able to access via string or symbol' do
+ h = subject.new(:abc => 123)
+ h[:abc].should == 123
+ h['abc'].should == 123
+ end
+
+ describe '#values_at' do
+ it 'should indifferently find values' do
+ h = subject.new(:foo => 'bar', 'baz' => 'qux')
+ h.values_at('foo', :baz).should == %w(bar qux)
+ end
+ end
+
+ describe '#fetch' do
+ it 'should work like normal fetch, but indifferent' do
+ h = subject.new(:foo => 'bar')
+ h.fetch(:foo).should == h.fetch('foo')
+ h.fetch(:foo).should == 'bar'
+ end
+ end
+
+ describe '#delete' do
+ it 'should delete indifferently' do
+ h = subject.new(:foo => 'bar', 'baz' => 'qux')
+ h.delete('foo')
+ h.delete(:baz)
+ h.should be_empty
+ end
+ end
+
+ describe '#key?' do
+ it 'should find it indifferently' do
+ h = subject.new(:foo => 'bar')
+ h.should be_key(:foo)
+ h.should be_key('foo')
+ end
+ end
+
+ describe '#update' do
+ subject{ IndifferentHash.new(:foo => 'bar') }
+ it 'should allow keys to be indifferent still' do
+ subject.update(:baz => 'qux')
+ subject['foo'].should == 'bar'
+ subject['baz'].should == 'qux'
+ end
+
+ it 'should recursively inject indifference into sub-hashes' do
+ subject.update(:baz => {:qux => 'abc'})
+ subject['baz']['qux'].should == 'abc'
+ end
+
+ it 'should not change the ancestors of the injected object class' do
+ subject.update(:baz => {:qux => 'abc'})
+ Hash.new.should_not be_respond_to(:indifferent_access?)
+ end
+ end
end

0 comments on commit 3bd7979

Please sign in to comment.