inquisitive 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ module Inquisitive
2
+ class Hash < HashWithIndifferentAccess
3
+ include Inquisitive
4
+
5
+ attr_accessor :negated
6
+ def no
7
+ dup.tap{ |s| s.negated = !s.negated }
8
+ end
9
+
10
+ def convert_value(value, options={})
11
+ super(Inquisitive[value], options)
12
+ end
13
+
14
+ private
15
+
16
+ def dup
17
+ super.tap{ |duplicate| duplicate.negated = self.negated }
18
+ end
19
+
20
+ def respond_to_missing?(method_name, include_private = false)
21
+ predicate_method?(method_name) or has_key?(method_name)
22
+ end
23
+ def method_missing(method_name, *arguments)
24
+ if predicate_method? method_name
25
+ if has_key? predication(method_name)
26
+ Inquisitive.present? self[predication(method_name)]
27
+ else
28
+ false
29
+ end ^ negated
30
+ elsif has_key? method_name
31
+ self[method_name]
32
+ else
33
+ super
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,185 @@
1
+ module Inquisitive
2
+ ####
3
+ # A trimmed down version of ActiveSupport 4.0's HashWithIndifferentAccess
4
+ # modified slightly so we don't have to inject behaviour into Hash.
5
+ # It lacks all `deep_` transforms since that requires patching Object and Array.
6
+ ##
7
+ class HashWithIndifferentAccess < ::Hash
8
+
9
+ def extractable_options?
10
+ true
11
+ end
12
+ def with_indifferent_access
13
+ dup
14
+ end
15
+ def nested_under_indifferent_access
16
+ self
17
+ end
18
+
19
+ def self.[](*args)
20
+ new.merge!(::Hash[*args])
21
+ end
22
+
23
+ def initialize(constructor = {}, &block)
24
+ if constructor.is_a?(::Hash)
25
+ super()
26
+ steal_default_from(constructor)
27
+ update(constructor)
28
+ else
29
+ super(constructor)
30
+ end.tap do |hash|
31
+ self.default_proc = block if block_given?
32
+ end
33
+ end
34
+
35
+ def default(key = nil)
36
+ if key.is_a?(Symbol) && include?(key = key.to_s)
37
+ self[key]
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ def steal_default_from(hash)
44
+ if hash.default_proc
45
+ self.default_proc = hash.default_proc
46
+ else
47
+ self.default = hash.default
48
+ end
49
+ end
50
+
51
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
52
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
53
+
54
+ def []=(key, value)
55
+ regular_writer(convert_key(key), convert_value(value, for: :assignment))
56
+ end
57
+
58
+ alias_method :store, :[]=
59
+
60
+ def update(other_hash)
61
+ if other_hash.is_a? self.class
62
+ super(other_hash)
63
+ else
64
+ other_hash.each_pair do |key, value|
65
+ if block_given? && key?(key)
66
+ value = yield(convert_key(key), self[key], value)
67
+ end
68
+ regular_writer(convert_key(key), convert_value(value))
69
+ end
70
+ self
71
+ end
72
+ end
73
+
74
+ alias_method :merge!, :update
75
+
76
+ def key?(key)
77
+ super(convert_key(key))
78
+ end
79
+
80
+ alias_method :include?, :key?
81
+ alias_method :has_key?, :key?
82
+ alias_method :member?, :key?
83
+
84
+ def fetch(key, *extras)
85
+ super(convert_key(key), *extras)
86
+ end
87
+
88
+ def values_at(*indices)
89
+ indices.collect {|key| self[convert_key(key)]}
90
+ end
91
+
92
+ def dup
93
+ self.class.new(self).tap do |new_hash|
94
+ new_hash.default = default
95
+ end
96
+ end
97
+
98
+ def merge(hash, &block)
99
+ self.dup.update(hash, &block)
100
+ end
101
+
102
+ def replace(other_hash)
103
+ super(self.class.new(other_hash))
104
+ end
105
+
106
+ def delete(key)
107
+ super(convert_key(key))
108
+ end
109
+
110
+ def transform_keys
111
+ result = {}
112
+ each_key do |key|
113
+ result[yield(key)] = self[key]
114
+ end
115
+ result
116
+ end
117
+
118
+ def transform_keys!
119
+ keys.each do |key|
120
+ self[yield(key)] = delete(key)
121
+ end
122
+ self
123
+ end
124
+
125
+ def symbolize_keys
126
+ transform_keys{ |key| key.to_sym rescue key }
127
+ end
128
+
129
+ def symbolize_keys!
130
+ transform_keys!{ |key| key.to_sym rescue key }
131
+ end
132
+
133
+ def assert_valid_keys(*valid_keys)
134
+ valid_string_keys = valid_keys.flatten.map(&:to_s).uniq
135
+ each_key do |k|
136
+ raise ArgumentError.new("Unknown key: #{k}") unless valid_string_keys.include?(k)
137
+ end
138
+ end
139
+
140
+ def stringify_keys!; self end
141
+ def stringify_keys; dup end
142
+ def to_options!; self end
143
+
144
+ def select(*args, &block)
145
+ dup.tap {|hash| hash.select!(*args, &block)}
146
+ end
147
+
148
+ def to_hash
149
+ _new_hash= {}
150
+ each do |key, value|
151
+ _new_hash[convert_key(key)] = convert_value(value, for: :to_hash)
152
+ end
153
+ ::Hash.new(default).merge!(_new_hash)
154
+ end
155
+
156
+ protected
157
+
158
+ def convert_key(key)
159
+ key.kind_of?(Symbol) ? key.to_s : key
160
+ end
161
+
162
+ def convert_value(value, options = {})
163
+ if value.is_a? ::Hash
164
+ if options[:for] == :to_hash
165
+ value.to_hash
166
+ else
167
+ if value.is_a? self.class
168
+ value
169
+ else
170
+ self.class.new value
171
+ end
172
+ end
173
+ elsif value.is_a?(::Array)
174
+ unless options[:for] == :assignment
175
+ value = value.dup
176
+ end
177
+ value.map! { |e| convert_value(e, options) }
178
+ else
179
+ value
180
+ end
181
+ end
182
+
183
+ end
184
+
185
+ end
@@ -0,0 +1,24 @@
1
+ module Inquisitive
2
+ class String < ::String
3
+ include Inquisitive
4
+
5
+ attr_accessor :negated
6
+ def not
7
+ self.dup.tap{ |s| s.negated = !s.negated }
8
+ end
9
+
10
+ private
11
+
12
+ def respond_to_missing?(method_name, include_private = false)
13
+ predicate_method? method_name
14
+ end
15
+ def method_missing(method_name, *arguments)
16
+ if predicate_method? method_name
17
+ (self == predication(method_name)) ^ negated
18
+ else
19
+ super
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ require 'test_helper'
2
+
3
+ class InquisitiveArrayTest < Test
4
+
5
+ def array
6
+ Inquisitive::Array.new @raw_array
7
+ end
8
+
9
+ include ArrayTests
10
+
11
+ end
@@ -0,0 +1,72 @@
1
+ class InquisitiveCombinatorialEnvironmentTest < EnvironmentTest
2
+
3
+ def setup
4
+ super
5
+ ENV['STRING'] = @raw_string
6
+ ENV['ARRAY'] = @raw_array.join(',')
7
+ ENV['HASH_AUTHENTICATION'] = @raw_hash[:authentication].to_s
8
+ ENV['HASH_IN'] = @raw_hash[:in]
9
+ ENV['HASH_DATABASES'] = @raw_hash[:databases].join(',')
10
+ end
11
+ def teardown
12
+ super
13
+ ENV.delete 'STRING'
14
+ ENV.delete 'ARRAY'
15
+ ENV.delete 'HASH_AUTHENTICATION'
16
+ ENV.delete 'HASH_IN'
17
+ ENV.delete 'HASH_DATABASES'
18
+ ENV.delete 'HASH_SOMETHING_NEW'
19
+ end
20
+
21
+ def change_string_variable
22
+ ENV['STRING'] = 'something_new'
23
+ end
24
+ def change_array_variable
25
+ ENV['ARRAY'] = [ ENV['ARRAY'], 'something_new' ].join ','
26
+ end
27
+ def change_hash_variable
28
+ ENV['HASH_SOMETHING_NEW'] = 'true'
29
+ end
30
+
31
+ end
32
+
33
+ %w[dynamic cached static].each do |mode|
34
+ %w[string array hash].each do |type|
35
+
36
+ Inquisitive.const_set(
37
+ :"Inquisitive#{mode.capitalize}#{type.capitalize}EnvironmentTest",
38
+ Class.new(InquisitiveCombinatorialEnvironmentTest) do
39
+
40
+ class << self
41
+ attr_accessor :mode, :type
42
+ end
43
+
44
+ def setup
45
+ super
46
+ @mode = Inquisitive[self.class.mode]
47
+ @type = Inquisitive[self.class.type]
48
+ App.inquires_about @type.upcase, mode: @mode
49
+ end
50
+
51
+ def string
52
+ App.string
53
+ end
54
+ def array
55
+ App.array
56
+ end
57
+ def hash
58
+ App.hash
59
+ end
60
+
61
+ include CombinatorialEnvironmentTests
62
+
63
+ end
64
+ ).tap do |klass|
65
+ klass.mode = mode
66
+ klass.type = type
67
+ end.send :include, Object.const_get(:"#{type.capitalize}Tests")
68
+ # Mixes in type-specific tests to ensure lookup behaves normally
69
+ # when accessed through the modes of App getters
70
+
71
+ end
72
+ end
@@ -0,0 +1,44 @@
1
+ require 'test_helper'
2
+
3
+ class InquisitiveEnvironmentTest < EnvironmentTest
4
+
5
+ def test_missing_variable_responses
6
+ App.inquires_about '__DOES_NOT_EXIST__', with: :exists
7
+ assert_equal App.exists, nil
8
+ end
9
+ def test_missing_variable_predicates
10
+ App.inquires_about '__DOES_NOT_EXIST__', with: :exists
11
+ refute App.exists?
12
+ end
13
+
14
+ def test_autonaming_of_inquirers
15
+ App.inquires_about 'NAME_NOT_SPECIFIED'
16
+ assert App.respond_to? :name_not_specified
17
+ end
18
+
19
+ def test_default_mode_of_dynamic
20
+ App.inquires_about 'DEFAULTS_TO', with: :defaults_to
21
+ App.defaults_to # Call once to ensure no caching
22
+ ENV['DEFAULTS_TO'] = 'dynamic'
23
+ assert App.defaults_to.dynamic?
24
+ end
25
+
26
+ def test_custom_string_presence
27
+ ENV['AUTHORIZABLE'] = 'false'
28
+ App.inquires_about 'AUTHORIZABLE', present_if: 'true'
29
+ refute App.authorizable?
30
+ end
31
+
32
+ def test_custom_regex_presence
33
+ ENV['AUTHORIZABLE'] = 'not at all'
34
+ App.inquires_about 'AUTHORIZABLE', present_if: /yes/
35
+ refute App.authorizable?
36
+ end
37
+
38
+ def test_custom_class_presence
39
+ ENV['AUTHORIZABLE'] = 'not at all'
40
+ App.inquires_about 'AUTHORIZABLE', present_if: Array
41
+ refute App.authorizable?
42
+ end
43
+
44
+ end
@@ -0,0 +1,20 @@
1
+ require 'test_helper'
2
+
3
+ class HashTest < Test
4
+
5
+ def hash
6
+ Inquisitive::Hash.new @raw_hash
7
+ end
8
+ include HashTests
9
+
10
+ def string
11
+ hash.in
12
+ end
13
+ include StringTests
14
+
15
+ def array
16
+ hash.databases
17
+ end
18
+ include ArrayTests
19
+
20
+ end
@@ -0,0 +1,482 @@
1
+ require 'test_helper'
2
+
3
+ class InquisitiveHashWithIndifferentAccessTest < Test
4
+ HashWithIndifferentAccess = Inquisitive::HashWithIndifferentAccess
5
+
6
+ class IndifferentHash < HashWithIndifferentAccess
7
+ end
8
+
9
+ class SubclassingArray < Array
10
+ end
11
+
12
+ class SubclassingHash < Hash
13
+ end
14
+
15
+ def setup
16
+ @strings = { 'a' => 1, 'b' => 2 }
17
+ @nested_strings = { 'a' => { 'b' => { 'c' => 3 } } }
18
+ @symbols = { :a => 1, :b => 2 }
19
+ @nested_symbols = { :a => { :b => { :c => 3 } } }
20
+ @mixed = { :a => 1, 'b' => 2 }
21
+ @nested_mixed = { 'a' => { :b => { 'c' => 3 } } }
22
+ @fixnums = { 0 => 1, 1 => 2 }
23
+ @nested_fixnums = { 0 => { 1 => { 2 => 3} } }
24
+ @illegal_symbols = { [] => 3 }
25
+ @nested_illegal_symbols = { [] => { [] => 3} }
26
+ @upcase_strings = { 'A' => 1, 'B' => 2 }
27
+ @nested_upcase_strings = { 'A' => { 'B' => { 'C' => 3 } } }
28
+
29
+ @indifferent_strings = HashWithIndifferentAccess.new @strings
30
+ @indifferent_nested_strings = HashWithIndifferentAccess.new @nested_strings
31
+ @indifferent_symbols = HashWithIndifferentAccess.new @symbols
32
+ @indifferent_nested_symbols = HashWithIndifferentAccess.new @nested_symbols
33
+ @indifferent_mixed = HashWithIndifferentAccess.new @mixed
34
+ @indifferent_nested_mixed = HashWithIndifferentAccess.new @nested_mixed
35
+ @indifferent_fixnums = HashWithIndifferentAccess.new @fixnums
36
+ @indifferent_nested_fixnums = HashWithIndifferentAccess.new @nested_fixnums
37
+ @indifferent_illegal_symbols = HashWithIndifferentAccess.new @illegal_symbols
38
+ @indifferent_nested_illegal_symbols = HashWithIndifferentAccess.new @nested_illegal_symbols
39
+ @indifferent_upcase_strings = HashWithIndifferentAccess.new @upcase_strings
40
+ @indifferent_nested_upcase_strings = HashWithIndifferentAccess.new @nested_upcase_strings
41
+ end
42
+
43
+ def test_extractable_options?
44
+ assert @indifferent_strings.extractable_options?
45
+ end
46
+
47
+ def test_with_indifferent_access
48
+ assert_equal @indifferent_strings, @indifferent_strings.with_indifferent_access
49
+ end
50
+
51
+ def test_nested_under_indifferent_access
52
+ assert_same @indifferent_strings, @indifferent_strings.nested_under_indifferent_access
53
+ end
54
+
55
+ def test_indifferent_transform_keys
56
+ assert_equal @indifferent_upcase_strings, @indifferent_strings.transform_keys{ |key| key.to_s.upcase }
57
+ assert_equal @indifferent_upcase_strings, @indifferent_symbols.transform_keys{ |key| key.to_s.upcase }
58
+ assert_equal @indifferent_upcase_strings, @indifferent_mixed.transform_keys{ |key| key.to_s.upcase }
59
+ end
60
+
61
+ def test_indifferent_transform_keys_not_mutates
62
+ transformed_hash = @indifferent_mixed.dup
63
+ transformed_hash.transform_keys{ |key| key.to_s.upcase }
64
+ assert_equal @indifferent_mixed, transformed_hash
65
+ end
66
+
67
+ def test_indifferent_transform_keys!
68
+ assert_equal @indifferent_upcase_strings, @indifferent_symbols.dup.transform_keys!{ |key| key.to_s.upcase }
69
+ assert_equal @indifferent_upcase_strings, @indifferent_strings.dup.transform_keys!{ |key| key.to_s.upcase }
70
+ assert_equal @indifferent_upcase_strings, @indifferent_mixed.dup.transform_keys!{ |key| key.to_s.upcase }
71
+ end
72
+
73
+ def test_indifferent_transform_keys_with_bang_mutates
74
+ transformed_hash = @indifferent_mixed.dup
75
+ transformed_hash.transform_keys!{ |key| key.to_s.upcase }
76
+ assert_equal @indifferent_upcase_strings, transformed_hash
77
+ assert_equal @mixed, { :a => 1, "b" => 2 }
78
+ end
79
+
80
+ def test_indifferent_symbolize_keys
81
+ assert_equal @symbols, @indifferent_symbols.symbolize_keys
82
+ assert_equal @symbols, @indifferent_strings.symbolize_keys
83
+ assert_equal @symbols, @indifferent_mixed.symbolize_keys
84
+ end
85
+
86
+ def test_indifferent_symbolize_keys_not_mutates
87
+ transformed_hash = @indifferent_mixed.dup
88
+ transformed_hash.symbolize_keys
89
+ assert_equal @indifferent_mixed, transformed_hash
90
+ end
91
+
92
+ def test_indifferent_symbolize_keys!
93
+ assert_equal @indifferent_symbols, @indifferent_symbols.dup.symbolize_keys!
94
+ assert_equal @indifferent_symbols, @indifferent_strings.dup.symbolize_keys!
95
+ assert_equal @indifferent_symbols, @indifferent_mixed.dup.symbolize_keys!
96
+ end
97
+
98
+ def test_indifferent_symbolize_keys_preserves_keys_that_cant_be_symbolized
99
+ assert_equal @indifferent_illegal_symbols, @indifferent_illegal_symbols.symbolize_keys
100
+ assert_equal @indifferent_illegal_symbols, @indifferent_illegal_symbols.dup.symbolize_keys!
101
+ end
102
+
103
+ def test_indifferent_stringify_keys
104
+ assert_equal @indifferent_strings, @indifferent_symbols.stringify_keys
105
+ assert_equal @indifferent_strings, @indifferent_strings.stringify_keys
106
+ assert_equal @indifferent_strings, @indifferent_mixed.stringify_keys
107
+ end
108
+
109
+ def test_indifferent_stringify_keys_not_mutates
110
+ transformed_hash = @indifferent_mixed.dup
111
+ transformed_hash.stringify_keys
112
+ assert_equal @indifferent_mixed, transformed_hash
113
+ end
114
+
115
+ def test_indifferent_stringify_keys!
116
+ assert_equal @indifferent_strings, @indifferent_symbols.dup.stringify_keys!
117
+ assert_equal @indifferent_strings, @indifferent_strings.dup.stringify_keys!
118
+ assert_equal @indifferent_strings, @indifferent_mixed.dup.stringify_keys!
119
+ end
120
+
121
+ def test_indifferent_stringify_keys_with_bang_mutates
122
+ transformed_hash = @indifferent_mixed.dup
123
+ transformed_hash.stringify_keys!
124
+ assert_equal @indifferent_strings, transformed_hash
125
+ assert_equal @mixed, { :a => 1, "b" => 2 }
126
+ end
127
+
128
+ def test_indifferent_assorted
129
+ @strings = HashWithIndifferentAccess.new @strings
130
+ @symbols = HashWithIndifferentAccess.new @symbols
131
+ @mixed = HashWithIndifferentAccess.new @mixed
132
+
133
+ assert_equal 'a', @strings.__send__(:convert_key, :a)
134
+
135
+ assert_equal 1, @strings.fetch('a')
136
+ assert_equal 1, @strings.fetch(:a.to_s)
137
+ assert_equal 1, @strings.fetch(:a)
138
+
139
+ hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
140
+ method_map = { :'[]' => 1, :fetch => 1, :values_at => [1],
141
+ :has_key? => true, :include? => true, :key? => true,
142
+ :member? => true }
143
+
144
+ hashes.each do |name, hash|
145
+ method_map.sort_by { |m| m.to_s }.each do |meth, expected|
146
+ assert_equal(expected, hash.__send__(meth, 'a'),
147
+ "Calling #{name}.#{meth} 'a'")
148
+ assert_equal(expected, hash.__send__(meth, :a),
149
+ "Calling #{name}.#{meth} :a")
150
+ end
151
+ end
152
+
153
+ assert_equal [1, 2], @strings.values_at('a', 'b')
154
+ assert_equal [1, 2], @strings.values_at(:a, :b)
155
+ assert_equal [1, 2], @symbols.values_at('a', 'b')
156
+ assert_equal [1, 2], @symbols.values_at(:a, :b)
157
+ assert_equal [1, 2], @mixed.values_at('a', 'b')
158
+ assert_equal [1, 2], @mixed.values_at(:a, :b)
159
+ end
160
+
161
+ def test_indifferent_reading
162
+ hash = HashWithIndifferentAccess.new
163
+ hash["a"] = 1
164
+ hash["b"] = true
165
+ hash["c"] = false
166
+ hash["d"] = nil
167
+
168
+ assert_equal 1, hash[:a]
169
+ assert_equal true, hash[:b]
170
+ assert_equal false, hash[:c]
171
+ assert_equal nil, hash[:d]
172
+ assert_equal nil, hash[:e]
173
+ end
174
+
175
+
176
+ def test_indifferent_reading_with_nonnil_default
177
+ hash = HashWithIndifferentAccess.new(1)
178
+ hash["a"] = 1
179
+ hash["b"] = true
180
+ hash["c"] = false
181
+ hash["d"] = nil
182
+
183
+ assert_equal 1, hash[:a]
184
+ assert_equal true, hash[:b]
185
+ assert_equal false, hash[:c]
186
+ assert_equal nil, hash[:d]
187
+ assert_equal 1, hash[:e]
188
+ end
189
+
190
+ def test_indifferent_writing
191
+ hash = HashWithIndifferentAccess.new
192
+ hash[:a] = 1
193
+ hash['b'] = 2
194
+ hash[3] = 3
195
+
196
+ assert_equal hash['a'], 1
197
+ assert_equal hash['b'], 2
198
+ assert_equal hash[:a], 1
199
+ assert_equal hash[:b], 2
200
+ assert_equal hash[3], 3
201
+ end
202
+
203
+ def test_indifferent_update
204
+ hash = HashWithIndifferentAccess.new
205
+ hash[:a] = 'a'
206
+ hash['b'] = 'b'
207
+
208
+ updated_with_strings = hash.update(@strings)
209
+ updated_with_symbols = hash.update(@symbols)
210
+ updated_with_mixed = hash.update(@mixed)
211
+
212
+ assert_equal updated_with_strings[:a], 1
213
+ assert_equal updated_with_strings['a'], 1
214
+ assert_equal updated_with_strings['b'], 2
215
+
216
+ assert_equal updated_with_symbols[:a], 1
217
+ assert_equal updated_with_symbols['b'], 2
218
+ assert_equal updated_with_symbols[:b], 2
219
+
220
+ assert_equal updated_with_mixed[:a], 1
221
+ assert_equal updated_with_mixed['b'], 2
222
+
223
+ assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
224
+ end
225
+
226
+ def test_indifferent_merging
227
+ hash = HashWithIndifferentAccess.new
228
+ hash[:a] = 'failure'
229
+ hash['b'] = 'failure'
230
+
231
+ other = { 'a' => 1, :b => 2 }
232
+
233
+ merged = hash.merge(other)
234
+
235
+ assert_equal HashWithIndifferentAccess, merged.class
236
+ assert_equal 1, merged[:a]
237
+ assert_equal 2, merged['b']
238
+
239
+ hash.update(other)
240
+
241
+ assert_equal 1, hash[:a]
242
+ assert_equal 2, hash['b']
243
+ end
244
+
245
+ def test_indifferent_replace
246
+ hash = HashWithIndifferentAccess.new
247
+ hash[:a] = 42
248
+
249
+ replaced = hash.replace(b: 12)
250
+
251
+ assert hash.key?('b')
252
+ assert !hash.key?(:a)
253
+ assert_equal 12, hash[:b]
254
+ assert_same hash, replaced
255
+ end
256
+
257
+ def test_indifferent_merging_with_block
258
+ hash = HashWithIndifferentAccess.new
259
+ hash[:a] = 1
260
+ hash['b'] = 3
261
+
262
+ other = { 'a' => 4, :b => 2, 'c' => 10 }
263
+
264
+ merged = hash.merge(other) { |key, old, new| old > new ? old : new }
265
+
266
+ assert_equal HashWithIndifferentAccess, merged.class
267
+ assert_equal 4, merged[:a]
268
+ assert_equal 3, merged['b']
269
+ assert_equal 10, merged[:c]
270
+
271
+ other_indifferent = HashWithIndifferentAccess.new('a' => 9, :b => 2)
272
+
273
+ merged = hash.merge(other_indifferent) { |key, old, new| old + new }
274
+
275
+ assert_equal HashWithIndifferentAccess, merged.class
276
+ assert_equal 10, merged[:a]
277
+ assert_equal 5, merged[:b]
278
+ end
279
+
280
+ def test_indifferent_deleting
281
+ get_hash = proc{ HashWithIndifferentAccess.new(:a => 'foo') }
282
+ hash = get_hash.call
283
+ assert_equal hash.delete(:a), 'foo'
284
+ assert_equal hash.delete(:a), nil
285
+ hash = get_hash.call
286
+ assert_equal hash.delete('a'), 'foo'
287
+ assert_equal hash.delete('a'), nil
288
+ end
289
+
290
+ def test_indifferent_select
291
+ hash = HashWithIndifferentAccess.new(@strings).select {|k,v| v == 1}
292
+
293
+ assert_equal({ 'a' => 1 }, hash)
294
+ assert_instance_of HashWithIndifferentAccess, hash
295
+ end
296
+
297
+ def test_indifferent_select_returns_a_hash_when_unchanged
298
+ hash = HashWithIndifferentAccess.new(@strings).select {|k,v| true}
299
+
300
+ assert_instance_of HashWithIndifferentAccess, hash
301
+ end
302
+
303
+ def test_indifferent_select_bang
304
+ indifferent_strings = HashWithIndifferentAccess.new(@strings)
305
+ indifferent_strings.select! {|k,v| v == 1}
306
+
307
+ assert_equal({ 'a' => 1 }, indifferent_strings)
308
+ assert_instance_of HashWithIndifferentAccess, indifferent_strings
309
+ end
310
+
311
+ def test_indifferent_reject
312
+ hash = HashWithIndifferentAccess.new(@strings).reject {|k,v| v != 1}
313
+
314
+ assert_equal({ 'a' => 1 }, hash)
315
+ assert_instance_of HashWithIndifferentAccess, hash
316
+ end
317
+
318
+ def test_indifferent_reject_bang
319
+ indifferent_strings = HashWithIndifferentAccess.new(@strings)
320
+ indifferent_strings.reject! {|k,v| v != 1}
321
+
322
+ assert_equal({ 'a' => 1 }, indifferent_strings)
323
+ assert_instance_of HashWithIndifferentAccess, indifferent_strings
324
+ end
325
+
326
+ def test_indifferent_to_hash
327
+ # Should convert to a Hash with String keys.
328
+ assert_equal @strings, HashWithIndifferentAccess.new(@mixed).to_hash
329
+
330
+ # Should preserve the default value.
331
+ mixed_with_default = @mixed.dup
332
+ mixed_with_default.default = '1234'
333
+ roundtrip = HashWithIndifferentAccess.new(mixed_with_default).to_hash
334
+ assert_equal @strings, roundtrip
335
+ assert_equal '1234', roundtrip.default
336
+
337
+ # Should preserve the default proc.
338
+ mixed_with_default_proc = @mixed.dup
339
+ mixed_with_default_proc.default_proc = -> (h, k) { '1234' }
340
+ roundtrip = HashWithIndifferentAccess.new(mixed_with_default_proc).to_hash
341
+ assert_equal @strings, roundtrip
342
+ assert_equal '1234', roundtrip.default
343
+
344
+ # Should preserve access
345
+ new_to_hash = HashWithIndifferentAccess.new(@nested_mixed).to_hash
346
+ refute_instance_of HashWithIndifferentAccess, new_to_hash
347
+ refute_instance_of HashWithIndifferentAccess, new_to_hash["a"]
348
+ refute_instance_of HashWithIndifferentAccess, new_to_hash["a"]["b"]
349
+ end
350
+
351
+ def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
352
+ hash = HashWithIndifferentAccess.new {|h, k| h[k] = []}
353
+ hash[:a] << 1
354
+
355
+ assert_equal [1], hash[:a]
356
+ end
357
+
358
+ def test_with_indifferent_access_has_no_side_effects_on_existing_hash
359
+ hash = {content: [{:foo => :bar, 'bar' => 'baz'}]}
360
+ HashWithIndifferentAccess.new(hash)
361
+
362
+ assert_equal [:foo, "bar"], hash[:content].first.keys
363
+ end
364
+
365
+ def test_indifferent_hash_with_array_of_hashes
366
+ hash = HashWithIndifferentAccess.new( "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] })
367
+ assert_equal "1", hash[:urls][:url].first[:address]
368
+
369
+ hash = hash.to_hash
370
+ refute_instance_of HashWithIndifferentAccess, hash
371
+ refute_instance_of HashWithIndifferentAccess, hash["urls"]
372
+ refute_instance_of HashWithIndifferentAccess, hash["urls"]["url"].first
373
+ end
374
+
375
+ def test_should_preserve_array_subclass_when_value_is_array
376
+ array = SubclassingArray.new
377
+ array << { "address" => "1" }
378
+ hash = HashWithIndifferentAccess.new "urls" => { "url" => array }
379
+ assert_equal SubclassingArray, hash[:urls][:url].class
380
+ end
381
+
382
+ def test_should_preserve_array_class_when_hash_value_is_frozen_array
383
+ array = SubclassingArray.new
384
+ array << { "address" => "1" }
385
+ hash = HashWithIndifferentAccess.new "urls" => { "url" => array.freeze }
386
+ assert_equal SubclassingArray, hash[:urls][:url].class
387
+ end
388
+
389
+ def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash
390
+ h = HashWithIndifferentAccess.new
391
+ h[:first] = 1
392
+ h = h.stringify_keys
393
+ assert_equal 1, h['first']
394
+ h = HashWithIndifferentAccess.new
395
+ h['first'] = 1
396
+ h = h.symbolize_keys
397
+ assert_equal 1, h[:first]
398
+ end
399
+
400
+ def test_to_options_on_indifferent_preserves_hash
401
+ h = HashWithIndifferentAccess.new
402
+ h['first'] = 1
403
+ h.to_options!
404
+ assert_equal 1, h['first']
405
+ end
406
+
407
+ def test_indifferent_subhashes
408
+ h = HashWithIndifferentAccess.new 'user' => {'id' => 5}
409
+ ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
410
+
411
+ h = HashWithIndifferentAccess.new :user => {:id => 5}
412
+ ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
413
+ end
414
+
415
+ def test_indifferent_duplication
416
+ # Should preserve default value
417
+ h = HashWithIndifferentAccess.new
418
+ h.default = '1234'
419
+ assert_equal h.default, h.dup.default
420
+
421
+ # Should preserve class for subclasses
422
+ h = IndifferentHash.new
423
+ assert_equal h.class, h.dup.class
424
+ end
425
+
426
+ def test_assert_valid_keys
427
+ refute_raise do
428
+ HashWithIndifferentAccess.new(:failure => "stuff", :funny => "business").
429
+ assert_valid_keys([ :failure, :funny ])
430
+ HashWithIndifferentAccess.new(:failure => "stuff", :funny => "business").
431
+ assert_valid_keys(:failure, :funny)
432
+ end
433
+
434
+ assert_raises(ArgumentError, "Unknown key: failore") do
435
+ HashWithIndifferentAccess.new(:failore => "stuff", :funny => "business").
436
+ assert_valid_keys([ :failure, :funny ])
437
+ HashWithIndifferentAccess.new(:failore => "stuff", :funny => "business").
438
+ assert_valid_keys(:failure, :funny)
439
+ end
440
+ end
441
+
442
+ def test_store_on_indifferent_access
443
+ hash = HashWithIndifferentAccess.new
444
+ hash.store(:test1, 1)
445
+ hash.store('test1', 11)
446
+ hash[:test2] = 2
447
+ hash['test2'] = 22
448
+ expected = { "test1" => 11, "test2" => 22 }
449
+ assert_equal expected, hash
450
+ end
451
+
452
+ def test_constructor_on_indifferent_access
453
+ hash = HashWithIndifferentAccess[:foo, 1]
454
+ assert_equal 1, hash[:foo]
455
+ assert_equal 1, hash['foo']
456
+ hash[:foo] = 3
457
+ assert_equal 3, hash[:foo]
458
+ assert_equal 3, hash['foo']
459
+ end
460
+
461
+ def test_should_use_default_value_for_unknown_key
462
+ hash_wia = HashWithIndifferentAccess.new(3)
463
+ assert_equal 3, hash_wia[:new_key]
464
+ end
465
+
466
+ def test_should_use_default_value_if_no_key_is_supplied
467
+ hash_wia = HashWithIndifferentAccess.new(3)
468
+ assert_equal 3, hash_wia.default
469
+ end
470
+
471
+ def test_should_nil_if_no_default_value_is_supplied
472
+ hash_wia = HashWithIndifferentAccess.new
473
+ assert_nil hash_wia.default
474
+ end
475
+
476
+ def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
477
+ hash = Hash.new(3)
478
+ hash_wia = HashWithIndifferentAccess.new(hash)
479
+ assert_equal 3, hash_wia.default
480
+ end
481
+
482
+ end