hashie 4.0.0 → 4.1.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 +4 -4
- data/CHANGELOG.md +213 -187
- data/CONTRIBUTING.md +13 -6
- data/README.md +33 -9
- data/UPGRADING.md +5 -5
- data/hashie.gemspec +11 -6
- data/lib/hashie.rb +1 -0
- data/lib/hashie/extensions/dash/property_translation.rb +1 -1
- data/lib/hashie/extensions/deep_merge.rb +18 -1
- data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
- data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +19 -2
- data/lib/hashie/extensions/ruby_version_check.rb +5 -1
- data/lib/hashie/mash.rb +31 -26
- data/lib/hashie/utils.rb +28 -0
- data/lib/hashie/version.rb +1 -1
- metadata +16 -131
- data/spec/hashie/array_spec.rb +0 -29
- data/spec/hashie/clash_spec.rb +0 -70
- data/spec/hashie/dash_spec.rb +0 -608
- data/spec/hashie/extensions/autoload_spec.rb +0 -24
- data/spec/hashie/extensions/coercion_spec.rb +0 -648
- data/spec/hashie/extensions/dash/coercion_spec.rb +0 -13
- data/spec/hashie/extensions/dash/indifferent_access_spec.rb +0 -84
- data/spec/hashie/extensions/deep_fetch_spec.rb +0 -97
- data/spec/hashie/extensions/deep_find_spec.rb +0 -144
- data/spec/hashie/extensions/deep_locate_spec.rb +0 -138
- data/spec/hashie/extensions/deep_merge_spec.rb +0 -74
- data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -48
- data/spec/hashie/extensions/indifferent_access_spec.rb +0 -295
- data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +0 -208
- data/spec/hashie/extensions/key_conversion_spec.rb +0 -12
- data/spec/hashie/extensions/mash/define_accessors_spec.rb +0 -90
- data/spec/hashie/extensions/mash/keep_original_keys_spec.rb +0 -46
- data/spec/hashie/extensions/mash/safe_assignment_spec.rb +0 -50
- data/spec/hashie/extensions/mash/symbolize_keys_spec.rb +0 -39
- data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
- data/spec/hashie/extensions/method_access_spec.rb +0 -233
- data/spec/hashie/extensions/strict_key_access_spec.rb +0 -109
- data/spec/hashie/extensions/stringify_keys_spec.rb +0 -124
- data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -131
- data/spec/hashie/hash_spec.rb +0 -123
- data/spec/hashie/mash_spec.rb +0 -1077
- data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -46
- data/spec/hashie/rash_spec.rb +0 -83
- data/spec/hashie/trash_spec.rb +0 -334
- data/spec/hashie/utils_spec.rb +0 -25
- data/spec/hashie/version_spec.rb +0 -7
- data/spec/hashie_spec.rb +0 -13
- data/spec/integration/elasticsearch/integration_spec.rb +0 -41
- data/spec/integration/omniauth-oauth2/app.rb +0 -52
- data/spec/integration/omniauth-oauth2/integration_spec.rb +0 -26
- data/spec/integration/omniauth-oauth2/some_site.rb +0 -38
- data/spec/integration/omniauth/app.rb +0 -11
- data/spec/integration/omniauth/integration_spec.rb +0 -38
- data/spec/integration/rails-without-dependency/integration_spec.rb +0 -15
- data/spec/integration/rails/app.rb +0 -40
- data/spec/integration/rails/integration_spec.rb +0 -47
- data/spec/spec_helper.rb +0 -23
- data/spec/support/integration_specs.rb +0 -36
- data/spec/support/logger.rb +0 -24
- data/spec/support/module_context.rb +0 -11
- data/spec/support/ruby_version_check.rb +0 -6
@@ -1,24 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'hashie'
|
3
|
-
|
4
|
-
describe Hashie::Extensions do
|
5
|
-
describe 'autloads constants' do
|
6
|
-
it { is_expected.to be_const_defined(:MethodAccess) }
|
7
|
-
it { is_expected.to be_const_defined(:Coercion) }
|
8
|
-
it { is_expected.to be_const_defined(:DeepMerge) }
|
9
|
-
it { is_expected.to be_const_defined(:IgnoreUndeclared) }
|
10
|
-
it { is_expected.to be_const_defined(:IndifferentAccess) }
|
11
|
-
it { is_expected.to be_const_defined(:MergeInitializer) }
|
12
|
-
it { is_expected.to be_const_defined(:MethodAccess) }
|
13
|
-
it { is_expected.to be_const_defined(:MethodQuery) }
|
14
|
-
it { is_expected.to be_const_defined(:MethodReader) }
|
15
|
-
it { is_expected.to be_const_defined(:MethodWriter) }
|
16
|
-
it { is_expected.to be_const_defined(:StringifyKeys) }
|
17
|
-
it { is_expected.to be_const_defined(:SymbolizeKeys) }
|
18
|
-
it { is_expected.to be_const_defined(:DeepFetch) }
|
19
|
-
it { is_expected.to be_const_defined(:DeepFind) }
|
20
|
-
it { is_expected.to be_const_defined(:PrettyInspect) }
|
21
|
-
it { is_expected.to be_const_defined(:KeyConversion) }
|
22
|
-
it { is_expected.to be_const_defined(:MethodAccessWithOverride) }
|
23
|
-
end
|
24
|
-
end
|
@@ -1,648 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Hashie::Extensions::Coercion do
|
4
|
-
class NotInitializable
|
5
|
-
private_class_method :new
|
6
|
-
end
|
7
|
-
|
8
|
-
class Initializable
|
9
|
-
attr_reader :coerced, :value
|
10
|
-
|
11
|
-
def initialize(obj, coerced = nil)
|
12
|
-
@coerced = coerced
|
13
|
-
@value = obj.class.to_s
|
14
|
-
end
|
15
|
-
|
16
|
-
def coerced?
|
17
|
-
!@coerced.nil?
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
class Coercable < Initializable
|
22
|
-
def self.coerce(obj)
|
23
|
-
new(obj, true)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
before(:each) do
|
28
|
-
class ExampleCoercableHash < Hash
|
29
|
-
include Hashie::Extensions::Coercion
|
30
|
-
include Hashie::Extensions::MergeInitializer
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
subject { ExampleCoercableHash }
|
35
|
-
|
36
|
-
let(:instance) { subject.new }
|
37
|
-
|
38
|
-
describe '#coerce_key' do
|
39
|
-
context 'nesting' do
|
40
|
-
class BaseCoercableHash < Hash
|
41
|
-
include Hashie::Extensions::Coercion
|
42
|
-
include Hashie::Extensions::MergeInitializer
|
43
|
-
end
|
44
|
-
|
45
|
-
class NestedCoercableHash < BaseCoercableHash
|
46
|
-
coerce_key :foo, String
|
47
|
-
coerce_key :bar, Integer
|
48
|
-
end
|
49
|
-
|
50
|
-
class OtherNestedCoercableHash < BaseCoercableHash
|
51
|
-
coerce_key :foo, Symbol
|
52
|
-
end
|
53
|
-
|
54
|
-
class RootCoercableHash < BaseCoercableHash
|
55
|
-
coerce_key :nested, NestedCoercableHash
|
56
|
-
coerce_key :other, OtherNestedCoercableHash
|
57
|
-
coerce_key :nested_list, Array[NestedCoercableHash]
|
58
|
-
coerce_key :nested_hash, Hash[String => NestedCoercableHash]
|
59
|
-
end
|
60
|
-
|
61
|
-
def test_nested_object(obj)
|
62
|
-
expect(obj).to be_a(NestedCoercableHash)
|
63
|
-
expect(obj[:foo]).to be_a(String)
|
64
|
-
expect(obj[:bar]).to be_an(Integer)
|
65
|
-
end
|
66
|
-
|
67
|
-
subject { RootCoercableHash }
|
68
|
-
let(:instance) { subject.new }
|
69
|
-
|
70
|
-
it 'does not add coercions to superclass' do
|
71
|
-
instance[:nested] = { foo: 'bar' }
|
72
|
-
instance[:other] = { foo: 'bar' }
|
73
|
-
expect(instance[:nested][:foo]).to be_a String
|
74
|
-
expect(instance[:other][:foo]).to be_a Symbol
|
75
|
-
end
|
76
|
-
|
77
|
-
it 'coerces nested objects' do
|
78
|
-
instance[:nested] = { foo: 123, bar: '456' }
|
79
|
-
test_nested_object(instance[:nested])
|
80
|
-
end
|
81
|
-
|
82
|
-
it 'coerces nested arrays' do
|
83
|
-
instance[:nested_list] = [
|
84
|
-
{ foo: 123, bar: '456' },
|
85
|
-
{ foo: 234, bar: '567' },
|
86
|
-
{ foo: 345, bar: '678' }
|
87
|
-
]
|
88
|
-
expect(instance[:nested_list]).to be_a Array
|
89
|
-
expect(instance[:nested_list].size).to eq(3)
|
90
|
-
instance[:nested_list].each do |nested|
|
91
|
-
test_nested_object nested
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
it 'coerces nested hashes' do
|
96
|
-
instance[:nested_hash] = {
|
97
|
-
a: { foo: 123, bar: '456' },
|
98
|
-
b: { foo: 234, bar: '567' },
|
99
|
-
c: { foo: 345, bar: '678' }
|
100
|
-
}
|
101
|
-
expect(instance[:nested_hash]).to be_a Hash
|
102
|
-
expect(instance[:nested_hash].size).to eq(3)
|
103
|
-
instance[:nested_hash].each do |key, nested|
|
104
|
-
expect(key).to be_a(String)
|
105
|
-
test_nested_object nested
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
context 'when repetitively including the module' do
|
110
|
-
class RepetitiveCoercableHash < NestedCoercableHash
|
111
|
-
include Hashie::Extensions::Coercion
|
112
|
-
include Hashie::Extensions::MergeInitializer
|
113
|
-
|
114
|
-
coerce_key :nested, NestedCoercableHash
|
115
|
-
end
|
116
|
-
|
117
|
-
subject { RepetitiveCoercableHash }
|
118
|
-
let(:instance) { subject.new }
|
119
|
-
|
120
|
-
it 'does not raise a stack overflow error' do
|
121
|
-
expect do
|
122
|
-
instance[:nested] = { foo: 123, bar: '456' }
|
123
|
-
test_nested_object(instance[:nested])
|
124
|
-
end.not_to raise_error
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
it { expect(subject).to be_respond_to(:coerce_key) }
|
130
|
-
|
131
|
-
it 'runs through coerce on a specified key' do
|
132
|
-
subject.coerce_key :foo, Coercable
|
133
|
-
|
134
|
-
instance[:foo] = 'bar'
|
135
|
-
expect(instance[:foo]).to be_coerced
|
136
|
-
end
|
137
|
-
|
138
|
-
it 'skips unnecessary coercions' do
|
139
|
-
subject.coerce_key :foo, Coercable
|
140
|
-
|
141
|
-
instance[:foo] = Coercable.new('bar')
|
142
|
-
expect(instance[:foo]).to_not be_coerced
|
143
|
-
end
|
144
|
-
|
145
|
-
it 'supports an array of keys' do
|
146
|
-
subject.coerce_keys :foo, :bar, Coercable
|
147
|
-
|
148
|
-
instance[:foo] = 'bar'
|
149
|
-
instance[:bar] = 'bax'
|
150
|
-
expect(instance[:foo]).to be_coerced
|
151
|
-
expect(instance[:bar]).to be_coerced
|
152
|
-
end
|
153
|
-
|
154
|
-
it 'supports coercion for Array' do
|
155
|
-
subject.coerce_key :foo, Array[Coercable]
|
156
|
-
|
157
|
-
instance[:foo] = %w[bar bar2]
|
158
|
-
expect(instance[:foo]).to all(be_coerced)
|
159
|
-
expect(instance[:foo]).to be_a(Array)
|
160
|
-
end
|
161
|
-
|
162
|
-
it 'supports coercion for Set' do
|
163
|
-
subject.coerce_key :foo, Set[Coercable]
|
164
|
-
|
165
|
-
instance[:foo] = Set.new(%w[bar bar2])
|
166
|
-
expect(instance[:foo]).to all(be_coerced)
|
167
|
-
expect(instance[:foo]).to be_a(Set)
|
168
|
-
end
|
169
|
-
|
170
|
-
it 'supports coercion for Set of primitive' do
|
171
|
-
subject.coerce_key :foo, Set[Initializable]
|
172
|
-
|
173
|
-
instance[:foo] = %w[bar bar2]
|
174
|
-
expect(instance[:foo].map(&:value)).to all(eq 'String')
|
175
|
-
expect(instance[:foo]).to be_none(&:coerced?)
|
176
|
-
expect(instance[:foo]).to be_a(Set)
|
177
|
-
end
|
178
|
-
|
179
|
-
it 'supports coercion for Hash' do
|
180
|
-
subject.coerce_key :foo, Hash[Coercable => Coercable]
|
181
|
-
|
182
|
-
instance[:foo] = { 'bar_key' => 'bar_value', 'bar2_key' => 'bar2_value' }
|
183
|
-
expect(instance[:foo].keys).to all(be_coerced)
|
184
|
-
expect(instance[:foo].values).to all(be_coerced)
|
185
|
-
expect(instance[:foo]).to be_a(Hash)
|
186
|
-
end
|
187
|
-
|
188
|
-
it 'supports coercion for Hash with primitive as value' do
|
189
|
-
subject.coerce_key :foo, Hash[Coercable => Initializable]
|
190
|
-
|
191
|
-
instance[:foo] = { 'bar_key' => '1', 'bar2_key' => '2' }
|
192
|
-
expect(instance[:foo].values.map(&:value)).to all(eq 'String')
|
193
|
-
expect(instance[:foo].keys).to all(be_coerced)
|
194
|
-
end
|
195
|
-
|
196
|
-
context 'coercing core types' do
|
197
|
-
def test_coercion(literal, target_type, coerce_method)
|
198
|
-
subject.coerce_key :foo, target_type
|
199
|
-
instance[:foo] = literal
|
200
|
-
expect(instance[:foo]).to be_a(target_type)
|
201
|
-
expect(instance[:foo]).to eq(literal.send(coerce_method))
|
202
|
-
end
|
203
|
-
|
204
|
-
RSpec.shared_examples 'coerces from numeric types' do |target_type, coerce_method|
|
205
|
-
it "coerces from String to #{target_type} via #{coerce_method}" do
|
206
|
-
test_coercion '2.0', target_type, coerce_method
|
207
|
-
end
|
208
|
-
|
209
|
-
it "coerces from Integer to #{target_type} via #{coerce_method}" do
|
210
|
-
# Fixnum
|
211
|
-
test_coercion 2, target_type, coerce_method
|
212
|
-
# Bignum
|
213
|
-
test_coercion 12_345_667_890_987_654_321, target_type, coerce_method
|
214
|
-
end
|
215
|
-
|
216
|
-
it "coerces from Rational to #{target_type} via #{coerce_method}" do
|
217
|
-
test_coercion Rational(2, 3), target_type, coerce_method
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
RSpec.shared_examples 'coerces from alphabetical types' do |target_type, coerce_method|
|
222
|
-
it "coerces from String to #{target_type} via #{coerce_method}" do
|
223
|
-
test_coercion 'abc', target_type, coerce_method
|
224
|
-
end
|
225
|
-
|
226
|
-
it "coerces from Symbol to #{target_type} via #{coerce_method}" do
|
227
|
-
test_coercion :abc, target_type, coerce_method
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
include_examples 'coerces from numeric types', Integer, :to_i
|
232
|
-
include_examples 'coerces from numeric types', Float, :to_f
|
233
|
-
include_examples 'coerces from numeric types', String, :to_s
|
234
|
-
|
235
|
-
include_examples 'coerces from alphabetical types', String, :to_s
|
236
|
-
include_examples 'coerces from alphabetical types', Symbol, :to_sym
|
237
|
-
|
238
|
-
it 'can coerce String to Rational when possible' do
|
239
|
-
test_coercion '2/3', Rational, :to_r
|
240
|
-
end
|
241
|
-
|
242
|
-
it 'can coerce String to Complex when possible' do
|
243
|
-
test_coercion '2/3+3/4i', Complex, :to_c
|
244
|
-
end
|
245
|
-
|
246
|
-
it 'coerces collections with core types' do
|
247
|
-
subject.coerce_key :foo, Hash[String => String]
|
248
|
-
|
249
|
-
instance[:foo] = {
|
250
|
-
abc: 123,
|
251
|
-
xyz: 987
|
252
|
-
}
|
253
|
-
expect(instance[:foo]).to eq(
|
254
|
-
'abc' => '123',
|
255
|
-
'xyz' => '987'
|
256
|
-
)
|
257
|
-
end
|
258
|
-
|
259
|
-
it 'can coerce via a proc' do
|
260
|
-
subject.coerce_key(:foo, lambda do |v|
|
261
|
-
case v
|
262
|
-
when String
|
263
|
-
return !!(v =~ /^(true|t|yes|y|1)$/i)
|
264
|
-
when Numeric
|
265
|
-
return !v.to_i.zero?
|
266
|
-
else
|
267
|
-
return v == true
|
268
|
-
end
|
269
|
-
end)
|
270
|
-
|
271
|
-
true_values = [true, 'true', 't', 'yes', 'y', '1', 1, -1]
|
272
|
-
false_values = [false, 'false', 'f', 'no', 'n', '0', 0]
|
273
|
-
|
274
|
-
true_values.each do |v|
|
275
|
-
instance[:foo] = v
|
276
|
-
expect(instance[:foo]).to be_a(TrueClass)
|
277
|
-
end
|
278
|
-
false_values.each do |v|
|
279
|
-
instance[:foo] = v
|
280
|
-
expect(instance[:foo]).to be_a(FalseClass)
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
it 'raises errors for non-coercable types' do
|
285
|
-
subject.coerce_key :foo, NotInitializable
|
286
|
-
expect { instance[:foo] = 'true' }
|
287
|
-
.to raise_error(Hashie::CoercionError, /NotInitializable is not a coercable type/)
|
288
|
-
end
|
289
|
-
|
290
|
-
it 'can coerce false' do
|
291
|
-
subject.coerce_key :foo, Coercable
|
292
|
-
|
293
|
-
instance[:foo] = false
|
294
|
-
expect(instance[:foo]).to be_coerced
|
295
|
-
expect(instance[:foo].value).to eq('FalseClass')
|
296
|
-
end
|
297
|
-
|
298
|
-
it 'does not coerce nil' do
|
299
|
-
subject.coerce_key :foo, String
|
300
|
-
|
301
|
-
instance[:foo] = nil
|
302
|
-
expect(instance[:foo]).to_not eq('')
|
303
|
-
expect(instance[:foo]).to be_nil
|
304
|
-
end
|
305
|
-
end
|
306
|
-
|
307
|
-
it 'calls #new if no coerce method is available' do
|
308
|
-
subject.coerce_key :foo, Initializable
|
309
|
-
|
310
|
-
instance[:foo] = 'bar'
|
311
|
-
expect(instance[:foo].value).to eq 'String'
|
312
|
-
expect(instance[:foo]).not_to be_coerced
|
313
|
-
end
|
314
|
-
|
315
|
-
it 'coerces when the merge initializer is used' do
|
316
|
-
subject.coerce_key :foo, Coercable
|
317
|
-
instance = subject.new(foo: 'bar')
|
318
|
-
|
319
|
-
expect(instance[:foo]).to be_coerced
|
320
|
-
end
|
321
|
-
|
322
|
-
context 'when #replace is used' do
|
323
|
-
before { subject.coerce_key :foo, :bar, Coercable }
|
324
|
-
|
325
|
-
let(:instance) do
|
326
|
-
subject.new(foo: 'bar').replace(foo: 'foz', bar: 'baz', hi: 'bye')
|
327
|
-
end
|
328
|
-
|
329
|
-
it 'coerces relevant keys' do
|
330
|
-
expect(instance[:foo]).to be_coerced
|
331
|
-
expect(instance[:bar]).to be_coerced
|
332
|
-
expect(instance[:hi]).not_to respond_to(:coerced?)
|
333
|
-
end
|
334
|
-
|
335
|
-
it 'sets correct values' do
|
336
|
-
expect(instance[:hi]).to eq 'bye'
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
context 'when used with a Mash' do
|
341
|
-
class UserMash < Hashie::Mash
|
342
|
-
end
|
343
|
-
class TweetMash < Hashie::Mash
|
344
|
-
include Hashie::Extensions::Coercion
|
345
|
-
coerce_key :user, UserMash
|
346
|
-
end
|
347
|
-
|
348
|
-
it 'coerces with instance initialization' do
|
349
|
-
tweet = TweetMash.new(user: { email: 'foo@bar.com' })
|
350
|
-
expect(tweet[:user]).to be_a(UserMash)
|
351
|
-
end
|
352
|
-
|
353
|
-
it 'coerces when setting with attribute style' do
|
354
|
-
tweet = TweetMash.new
|
355
|
-
tweet.user = { email: 'foo@bar.com' }
|
356
|
-
expect(tweet[:user]).to be_a(UserMash)
|
357
|
-
end
|
358
|
-
|
359
|
-
it 'coerces when setting with string index' do
|
360
|
-
tweet = TweetMash.new
|
361
|
-
tweet['user'] = { email: 'foo@bar.com' }
|
362
|
-
expect(tweet[:user]).to be_a(UserMash)
|
363
|
-
end
|
364
|
-
|
365
|
-
it 'coerces when setting with symbol index' do
|
366
|
-
tweet = TweetMash.new
|
367
|
-
tweet[:user] = { email: 'foo@bar.com' }
|
368
|
-
expect(tweet[:user]).to be_a(UserMash)
|
369
|
-
end
|
370
|
-
end
|
371
|
-
|
372
|
-
context 'when used with a Trash' do
|
373
|
-
class UserTrash < Hashie::Trash
|
374
|
-
property :email
|
375
|
-
end
|
376
|
-
class TweetTrash < Hashie::Trash
|
377
|
-
include Hashie::Extensions::Coercion
|
378
|
-
|
379
|
-
property :user, from: :user_data
|
380
|
-
coerce_key :user, UserTrash
|
381
|
-
end
|
382
|
-
|
383
|
-
it 'coerces with instance initialization' do
|
384
|
-
tweet = TweetTrash.new(user_data: { email: 'foo@bar.com' })
|
385
|
-
expect(tweet[:user]).to be_a(UserTrash)
|
386
|
-
end
|
387
|
-
end
|
388
|
-
|
389
|
-
context 'when used with IndifferentAccess to coerce a Mash' do
|
390
|
-
class MyHash < Hash
|
391
|
-
include Hashie::Extensions::Coercion
|
392
|
-
include Hashie::Extensions::IndifferentAccess
|
393
|
-
include Hashie::Extensions::MergeInitializer
|
394
|
-
end
|
395
|
-
|
396
|
-
class UserHash < MyHash
|
397
|
-
end
|
398
|
-
|
399
|
-
class TweetHash < MyHash
|
400
|
-
coerce_key :user, UserHash
|
401
|
-
end
|
402
|
-
|
403
|
-
it 'coerces with instance initialization' do
|
404
|
-
tweet = TweetHash.new(user: Hashie::Mash.new(email: 'foo@bar.com'))
|
405
|
-
expect(tweet[:user]).to be_a(UserHash)
|
406
|
-
end
|
407
|
-
|
408
|
-
it 'coerces when setting with string index' do
|
409
|
-
tweet = TweetHash.new
|
410
|
-
tweet['user'] = Hashie::Mash.new(email: 'foo@bar.com')
|
411
|
-
expect(tweet[:user]).to be_a(UserHash)
|
412
|
-
end
|
413
|
-
|
414
|
-
it 'coerces when setting with symbol index' do
|
415
|
-
tweet = TweetHash.new
|
416
|
-
tweet[:user] = Hashie::Mash.new(email: 'foo@bar.com')
|
417
|
-
expect(tweet[:user]).to be_a(UserHash)
|
418
|
-
end
|
419
|
-
end
|
420
|
-
|
421
|
-
context 'when subclassing' do
|
422
|
-
class MyOwnBase < Hash
|
423
|
-
include Hashie::Extensions::Coercion
|
424
|
-
end
|
425
|
-
|
426
|
-
class MyOwnHash < MyOwnBase
|
427
|
-
coerce_key :value, Integer
|
428
|
-
end
|
429
|
-
|
430
|
-
class MyOwnSubclass < MyOwnHash
|
431
|
-
end
|
432
|
-
|
433
|
-
it 'inherits key coercions' do
|
434
|
-
expect(MyOwnHash.key_coercions).to eql(MyOwnSubclass.key_coercions)
|
435
|
-
end
|
436
|
-
|
437
|
-
it 'the superclass does not accumulate coerced attributes from subclasses' do
|
438
|
-
expect(MyOwnBase.key_coercions).to eq({})
|
439
|
-
end
|
440
|
-
end
|
441
|
-
|
442
|
-
context 'when using circular coercion' do
|
443
|
-
context 'with a proc on one side' do
|
444
|
-
class CategoryHash < Hash
|
445
|
-
include Hashie::Extensions::Coercion
|
446
|
-
include Hashie::Extensions::MergeInitializer
|
447
|
-
|
448
|
-
coerce_key :products, lambda { |value|
|
449
|
-
return value.map { |v| ProductHash.new(v) } if value.respond_to?(:map)
|
450
|
-
|
451
|
-
ProductHash.new(v)
|
452
|
-
}
|
453
|
-
end
|
454
|
-
|
455
|
-
class ProductHash < Hash
|
456
|
-
include Hashie::Extensions::Coercion
|
457
|
-
include Hashie::Extensions::MergeInitializer
|
458
|
-
|
459
|
-
coerce_key :categories, Array[CategoryHash]
|
460
|
-
end
|
461
|
-
|
462
|
-
let(:category) do
|
463
|
-
CategoryHash.new(type: 'rubygem', products: [Hashie::Mash.new(name: 'Hashie')])
|
464
|
-
end
|
465
|
-
let(:product) do
|
466
|
-
ProductHash.new(name: 'Hashie', categories: [Hashie::Mash.new(type: 'rubygem')])
|
467
|
-
end
|
468
|
-
|
469
|
-
it 'coerces CategoryHash[:products] correctly' do
|
470
|
-
expected = [ProductHash]
|
471
|
-
actual = category[:products].map(&:class)
|
472
|
-
|
473
|
-
expect(actual).to eq(expected)
|
474
|
-
end
|
475
|
-
|
476
|
-
it 'coerces ProductHash[:categories] correctly' do
|
477
|
-
expected = [CategoryHash]
|
478
|
-
actual = product[:categories].map(&:class)
|
479
|
-
|
480
|
-
expect(actual).to eq(expected)
|
481
|
-
end
|
482
|
-
end
|
483
|
-
|
484
|
-
context 'without a proc on either side' do
|
485
|
-
it 'fails with a NameError since the other class is not defined yet' do
|
486
|
-
attempted_code = lambda do
|
487
|
-
class AnotherCategoryHash < Hash
|
488
|
-
include Hashie::Extensions::Coercion
|
489
|
-
include Hashie::Extensions::MergeInitializer
|
490
|
-
|
491
|
-
coerce_key :products, Array[AnotherProductHash]
|
492
|
-
end
|
493
|
-
|
494
|
-
class AnotherProductHash < Hash
|
495
|
-
include Hashie::Extensions::Coercion
|
496
|
-
include Hashie::Extensions::MergeInitializer
|
497
|
-
|
498
|
-
coerce_key :categories, Array[AnotherCategoryHash]
|
499
|
-
end
|
500
|
-
end
|
501
|
-
|
502
|
-
expect { attempted_code.call }.to raise_error(NameError)
|
503
|
-
end
|
504
|
-
end
|
505
|
-
end
|
506
|
-
end
|
507
|
-
|
508
|
-
describe '#coerce_value' do
|
509
|
-
context 'with strict: true' do
|
510
|
-
it 'coerces any value of the exact right class' do
|
511
|
-
subject.coerce_value String, Coercable
|
512
|
-
|
513
|
-
instance[:foo] = 'bar'
|
514
|
-
instance[:bar] = 'bax'
|
515
|
-
instance[:hi] = :bye
|
516
|
-
expect(instance[:foo]).to be_coerced
|
517
|
-
expect(instance[:bar]).to be_coerced
|
518
|
-
expect(instance[:hi]).not_to respond_to(:coerced?)
|
519
|
-
end
|
520
|
-
|
521
|
-
it 'coerces values from a #replace call' do
|
522
|
-
subject.coerce_value String, Coercable
|
523
|
-
|
524
|
-
instance[:foo] = :bar
|
525
|
-
instance.replace(foo: 'bar', bar: 'bax')
|
526
|
-
expect(instance[:foo]).to be_coerced
|
527
|
-
expect(instance[:bar]).to be_coerced
|
528
|
-
end
|
529
|
-
|
530
|
-
it 'does not coerce superclasses' do
|
531
|
-
klass = Class.new(String)
|
532
|
-
subject.coerce_value klass, Coercable
|
533
|
-
|
534
|
-
instance[:foo] = 'bar'
|
535
|
-
expect(instance[:foo]).not_to be_kind_of(Coercable)
|
536
|
-
instance[:foo] = klass.new
|
537
|
-
expect(instance[:foo]).to be_kind_of(Coercable)
|
538
|
-
end
|
539
|
-
end
|
540
|
-
|
541
|
-
context 'core types' do
|
542
|
-
it 'coerces String to Integer when possible' do
|
543
|
-
subject.coerce_value String, Integer
|
544
|
-
|
545
|
-
instance[:foo] = '2'
|
546
|
-
instance[:bar] = '2.7'
|
547
|
-
instance[:hi] = 'hi'
|
548
|
-
expect(instance[:foo]).to be_a(Integer)
|
549
|
-
expect(instance[:foo]).to eq(2)
|
550
|
-
expect(instance[:bar]).to be_a(Integer)
|
551
|
-
expect(instance[:bar]).to eq(2)
|
552
|
-
expect(instance[:hi]).to be_a(Integer)
|
553
|
-
expect(instance[:hi]).to eq(0) # not what I expected...
|
554
|
-
end
|
555
|
-
|
556
|
-
it 'coerces non-numeric from String to Integer' do
|
557
|
-
# This was surprising, but I guess it's "correct"
|
558
|
-
# unless there is a stricter `to_i` alternative
|
559
|
-
subject.coerce_value String, Integer
|
560
|
-
instance[:hi] = 'hi'
|
561
|
-
expect(instance[:hi]).to be_a(Integer)
|
562
|
-
expect(instance[:hi]).to eq(0)
|
563
|
-
end
|
564
|
-
|
565
|
-
it 'raises a CoercionError when coercion is not possible' do
|
566
|
-
type =
|
567
|
-
if Hashie::Extensions::RubyVersion.new(RUBY_VERSION) >=
|
568
|
-
Hashie::Extensions::RubyVersion.new('2.4.0')
|
569
|
-
Integer
|
570
|
-
else
|
571
|
-
Fixnum
|
572
|
-
end
|
573
|
-
|
574
|
-
subject.coerce_value type, Symbol
|
575
|
-
expect { instance[:hi] = 1 }.to raise_error(
|
576
|
-
Hashie::CoercionError, /Cannot coerce property :hi from #{type} to Symbol/
|
577
|
-
)
|
578
|
-
end
|
579
|
-
|
580
|
-
it 'coerces Integer to String' do
|
581
|
-
type =
|
582
|
-
if Hashie::Extensions::RubyVersion.new(RUBY_VERSION) >=
|
583
|
-
Hashie::Extensions::RubyVersion.new('2.4.0')
|
584
|
-
Integer
|
585
|
-
else
|
586
|
-
Fixnum
|
587
|
-
end
|
588
|
-
|
589
|
-
subject.coerce_value type, String
|
590
|
-
|
591
|
-
{
|
592
|
-
fixnum: 2,
|
593
|
-
bignum: 12_345_667_890_987_654_321,
|
594
|
-
float: 2.7,
|
595
|
-
rational: Rational(2, 3),
|
596
|
-
complex: Complex(1)
|
597
|
-
}.each do |k, v|
|
598
|
-
instance[k] = v
|
599
|
-
if v.is_a? type
|
600
|
-
expect(instance[k]).to be_a(String)
|
601
|
-
expect(instance[k]).to eq(v.to_s)
|
602
|
-
else
|
603
|
-
expect(instance[k]).to_not be_a(String)
|
604
|
-
expect(instance[k]).to eq(v)
|
605
|
-
end
|
606
|
-
end
|
607
|
-
end
|
608
|
-
|
609
|
-
it 'coerces Numeric to String' do
|
610
|
-
subject.coerce_value Numeric, String
|
611
|
-
|
612
|
-
{
|
613
|
-
fixnum: 2,
|
614
|
-
bignum: 12_345_667_890_987_654_321,
|
615
|
-
float: 2.7,
|
616
|
-
rational: Rational(2, 3),
|
617
|
-
complex: Complex(1)
|
618
|
-
}.each do |k, v|
|
619
|
-
instance[k] = v
|
620
|
-
expect(instance[k]).to be_a(String)
|
621
|
-
expect(instance[k]).to eq(v.to_s)
|
622
|
-
end
|
623
|
-
end
|
624
|
-
|
625
|
-
it 'can coerce via a proc' do
|
626
|
-
subject.coerce_value(String, lambda do |v|
|
627
|
-
return !!(v =~ /^(true|t|yes|y|1)$/i)
|
628
|
-
end)
|
629
|
-
|
630
|
-
true_values = %w[true t yes y 1]
|
631
|
-
false_values = %w[false f no n 0]
|
632
|
-
|
633
|
-
true_values.each do |v|
|
634
|
-
instance[:foo] = v
|
635
|
-
expect(instance[:foo]).to be_a(TrueClass)
|
636
|
-
end
|
637
|
-
false_values.each do |v|
|
638
|
-
instance[:foo] = v
|
639
|
-
expect(instance[:foo]).to be_a(FalseClass)
|
640
|
-
end
|
641
|
-
end
|
642
|
-
end
|
643
|
-
end
|
644
|
-
|
645
|
-
after(:each) do
|
646
|
-
Object.send(:remove_const, :ExampleCoercableHash)
|
647
|
-
end
|
648
|
-
end
|