Class: Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess

Inherits:
ActiveSupport::HashWithIndifferentAccess
  • Object
show all
Defined in:
lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb

Overview

A subclass of ActiveSupport::HashWithIndifferentAccess where not only can Hash keys be queried as Symbols or Strings, but they are looked up in a case-insensitive fashion too.

During enumeration, Hash keys will always be returned in whatever case they were originally set. Just as with ActiveSupport::HashWithIndifferentAccess, though, the type of the keys is always returned as a String, even if originally set as a Symbol - only the upper/lower case nature of the original key is preserved.

If a key is written more than once with the same effective meaning in a to-string, to-downcase form, then whatever case was used first wins; e.g. if you did hash = 23, then hash = 42, the result would be => 42.

It’s important to remember that Hash#merge is shallow and replaces values found at existing keys in the target (“this”) hash with values in the inbound Hash. If that new value that is itself a Hash, this replaces the value. For example:

  • Original: 'Foo' => { 'Bar' => 42 }

  • Merge: 'FOO' => { 'BAR' => 24 }

…results in “this” target hash’s key Foo being addressed in the merge by inbound key FOO, so the case doesn’t change. But the value for Foo is replaced by the merging-in Hash completely:

  • Result: 'Foo' => { 'BAR' => 24 }

…and of course we might’ve replaced with a totally different type, such as true:

  • Original: 'Foo' => { 'Bar' => 42 }

  • Merge: 'FOO' => true

  • Result: 'Foo' => true

If you’re intending to merge nested Hashes, then use ActiveSupport’s #deep_merge or an equivalent. This will have the expected outcome, where the hash with ‘BAR’ is merged into the existing value and, therefore, the original ‘Bar’ key case is preserved:

  • Original: 'Foo' => { 'Bar' => 42 }

  • Deep merge: 'FOO' => { 'BAR' => 24 }

  • Result: 'Foo' => { 'Bar' => 24 }

Instance Method Summary collapse

Constructor Details

#initialize(constructor = nil) ⇒ HashWithIndifferentCaseInsensitiveAccess

Returns a new instance of HashWithIndifferentCaseInsensitiveAccess.



97
98
99
100
# File 'lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb', line 97

def initialize(constructor = nil)
  @scimitar_hash_with_indifferent_case_insensitive_access_key_map = {}
  super
end

Instance Method Details

#[]=(key, value) ⇒ Object

Override the individual key writer.



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb', line 118

def []=(key, value)
  string_key      = scimitar_hash_with_indifferent_case_insensitive_access_string(key)
  indifferent_key = scimitar_hash_with_indifferent_case_insensitive_access_downcase(string_key)
  converted_value = convert_value(value, conversion: :assignment)

  # Note '||=', as there might have been a prior use of the "same" key in
  # a different case. The earliest one is preserved since the actual Hash
  # underneath all this is already using that variant of the key.
  #
  key_for_writing = (
    @scimitar_hash_with_indifferent_case_insensitive_access_key_map[indifferent_key] ||= string_key
  )

  regular_writer(key_for_writing, converted_value)
end

#dupObject

It’s vital that the attribute map is carried over when one of these objects is duplicated. Duplication of this ivar state does not happen when ‘dup’ is called on our superclass, so we have to do that manually.



106
107
108
109
110
111
112
113
114
# File 'lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb', line 106

def dup
  duplicate = super
  duplicate.instance_variable_set(
    '@scimitar_hash_with_indifferent_case_insensitive_access_key_map',
    @scimitar_hash_with_indifferent_case_insensitive_access_key_map
  )

  return duplicate
end

#merge(*other_hashes, &block) ⇒ Object

Override #merge to express it in terms of #merge! (also overridden), so that merged hashes can have their keys treated indifferently too.



137
138
139
# File 'lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb', line 137

def merge(*other_hashes, &block)
  dup.merge!(*other_hashes, &block)
end

#merge!(*hashes_to_merge_to_self, &block) ⇒ Object

Modifies-self version of #merge, overriding Hash#merge!.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb', line 143

def merge!(*hashes_to_merge_to_self, &block)
  if block_given?
    hashes_to_merge_to_self.each do |hash_to_merge_to_self|
      hash_to_merge_to_self.each_pair do |key, value|
        value = block.call(key, self[key], value) if self.key?(key)
        self[key] = value
      end
    end
  else
    hashes_to_merge_to_self.each do |hash_to_merge_to_self|
      hash_to_merge_to_self.each_pair do |key, value|
        self[key] = value
      end
    end
  end

  self
end

#with_indifferent_case_insensitive_accessObject



93
94
95
# File 'lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb', line 93

def with_indifferent_case_insensitive_access
  self
end