class HashWithIndifferentAccess def initialize data=nil @data = {} merge! data if data end def merge! data data.each { |key, value| @data[convert_key(key)] = convert_value(value) } end alias_method :update, :merge! def merge data copy = self.class.new @data copy.merge! data copy end def [] key @data[convert_key(key)] end def []= key, value @data[convert_key(key)] = convert_value(value) end alias_method :store, :[]= def key? name @data.key? convert_key(name) end alias_method :include?, :key? alias_method :has_key?, :key? alias_method :member?, :key? def to_json opts=nil @data.to_json opts end def delete_if &block @data.delete_if(&block) end def delete key @data.delete convert_key(key) end def dig *args list = args.map{ |_| _.class == Symbol ? _.to_s : _ } @data.dig *list end def clear @data.keys.each { |key| @data.delete(key) } end def each; @data.each { |k,v| yield(k,v) }; end def keys; @data.keys; end def values; @data.keys; end def to_hash; @data; end private def convert_key key key.kind_of?(Symbol) ? key.to_s : key end def convert_value value value.is_a?(Hash) ? self.class.new(value) : value end end # exrtacted from Rails # class HashWithIndifferentAccess < Hash # # Returns +true+ so that Array#extract_options! finds members of # # this class. # def extractable_options? # true # end # def with_indifferent_access # dup # end # def initialize(constructor = {}) # if constructor.respond_to?(:to_hash) # super() # update(constructor) # hash = constructor.to_hash # self.default = hash.default if hash.default # self.default_proc = hash.default_proc if hash.default_proc # else # super(constructor) # end # end # def default(*args) # arg_key = args.first # if include?(key = convert_key(arg_key)) # self[key] # else # super # end # end # def self.[](*args) # new.merge!(Hash[*args]) # 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 = 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)) # end # alias_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 # # ActiveSupport::HashWithIndifferentAccess 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.to_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 # alias_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') # => true # def key?(key) # super(convert_key(key)) # end # alias_method :include?, :key? # alias_method :has_key?, :key? # alias_method :member?, :key? # # Same as Hash#[] where the key passed as argument can be # # either a string or a symbol: # # # # counters = ActiveSupport::HashWithIndifferentAccess.new # # counters[:foo] = 1 # # # # counters['foo'] # => 1 # # counters[:foo] # => 1 # # counters[:zoo] # => nil # def [](key) # super(convert_key(key)) # end # # Same as Hash#fetch 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 = ActiveSupport::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 array of the values at the specified indices, but also # # raises an exception when one of the keys can't be found. # # # # hash = ActiveSupport::HashWithIndifferentAccess.new # # hash[:a] = 'x' # # hash[:b] = 'y' # # hash.fetch_values('a', 'b') # => ["x", "y"] # # hash.fetch_values('a', 'c') { |key| 'z' } # => ["x", "z"] # # hash.fetch_values('a', 'c') # => KeyError: key not found: "c" # def fetch_values(*indices, &block) # indices.collect { |key| fetch(key, &block) } # end if Hash.method_defined?(:fetch_values) # # 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" # def dup # self.class.new(self).tap do |new_hash| # set_defaults(new_hash) # end # end # # 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) # 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} # def reverse_merge(other_hash) # super(self.class.new(other_hash)) # end # alias_method :with_defaults, :reverse_merge # # Same semantics as +reverse_merge+ but modifies the receiver in-place. # def reverse_merge!(other_hash) # replace(reverse_merge(other_hash)) # end # alias_method :with_defaults!, :reverse_merge! # # Replaces the contents of this hash with other_hash. # # # # h = { "a" => 100, "b" => 200 } # # h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400} # def replace(other_hash) # super(self.class.new(other_hash)) # end # # Removes the specified key from the hash. # def delete(key) # super(convert_key(key)) # end # def stringify_keys!; self end # def deep_stringify_keys!; self end # def stringify_keys; dup end # def deep_stringify_keys; dup end # # undef :symbolize_keys! # # undef :deep_symbolize_keys! # def symbolize_keys; to_hash.symbolize_keys! end # def deep_symbolize_keys; to_hash.deep_symbolize_keys! end # def to_options!; self end # def select(*args, &block) # return to_enum(:select) unless block_given? # dup.tap { |hash| hash.select!(*args, &block) } # end # def reject(*args, &block) # return to_enum(:reject) unless block_given? # dup.tap { |hash| hash.reject!(*args, &block) } # end # def transform_values(*args, &block) # return to_enum(:transform_values) unless block_given? # dup.tap { |hash| hash.transform_values!(*args, &block) } # end # def compact # dup.tap(&:compact!) # end # # Convert to a regular hash with string keys. # def to_hash # _new_hash = Hash.new # set_defaults(_new_hash) # each do |key, value| # _new_hash[key] = convert_value(value, for: :to_hash) # end # _new_hash # end # private # def convert_key(key) # :doc: # key.kind_of?(Symbol) ? key.to_s : key # end # def convert_value(value, options = {}) # :doc: # if value.is_a? Hash # if options[:for] == :to_hash # value.to_hash # else # value.class == Hash ? HashWithIndifferentAccess.new(value) : value # end # elsif value.is_a?(Array) # if options[:for] != :assignment || value.frozen? # value = value.dup # end # value.map! { |e| convert_value(e, options) } # else # value # end # end # def set_defaults(target) # :doc: # if default_proc # target.default_proc = default_proc.dup # else # target.default = default # end # end # end