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
|
require 'active_support/core_ext/hash/keys'
module ActiveSupport
# This class has dubious semantics and we only have it so that
# people can write <tt>params[:key]</tt> instead of <tt>params['key']</tt>
# and they get the same value for both keys.
class HashWithIndifferentAccess < Hash
# Always returns true, so that <tt>Array#extract_options!</tt> finds members of this class.
def extractable_options?
true
end
def with_indifferent_access
dup
end
def nested_under_indifferent_access
self
end
def initialize(constructor = {})
if constructor.is_a?(Hash)
super()
update(constructor)
else
super(constructor)
end
end
def default(key = nil)
if key.is_a?(Symbol) && include?(key = key.to_s)
self[key]
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
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 = 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)
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?
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be
# either a string or a symbol:
#
# counters = 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 = 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 <tt>HashWithDifferentAccess</tt>.
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 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
# Convert to a Hash with String keys.
def to_hash
Hash.new(default).merge!(self)
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 = value.dup if value.frozen?
value.map! { |e| convert_value(e) }
else
value
end
end
end
end
HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
|