require'active_support/core_ext/hash/keys'require'active_support/core_ext/hash/reverse_merge'moduleActiveSupport# 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.classHashWithIndifferentAccess<Hash# Returns +true+ so that <tt>Array#extract_options!</tt> finds members of# this class.defextractable_options?trueenddefwith_indifferent_accessdupenddefnested_under_indifferent_accessselfenddefinitialize(constructor={})ifconstructor.respond_to?(:to_hash)super()update(constructor)elsesuper(constructor)endenddefdefault(key=nil)ifkey.is_a?(Symbol)&&include?(key=key.to_s)self[key]elsesuperendenddefself.new_from_hash_copying_default(hash)hash=hash.to_hashnew(hash).tapdo|new_hash|new_hash.default=hash.defaultnew_hash.default_proc=hash.default_procifhash.default_procendenddefself.[](*args)new.merge!(Hash[*args])endalias_method:regular_writer,:[]=unlessmethod_defined?(:regular_writer)alias_method:regular_update,:updateunlessmethod_defined?(:regular_update)# Assigns a new value to the hash:## 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,for: :assignment))endalias_method:store,:[]=# Updates the receiver in-place, merging in the hash passed as argument:## hash_1 = ActiveSupport::HashWithIndifferentAccess.new# hash_1[:key] = 'value'## 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}defupdate(other_hash)ifother_hash.is_a?HashWithIndifferentAccesssuper(other_hash)elseother_hash.to_hash.each_pairdo|key,value|ifblock_given?&&key?(key)value=yield(convert_key(key),self[key],value)endregular_writer(convert_key(key),convert_value(value))endselfendendalias_method:merge!,:update# Checks the hash for a key matching the argument passed in:## hash = ActiveSupport::HashWithIndifferentAccess.new# hash['key'] = 'value'# hash.key?(:key) # => true# hash.key?('key') # => truedefkey?(key)super(convert_key(key))endalias_method:include?,:key?alias_method:has_key?,:key?alias_method:member?,: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"deffetch(key,*extras)super(convert_key(key),*extras)end# Returns an array of the values at the specified indices:## hash = ActiveSupport::HashWithIndifferentAccess.new# hash[:a] = 'x'# hash[:b] = 'y'# hash.values_at('a', 'b') # => ["x", "y"]defvalues_at(*indices)indices.collect{|key|self[convert_key(key)]}end# Returns a shallow copy of the hash.## hash = ActiveSupport::HashWithIndifferentAccess.new({ a: { b: 'b' } })# dup = hash.dup# dup[:a][:c] = 'c'## hash[:a][:c] # => nil# dup[:a][:c] # => "c"defdupself.class.new(self).tapdo|new_hash|set_defaults(new_hash)endend# 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.defmerge(hash,&block)self.dup.update(hash,&block)end# 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}defreverse_merge(other_hash)super(self.class.new_from_hash_copying_default(other_hash))end# Same semantics as +reverse_merge+ but modifies the receiver in-place.defreverse_merge!(other_hash)replace(reverse_merge(other_hash))end# Replaces the contents of this hash with other_hash.## h = { "a" => 100, "b" => 200 }# h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400}defreplace(other_hash)super(self.class.new_from_hash_copying_default(other_hash))end# Removes the specified key from the hash.defdelete(key)super(convert_key(key))enddefstringify_keys!;selfenddefdeep_stringify_keys!;selfenddefstringify_keys;dupenddefdeep_stringify_keys;dupendundef:symbolize_keys!undef:deep_symbolize_keys!defsymbolize_keys;to_hash.symbolize_keys!enddefdeep_symbolize_keys;to_hash.deep_symbolize_keys!enddefto_options!;selfenddefselect(*args,&block)dup.tap{|hash|hash.select!(*args,&block)}enddefreject(*args,&block)dup.tap{|hash|hash.reject!(*args,&block)}end# Convert to a regular hash with string keys.defto_hash_new_hash=Hash.newset_defaults(_new_hash)eachdo|key,value|_new_hash[key]=convert_value(value,for: :to_hash)end_new_hashendprotecteddefconvert_key(key)key.kind_of?(Symbol)?key.to_s:keyenddefconvert_value(value,options={})ifvalue.is_a?Hashifoptions[:for]==:to_hashvalue.to_hashelsevalue.nested_under_indifferent_accessendelsifvalue.is_a?(Array)ifoptions[:for]!=:assignment||value.frozen?value=value.dupendvalue.map!{|e|convert_value(e,options)}elsevalueendenddefset_defaults(target)ifdefault_proctarget.default_proc=default_proc.dupelsetarget.default=defaultendendendendHashWithIndifferentAccess=ActiveSupport::HashWithIndifferentAccess