require 'gorillib/hash/keys' # This class has dubious semantics and we only have it so that # people can write params[:key] instead of params['key'] # and they get the same value for both keys. module Gorillib class HashWithIndifferentAccess < Hash def extractable_options? true end def with_indifferent_access dup end def initialize(constructor = {}) if constructor.is_a?(Hash) super() update(constructor) else super(constructor) end end def default(key = nil) if include?(converted = convert_key(key)) self[converted] else super end end def self.new_from_hash_copying_default(hash) new(hash).tap do |new_hash| new_hash.default = hash.default end end alias_method(:regular_writer, :[]=) unless method_defined?(:regular_writer) alias_method :regular_update, :update unless method_defined?(:regular_update) # Assigns a new value to the hash: # # hash = HashWithIndifferentAccess.new # hash[:key] = "value" # def []=(key, value) regular_writer(convert_key(key), convert_value(value)) end alias_method :store, :[]= # Updates the instantized hash with values from the second: # # hash_1 = HashWithIndifferentAccess.new # hash_1[:key] = "value" # # hash_2 = HashWithIndifferentAccess.new # hash_2[:key] = "New Value!" # # hash_1.update(hash_2) # => {"key"=>"New Value!"} # def update(other_hash) raise TypeError, "can't convert #{other_hash.nil? ? 'nil' : other_hash.class} into Hash" unless other_hash.respond_to?(:each_pair) if other_hash.is_a? HashWithIndifferentAccess super(other_hash) else other_hash.each_pair{|key, value| regular_writer(convert_key(key), convert_value(value)) } self end end alias_method :merge!, :update # Checks the hash for a key matching the argument passed in: # # hash = HashWithIndifferentAccess.new # hash["key"] = "value" # hash.key? :key # => true # hash.key? "key" # => true # def key?(key) super(convert_key(key)) end alias_method :include?, :key? alias_method :has_key?, :key? alias_method :member?, :key? # Fetches the value for the specified key, same as doing hash[key] def fetch(key, *extras) super(convert_key(key), *extras) end # Returns an array of the values at the specified indices: # # hash = HashWithIndifferentAccess.new # hash[:a] = "x" # hash[:b] = "y" # hash.values_at("a", "b") # => ["x", "y"] # def values_at(*indices) indices.collect {|key| self[convert_key(key)]} end # Returns an exact copy of the hash. def dup self.class.new(self).tap do |new_hash| new_hash.default = default 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) 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 HashWithDifferentAccess. def reverse_merge(other_hash) super self.class.new_from_hash_copying_default(other_hash) end def reverse_merge!(other_hash) replace(reverse_merge( other_hash )) end # Removes a specified key from the hash. def delete(key) super(convert_key(key)) end def stringify_keys!; self end def stringify_keys; dup end undef_method :symbolize_keys! if method_defined?(:symbolize_keys!) def symbolize_keys; to_hash.symbolize_keys end def to_options!; self end # Convert to a Hash with String keys. def to_hash Hash.new(default).merge!(self) end def assoc key key = convert_key(key) return unless has_key?(key) [key, self[key]] end def rassoc val key = key(val) or return [key, self[key]] end protected def convert_key(key) key.kind_of?(Symbol) ? key.to_s : key end def convert_value(value) if value.is_a? Hash value.nested_under_indifferent_access elsif value.is_a?(Array) value.dup.replace(value.map{|e| convert_value(e) }) else value end end end end module Gorillib class HashWithIndifferentSymbolKeys < Gorillib::HashWithIndifferentAccess def convert_key key return key if key.is_a?(Fixnum) key.respond_to?(:to_sym) ? key.to_sym : key end end end class Hash # Returns an +ActiveSupport::HashWithIndifferentAccess+ out of its receiver: # # {:a => 1}.with_indifferent_access["a"] # => 1 # def with_indifferent_access Gorillib::HashWithIndifferentAccess.new_from_hash_copying_default(self) end # Called when object is nested under an object that receives # #with_indifferent_access. This method with be called on the current object # by the enclosing object and is aliased to #with_indifferent_access by # default. Subclasses of Hash may overwrite this method to return +self+ if # converting to an +ActiveSupport::HashWithIndifferentAccess+ would not be # desirable. # # b = {:b => 1} # {:a => b}.with_indifferent_access["a"] # calls b.nested_under_indifferent_access # alias nested_under_indifferent_access with_indifferent_access end