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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.md +24 -0
- data/README.md +261 -0
- data/Rakefile +8 -0
- data/inquisitive.gemspec +23 -0
- data/lib/inquisitive.rb +48 -0
- data/lib/inquisitive/array.rb +24 -0
- data/lib/inquisitive/environment.rb +89 -0
- data/lib/inquisitive/hash.rb +39 -0
- data/lib/inquisitive/hash_with_indifferent_access.rb +185 -0
- data/lib/inquisitive/string.rb +24 -0
- data/test/inquisitive/array_test.rb +11 -0
- data/test/inquisitive/combinatorial_environment_test.rb +72 -0
- data/test/inquisitive/environment_test.rb +44 -0
- data/test/inquisitive/hash_test.rb +20 -0
- data/test/inquisitive/hash_with_indifferent_access_test.rb +482 -0
- data/test/inquisitive/string_test.rb +11 -0
- data/test/inquisitive_test.rb +151 -0
- data/test/shared/array_tests.rb +32 -0
- data/test/shared/combinatorial_environment_tests.rb +33 -0
- data/test/shared/hash_tests.rb +26 -0
- data/test/shared/string_tests.rb +32 -0
- data/test/test_helper.rb +67 -0
- metadata +136 -0
@@ -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,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
|