aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/hash_with_indifferent_access.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support/hash_with_indifferent_access.rb')
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb121
1 files changed, 97 insertions, 24 deletions
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 3e6c8893e9..71713644a7 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -1,12 +1,46 @@
require 'active_support/core_ext/hash/keys'
module ActiveSupport
- # This class has dubious semantics and we only have it so that
- # people can write <tt>params[:key]</tt> instead of <tt>params['key']</tt>
- # and they get the same value for both keys.
+ # Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered to be the same.
+ #
+ # 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
-
- # Always returns true, so that <tt>Array#extract_options!</tt> finds members of this class.
+ # Returns true so that <tt>Array#extract_options!</tt> finds members of
+ # this class.
def extractable_options?
true
end
@@ -51,30 +85,52 @@ module ActiveSupport
# Assigns a new value to the hash:
#
- # hash = HashWithIndifferentAccess.new
+ # hash = ActiveSupport::HashWithIndifferentAccess.new
# hash[:key] = "value"
#
+ # This value can be later fetched using either +:key+ or +"key"+.
def []=(key, value)
regular_writer(convert_key(key), convert_value(value))
end
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 = HashWithIndifferentAccess.new
+ # hash_2 = ActiveSupport::HashWithIndifferentAccess.new
# 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 one is unspecified.
+ #
+ # When given a block, the value for duplicated keys will be determined
+ # by the result of invoking the block with the duplicated key, the value
+ # in the receiver, and the value in +other_hash+. The rules for duplicated
+ # keys follow the semantics of indifferent access:
+ #
+ # hash_1[:key] = 10
+ # hash_2['key'] = 12
+ # hash_1.update(hash_2) { |key, old, new| old + new } # => {"key"=>22}
+ #
def update(other_hash)
if other_hash.is_a? HashWithIndifferentAccess
super(other_hash)
else
- other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
+ other_hash.each_pair do |key, value|
+ if block_given? && key?(key)
+ value = yield(convert_key(key), self[key], value)
+ end
+ regular_writer(convert_key(key), convert_value(value))
+ end
self
end
end
@@ -83,10 +139,10 @@ module ActiveSupport
# Checks the hash for a key matching the argument passed in:
#
- # hash = HashWithIndifferentAccess.new
+ # hash = ActiveSupport::HashWithIndifferentAccess.new
# hash["key"] = "value"
- # hash.key? :key # => true
- # hash.key? "key" # => true
+ # hash.key?(:key) # => true
+ # hash.key?("key") # => true
#
def key?(key)
super(convert_key(key))
@@ -96,14 +152,24 @@ module ActiveSupport
alias_method :has_key?, :key?
alias_method :member?, :key?
- # Fetches the value for the specified key, same as doing hash[key]
+ # Same as <tt>Hash#fetch</tt> where the key passed as argument can be
+ # either a string or a symbol:
+ #
+ # counters = ActiveSupport::HashWithIndifferentAccess.new
+ # counters[:foo] = 1
+ #
+ # counters.fetch("foo") # => 1
+ # counters.fetch(:bar, 0) # => 0
+ # counters.fetch(:bar) {|key| 0} # => 0
+ # counters.fetch(:zoo) # => KeyError: key not found: "zoo"
+ #
def fetch(key, *extras)
super(convert_key(key), *extras)
end
# Returns an array of the values at the specified indices:
#
- # hash = HashWithIndifferentAccess.new
+ # hash = ActiveSupport::HashWithIndifferentAccess.new
# hash[:a] = "x"
# hash[:b] = "y"
# hash.values_at("a", "b") # => ["x", "y"]
@@ -119,23 +185,30 @@ module ActiveSupport
end
end
- # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash.
- # Does not overwrite the existing hash.
- def merge(hash)
- self.dup.update(hash)
+ # This method has the same semantics of +update+, except it does not
+ # modify the receiver but rather returns a new hash with indifferent
+ # access with the result of the merge.
+ def merge(hash, &block)
+ self.dup.update(hash, &block)
end
- # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
- # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a <tt>HashWithDifferentAccess</tt>.
+ # Like +merge+ but the other way around: Merges the receiver into the
+ # 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)
- super self.class.new_from_hash_copying_default(other_hash)
+ super(self.class.new_from_hash_copying_default(other_hash))
end
+ # Same semantics as +reverse_merge+ but modifies the receiver in-place.
def reverse_merge!(other_hash)
replace(reverse_merge( other_hash ))
end
- # Removes a specified key from the hash.
+ # Removes the specified key from the hash.
def delete(key)
super(convert_key(key))
end
@@ -150,7 +223,7 @@ module ActiveSupport
def deep_symbolize_keys; to_hash.deep_symbolize_keys end
def to_options!; self end
- # Convert to a Hash with String keys.
+ # Convert to a regular hash with string keys.
def to_hash
Hash.new(default).merge!(self)
end