module Hashie module Extensions # IndifferentAccess gives you the ability to not care # whether your hash has string or symbol keys. Made famous # in Rails for accessing query and POST parameters, this # is a handy tool for making sure your hash has maximum # utility. # # One unique feature of this mixin is that it will recursively # inject itself into sub-hash instances without modifying # the actual class of the sub-hash. # # @example # class MyHash < Hash # include Hashie::Extensions::MergeInitializer # include Hashie::Extensions::IndifferentAccess # end # # h = MyHash.new(:foo => 'bar', 'baz' => 'blip') # h['foo'] # => 'bar' # h[:foo] # => 'bar' # h[:baz] # => 'blip' # h['baz'] # => 'blip' # module IndifferentAccess include Hashie::Extensions::RubyVersionCheck def self.included(base) Hashie::Extensions::Dash::IndifferentAccess.maybe_extend(base) base.class_eval do alias_method :regular_writer, :[]= unless method_defined?(:regular_writer) alias_method :[]=, :indifferent_writer alias_method :store, :indifferent_writer %w[default update replace fetch delete key? values_at].each do |m| alias_method "regular_#{m}", m unless method_defined?("regular_#{m}") alias_method m, "indifferent_#{m}" end %w[include? member? has_key?].each do |key_alias| alias_method key_alias, :indifferent_key? end class << self def [](*) super.convert! end def try_convert(*) (hash = super) && self[hash] end end end end # This will inject indifferent access into an instance of # a hash without modifying the actual class. This is what # allows IndifferentAccess to spread to sub-hashes. def self.inject!(hash) (class << hash; self; end).send :include, IndifferentAccess hash.convert! end # Injects indifferent access into a duplicate of the hash # provided. See #inject! def self.inject(hash) inject!(hash.dup) end def convert_key(key) key.to_s end # Iterates through the keys and values, reconverting them to # their proper indifferent state. Used when IndifferentAccess # is injecting itself into member hashes. def convert! keys.each do |k| # rubocop:disable Performance/HashEachMethods indifferent_writer k, regular_delete(k) end self end def indifferent_value(value) if hash_lacking_indifference?(value) IndifferentAccess.inject!(value) elsif value.is_a?(::Array) value.replace(value.map { |e| indifferent_value(e) }) else value end end def indifferent_default(key = nil) return self[convert_key(key)] if key?(key) regular_default(key) end def indifferent_update(other_hash) return regular_update(other_hash) if hash_with_indifference?(other_hash) other_hash.each_pair do |k, v| self[k] = v end end def indifferent_writer(key, value) regular_writer convert_key(key), indifferent_value(value) end def indifferent_fetch(key, *args, &block) regular_fetch convert_key(key), *args, &block end def indifferent_delete(key) regular_delete convert_key(key) end def indifferent_key?(key) regular_key? convert_key(key) end def indifferent_values_at(*indices) indices.map { |i| self[i] } end def indifferent_access? true end def indifferent_replace(other_hash) (keys - other_hash.keys).each { |key| delete(key) } other_hash.each { |key, value| self[key] = value } self end def merge(*args) result = super return IndifferentAccess.inject!(result) if hash_lacking_indifference?(result) result.convert! end def merge!(*) super.convert! end def to_hash {}.tap do |result| each_pair { |key, value| result[key] = value } if default_proc result.default_proc = default_proc else result.default = default end end end with_minimum_ruby('2.5.0') do def slice(*keys) string_keys = keys.map { |key| convert_key(key) } super(*string_keys) end end with_minimum_ruby('3.0.0') do def except(*keys) string_keys = keys.map { |key| convert_key(key) } super(*string_keys) end end protected def hash_lacking_indifference?(other) other.is_a?(::Hash) && !(other.respond_to?(:indifferent_access?) && other.indifferent_access?) end def hash_with_indifference?(other) other.is_a?(::Hash) && other.respond_to?(:indifferent_access?) && other.indifferent_access? end end end end