Skip to content

Commit

Permalink
Adds IndifferentAccess.
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Bleigh committed Aug 2, 2011
1 parent 82295cc commit 3bd7979
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 8 deletions.
1 change: 1 addition & 0 deletions .yardopts
@@ -0,0 +1 @@
-m markdown
22 changes: 16 additions & 6 deletions README.markdown
Expand Up @@ -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
Expand All @@ -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
Expand Down
105 changes: 104 additions & 1 deletion lib/hashie/extensions/indifferent_access.rb
@@ -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
63 changes: 62 additions & 1 deletion spec/hashie/extensions/indifferent_access_spec.rb
@@ -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.