inquisitive 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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