diff options
Diffstat (limited to 'activesupport/lib/active_support/hash_with_indifferent_access.rb')
-rw-r--r-- | activesupport/lib/active_support/hash_with_indifferent_access.rb | 121 |
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 |