Adds IndifferentAccess.

This commit is contained in:
Michael Bleigh 2011-08-02 16:03:21 -05:00
parent 82295cc65f
commit 3bd79790bc
4 changed files with 183 additions and 8 deletions

1
.yardopts Normal file
View File

@ -0,0 +1 @@
-m markdown

View File

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

View File

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

View File

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