Skip to content

Commit

Permalink
makes a pass over the API of ActiveSupport::HashWithIndifferentAccess
Browse files Browse the repository at this point in the history
  • Loading branch information
fxn committed Jul 21, 2012
1 parent 98f4aee commit db4fdb5
Showing 1 changed file with 70 additions and 22 deletions.
92 changes: 70 additions & 22 deletions activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -1,12 +1,46 @@
require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/keys'


module ActiveSupport module ActiveSupport
# This class has dubious semantics and we only have it so that # Implements a hash where keys +:foo+ and +"foo"+ are considered to be the same.
# people can write <tt>params[:key]</tt> instead of <tt>params['key']</tt> #
# and they get the same value for both keys. # rgb = ActiveSupport::HashWithIndifferentAccess.new
#
# rgb[:black] = '#000000'
# rgb[:black] # => '#000000'
# rgb['black'] # => '#000000'
#
# rgb['white'] = '#FFFFFF'
# rgb[:white] # => '#FFFFFF'
# rgb['white'] # => '#FFFFFF'
#
# Internally symbols are mapped to strings when used as keys in the entire
# writing interface (calling <tt>[]=</tt>, <tt>merge</tt>, etc). This
# mapping belongs to the public interface. For example, given
#
# hash = ActiveSupport::HashWithIndifferentAccess.new(:a => 1)
#
# you are guaranteed that the key is returned as a string:
#
# hash.keys # => ["a"]
#
# Technically other types of keys are accepted:
#
# hash = ActiveSupport::HashWithIndifferentAccess.new(:a => 1)
# hash[0] = 0
# hash # => {"a"=>1, 0=>0}
#
# but this class is intended for use cases where strings or symbols are the
# expected keys and it is convenient to understand both as the same. For
# example the +params+ hash in Ruby on Rails.
#
# Note that core extensions define <tt>Hash#with_indifferent_access</tt>:
#
# rgb = {:black => '#000000', :white => '#FFFFFF'}.with_indifferent_access
#
# which may be handy.
class HashWithIndifferentAccess < Hash class HashWithIndifferentAccess < Hash

# Returns true so that <tt>Array#extract_options!</tt> finds members of
# Always returns true, so that <tt>Array#extract_options!</tt> finds members of this class. # this class.
def extractable_options? def extractable_options?
true true
end end
Expand Down Expand Up @@ -51,25 +85,32 @@ def self.[](*args)


# Assigns a new value to the hash: # Assigns a new value to the hash:
# #
# hash = HashWithIndifferentAccess.new # hash = ActiveSupport::HashWithIndifferentAccess.new
# hash[:key] = "value" # hash[:key] = "value"
# #
# This value can be later fetched using either +:key+ or +"key"+.
def []=(key, value) def []=(key, value)
regular_writer(convert_key(key), convert_value(value)) regular_writer(convert_key(key), convert_value(value))
end end


alias_method :store, :[]= alias_method :store, :[]=


# Updates the instantized hash with values from the second: # Updates the receiver in-place merging in the hash passed as argument:
# #
# hash_1 = HashWithIndifferentAccess.new # hash_1 = ActiveSupport::HashWithIndifferentAccess.new
# hash_1[:key] = "value" # hash_2[:key] = "value"
# #
# hash_2 = HashWithIndifferentAccess.new # hash_2 = ActiveSupport::HashWithIndifferentAccess.new
# hash_2[:key] = "New Value!" # hash_2[:key] = "New Value!"
# #
# hash_1.update(hash_2) # => {"key"=>"New Value!"} # hash_1.update(hash_2) # => {"key"=>"New Value!"}
# #
# The argument can be either an
# <tt>ActiveSupport::HashWithIndifferentAccess</tt> or a regular +Hash+.
# In either case the merge respects the semantics of indifferent access.
#
# If the argument is a regular hash with keys +:key+ and +"key"+ only one
# of the values end up in the receiver, but which was is unespecified.
def update(other_hash) def update(other_hash)
if other_hash.is_a? HashWithIndifferentAccess if other_hash.is_a? HashWithIndifferentAccess
super(other_hash) super(other_hash)
Expand All @@ -83,10 +124,10 @@ def update(other_hash)


# Checks the hash for a key matching the argument passed in: # Checks the hash for a key matching the argument passed in:
# #
# hash = HashWithIndifferentAccess.new # hash = ActiveSupport::HashWithIndifferentAccess.new
# hash["key"] = "value" # hash["key"] = "value"
# hash.key? :key # => true # hash.key?(:key) # => true
# hash.key? "key" # => true # hash.key?("key") # => true
# #
def key?(key) def key?(key)
super(convert_key(key)) super(convert_key(key))
Expand All @@ -99,7 +140,7 @@ def key?(key)
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be # Same as <tt>Hash#fetch</tt> where the key passed as argument can be
# either a string or a symbol: # either a string or a symbol:
# #
# counters = HashWithIndifferentAccess.new # counters = ActiveSupport::HashWithIndifferentAccess.new
# counters[:foo] = 1 # counters[:foo] = 1
# #
# counters.fetch("foo") # => 1 # counters.fetch("foo") # => 1
Expand All @@ -113,7 +154,7 @@ def fetch(key, *extras)


# Returns an array of the values at the specified indices: # Returns an array of the values at the specified indices:
# #
# hash = HashWithIndifferentAccess.new # hash = ActiveSupport::HashWithIndifferentAccess.new
# hash[:a] = "x" # hash[:a] = "x"
# hash[:b] = "y" # hash[:b] = "y"
# hash.values_at("a", "b") # => ["x", "y"] # hash.values_at("a", "b") # => ["x", "y"]
Expand All @@ -129,23 +170,30 @@ def dup
end end
end end


# Merges the instantized and the specified hashes together, giving precedence to the values from the second hash. # This method has the same semantics of +update+, except it does not
# Does not overwrite the existing hash. # modify the receiver but rather returns a new hash with indifferent
# access with the result of the merge.
def merge(hash) def merge(hash)
self.dup.update(hash) self.dup.update(hash)
end end


# Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second. # Like +merge+ but the other way around: Merges the receiver into the
# This overloaded definition prevents returning a regular hash, if reverse_merge is called on a <tt>HashWithDifferentAccess</tt>. # argument and returns a new hash with indifferent access as result:
#
# hash = ActiveSupport::HashWithIndifferentAccess.new
# hash['a'] = nil
# hash.reverse_merge(:a => 0, :b => 1) # => {"a"=>nil, "b"=>1}
#
def reverse_merge(other_hash) def reverse_merge(other_hash)
super self.class.new_from_hash_copying_default(other_hash) super(self.class.new_from_hash_copying_default(other_hash))
end end


# Same semantics as +reverse_merge+ but modifies the receiver in-place.
def reverse_merge!(other_hash) def reverse_merge!(other_hash)
replace(reverse_merge( other_hash )) replace(reverse_merge( other_hash ))
end end


# Removes a specified key from the hash. # Removes the specified key from the hash.
def delete(key) def delete(key)
super(convert_key(key)) super(convert_key(key))
end end
Expand All @@ -160,7 +208,7 @@ def symbolize_keys; to_hash.symbolize_keys end
def deep_symbolize_keys; to_hash.deep_symbolize_keys end def deep_symbolize_keys; to_hash.deep_symbolize_keys end
def to_options!; self end def to_options!; self end


# Convert to a Hash with String keys. # Convert to a regular hash with string keys.
def to_hash def to_hash
Hash.new(default).merge!(self) Hash.new(default).merge!(self)
end end
Expand Down

0 comments on commit db4fdb5

Please sign in to comment.