1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
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
|