hashie 3.6.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +228 -174
  3. data/CONTRIBUTING.md +13 -6
  4. data/README.md +127 -15
  5. data/UPGRADING.md +83 -7
  6. data/hashie.gemspec +13 -7
  7. data/lib/hashie.rb +21 -19
  8. data/lib/hashie/dash.rb +2 -1
  9. data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
  10. data/lib/hashie/extensions/coercion.rb +23 -16
  11. data/lib/hashie/extensions/dash/indifferent_access.rb +20 -1
  12. data/lib/hashie/extensions/dash/property_translation.rb +6 -3
  13. data/lib/hashie/extensions/deep_fetch.rb +4 -2
  14. data/lib/hashie/extensions/deep_find.rb +12 -3
  15. data/lib/hashie/extensions/deep_locate.rb +22 -7
  16. data/lib/hashie/extensions/deep_merge.rb +18 -1
  17. data/lib/hashie/extensions/indifferent_access.rb +1 -3
  18. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  19. data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
  20. data/lib/hashie/extensions/mash/keep_original_keys.rb +2 -1
  21. data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
  22. data/lib/hashie/extensions/mash/safe_assignment.rb +3 -1
  23. data/lib/hashie/extensions/method_access.rb +5 -2
  24. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +26 -4
  25. data/lib/hashie/extensions/ruby_version_check.rb +5 -1
  26. data/lib/hashie/extensions/strict_key_access.rb +8 -4
  27. data/lib/hashie/hash.rb +16 -9
  28. data/lib/hashie/mash.rb +114 -53
  29. data/lib/hashie/railtie.rb +7 -0
  30. data/lib/hashie/rash.rb +1 -1
  31. data/lib/hashie/utils.rb +28 -0
  32. data/lib/hashie/version.rb +1 -1
  33. metadata +19 -130
  34. data/spec/hashie/array_spec.rb +0 -29
  35. data/spec/hashie/clash_spec.rb +0 -70
  36. data/spec/hashie/dash_spec.rb +0 -598
  37. data/spec/hashie/extensions/autoload_spec.rb +0 -24
  38. data/spec/hashie/extensions/coercion_spec.rb +0 -639
  39. data/spec/hashie/extensions/dash/coercion_spec.rb +0 -13
  40. data/spec/hashie/extensions/dash/indifferent_access_spec.rb +0 -84
  41. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -97
  42. data/spec/hashie/extensions/deep_find_spec.rb +0 -138
  43. data/spec/hashie/extensions/deep_locate_spec.rb +0 -137
  44. data/spec/hashie/extensions/deep_merge_spec.rb +0 -70
  45. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -47
  46. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -295
  47. data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +0 -208
  48. data/spec/hashie/extensions/key_conversion_spec.rb +0 -12
  49. data/spec/hashie/extensions/mash/keep_original_keys_spec.rb +0 -46
  50. data/spec/hashie/extensions/mash/safe_assignment_spec.rb +0 -50
  51. data/spec/hashie/extensions/mash/symbolize_keys_spec.rb +0 -39
  52. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  53. data/spec/hashie/extensions/method_access_spec.rb +0 -226
  54. data/spec/hashie/extensions/strict_key_access_spec.rb +0 -110
  55. data/spec/hashie/extensions/stringify_keys_spec.rb +0 -124
  56. data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -129
  57. data/spec/hashie/hash_spec.rb +0 -84
  58. data/spec/hashie/mash_spec.rb +0 -771
  59. data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -46
  60. data/spec/hashie/rash_spec.rb +0 -83
  61. data/spec/hashie/trash_spec.rb +0 -328
  62. data/spec/hashie/utils_spec.rb +0 -25
  63. data/spec/hashie/version_spec.rb +0 -7
  64. data/spec/hashie_spec.rb +0 -13
  65. data/spec/integration/elasticsearch/integration_spec.rb +0 -40
  66. data/spec/integration/omniauth-oauth2/app.rb +0 -52
  67. data/spec/integration/omniauth-oauth2/integration_spec.rb +0 -26
  68. data/spec/integration/omniauth-oauth2/some_site.rb +0 -38
  69. data/spec/integration/omniauth/app.rb +0 -11
  70. data/spec/integration/omniauth/integration_spec.rb +0 -38
  71. data/spec/integration/rails-without-dependency/integration_spec.rb +0 -15
  72. data/spec/integration/rails/app.rb +0 -47
  73. data/spec/integration/rails/integration_spec.rb +0 -26
  74. data/spec/spec_helper.rb +0 -23
  75. data/spec/support/integration_specs.rb +0 -36
  76. data/spec/support/logger.rb +0 -24
  77. data/spec/support/module_context.rb +0 -11
  78. 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,639 +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' }.to raise_error(Hashie::CoercionError, /NotInitializable is not a coercable type/)
287
- end
288
-
289
- it 'can coerce false' do
290
- subject.coerce_key :foo, Coercable
291
-
292
- instance[:foo] = false
293
- expect(instance[:foo]).to be_coerced
294
- expect(instance[:foo].value).to eq('FalseClass')
295
- end
296
-
297
- it 'does not coerce nil' do
298
- subject.coerce_key :foo, String
299
-
300
- instance[:foo] = nil
301
- expect(instance[:foo]).to_not eq('')
302
- expect(instance[:foo]).to be_nil
303
- end
304
- end
305
-
306
- it 'calls #new if no coerce method is available' do
307
- subject.coerce_key :foo, Initializable
308
-
309
- instance[:foo] = 'bar'
310
- expect(instance[:foo].value).to eq 'String'
311
- expect(instance[:foo]).not_to be_coerced
312
- end
313
-
314
- it 'coerces when the merge initializer is used' do
315
- subject.coerce_key :foo, Coercable
316
- instance = subject.new(foo: 'bar')
317
-
318
- expect(instance[:foo]).to be_coerced
319
- end
320
-
321
- context 'when #replace is used' do
322
- before { subject.coerce_key :foo, :bar, Coercable }
323
-
324
- let(:instance) do
325
- subject.new(foo: 'bar').replace(foo: 'foz', bar: 'baz', hi: 'bye')
326
- end
327
-
328
- it 'coerces relevant keys' do
329
- expect(instance[:foo]).to be_coerced
330
- expect(instance[:bar]).to be_coerced
331
- expect(instance[:hi]).not_to respond_to(:coerced?)
332
- end
333
-
334
- it 'sets correct values' do
335
- expect(instance[:hi]).to eq 'bye'
336
- end
337
- end
338
-
339
- context 'when used with a Mash' do
340
- class UserMash < Hashie::Mash
341
- end
342
- class TweetMash < Hashie::Mash
343
- include Hashie::Extensions::Coercion
344
- coerce_key :user, UserMash
345
- end
346
-
347
- it 'coerces with instance initialization' do
348
- tweet = TweetMash.new(user: { email: 'foo@bar.com' })
349
- expect(tweet[:user]).to be_a(UserMash)
350
- end
351
-
352
- it 'coerces when setting with attribute style' do
353
- tweet = TweetMash.new
354
- tweet.user = { email: 'foo@bar.com' }
355
- expect(tweet[:user]).to be_a(UserMash)
356
- end
357
-
358
- it 'coerces when setting with string index' do
359
- tweet = TweetMash.new
360
- tweet['user'] = { email: 'foo@bar.com' }
361
- expect(tweet[:user]).to be_a(UserMash)
362
- end
363
-
364
- it 'coerces when setting with symbol index' do
365
- tweet = TweetMash.new
366
- tweet[:user] = { email: 'foo@bar.com' }
367
- expect(tweet[:user]).to be_a(UserMash)
368
- end
369
- end
370
-
371
- context 'when used with a Trash' do
372
- class UserTrash < Hashie::Trash
373
- property :email
374
- end
375
- class TweetTrash < Hashie::Trash
376
- include Hashie::Extensions::Coercion
377
-
378
- property :user, from: :user_data
379
- coerce_key :user, UserTrash
380
- end
381
-
382
- it 'coerces with instance initialization' do
383
- tweet = TweetTrash.new(user_data: { email: 'foo@bar.com' })
384
- expect(tweet[:user]).to be_a(UserTrash)
385
- end
386
- end
387
-
388
- context 'when used with IndifferentAccess to coerce a Mash' do
389
- class MyHash < Hash
390
- include Hashie::Extensions::Coercion
391
- include Hashie::Extensions::IndifferentAccess
392
- include Hashie::Extensions::MergeInitializer
393
- end
394
-
395
- class UserHash < MyHash
396
- end
397
-
398
- class TweetHash < MyHash
399
- coerce_key :user, UserHash
400
- end
401
-
402
- it 'coerces with instance initialization' do
403
- tweet = TweetHash.new(user: Hashie::Mash.new(email: 'foo@bar.com'))
404
- expect(tweet[:user]).to be_a(UserHash)
405
- end
406
-
407
- it 'coerces when setting with string index' do
408
- tweet = TweetHash.new
409
- tweet['user'] = Hashie::Mash.new(email: 'foo@bar.com')
410
- expect(tweet[:user]).to be_a(UserHash)
411
- end
412
-
413
- it 'coerces when setting with symbol index' do
414
- tweet = TweetHash.new
415
- tweet[:user] = Hashie::Mash.new(email: 'foo@bar.com')
416
- expect(tweet[:user]).to be_a(UserHash)
417
- end
418
- end
419
-
420
- context 'when subclassing' do
421
- class MyOwnBase < Hash
422
- include Hashie::Extensions::Coercion
423
- end
424
-
425
- class MyOwnHash < MyOwnBase
426
- coerce_key :value, Integer
427
- end
428
-
429
- class MyOwnSubclass < MyOwnHash
430
- end
431
-
432
- it 'inherits key coercions' do
433
- expect(MyOwnHash.key_coercions).to eql(MyOwnSubclass.key_coercions)
434
- end
435
-
436
- it 'the superclass does not accumulate coerced attributes from subclasses' do
437
- expect(MyOwnBase.key_coercions).to eq({})
438
- end
439
- end
440
-
441
- context 'when using circular coercion' do
442
- context 'with a proc on one side' do
443
- class CategoryHash < Hash
444
- include Hashie::Extensions::Coercion
445
- include Hashie::Extensions::MergeInitializer
446
-
447
- coerce_key :products, lambda { |value|
448
- return value.map { |v| ProductHash.new(v) } if value.respond_to?(:map)
449
-
450
- ProductHash.new(v)
451
- }
452
- end
453
-
454
- class ProductHash < Hash
455
- include Hashie::Extensions::Coercion
456
- include Hashie::Extensions::MergeInitializer
457
-
458
- coerce_key :categories, Array[CategoryHash]
459
- end
460
-
461
- let(:category) { CategoryHash.new(type: 'rubygem', products: [Hashie::Mash.new(name: 'Hashie')]) }
462
- let(:product) { ProductHash.new(name: 'Hashie', categories: [Hashie::Mash.new(type: 'rubygem')]) }
463
-
464
- it 'coerces CategoryHash[:products] correctly' do
465
- expected = [ProductHash]
466
- actual = category[:products].map(&:class)
467
-
468
- expect(actual).to eq(expected)
469
- end
470
-
471
- it 'coerces ProductHash[:categories] correctly' do
472
- expected = [CategoryHash]
473
- actual = product[:categories].map(&:class)
474
-
475
- expect(actual).to eq(expected)
476
- end
477
- end
478
-
479
- context 'without a proc on either side' do
480
- it 'fails with a NameError since the other class is not defined yet' do
481
- attempted_code = lambda do
482
- class AnotherCategoryHash < Hash
483
- include Hashie::Extensions::Coercion
484
- include Hashie::Extensions::MergeInitializer
485
-
486
- coerce_key :products, Array[AnotherProductHash]
487
- end
488
-
489
- class AnotherProductHash < Hash
490
- include Hashie::Extensions::Coercion
491
- include Hashie::Extensions::MergeInitializer
492
-
493
- coerce_key :categories, Array[AnotherCategoryHash]
494
- end
495
- end
496
-
497
- expect { attempted_code.call }.to raise_error(NameError)
498
- end
499
- end
500
- end
501
- end
502
-
503
- describe '#coerce_value' do
504
- context 'with strict: true' do
505
- it 'coerces any value of the exact right class' do
506
- subject.coerce_value String, Coercable
507
-
508
- instance[:foo] = 'bar'
509
- instance[:bar] = 'bax'
510
- instance[:hi] = :bye
511
- expect(instance[:foo]).to be_coerced
512
- expect(instance[:bar]).to be_coerced
513
- expect(instance[:hi]).not_to respond_to(:coerced?)
514
- end
515
-
516
- it 'coerces values from a #replace call' do
517
- subject.coerce_value String, Coercable
518
-
519
- instance[:foo] = :bar
520
- instance.replace(foo: 'bar', bar: 'bax')
521
- expect(instance[:foo]).to be_coerced
522
- expect(instance[:bar]).to be_coerced
523
- end
524
-
525
- it 'does not coerce superclasses' do
526
- klass = Class.new(String)
527
- subject.coerce_value klass, Coercable
528
-
529
- instance[:foo] = 'bar'
530
- expect(instance[:foo]).not_to be_kind_of(Coercable)
531
- instance[:foo] = klass.new
532
- expect(instance[:foo]).to be_kind_of(Coercable)
533
- end
534
- end
535
-
536
- context 'core types' do
537
- it 'coerces String to Integer when possible' do
538
- subject.coerce_value String, Integer
539
-
540
- instance[:foo] = '2'
541
- instance[:bar] = '2.7'
542
- instance[:hi] = 'hi'
543
- expect(instance[:foo]).to be_a(Integer)
544
- expect(instance[:foo]).to eq(2)
545
- expect(instance[:bar]).to be_a(Integer)
546
- expect(instance[:bar]).to eq(2)
547
- expect(instance[:hi]).to be_a(Integer)
548
- expect(instance[:hi]).to eq(0) # not what I expected...
549
- end
550
-
551
- it 'coerces non-numeric from String to Integer' do
552
- # This was surprising, but I guess it's "correct"
553
- # unless there is a stricter `to_i` alternative
554
- subject.coerce_value String, Integer
555
- instance[:hi] = 'hi'
556
- expect(instance[:hi]).to be_a(Integer)
557
- expect(instance[:hi]).to eq(0)
558
- end
559
-
560
- it 'raises a CoercionError when coercion is not possible' do
561
- type =
562
- if Hashie::Extensions::RubyVersion.new(RUBY_VERSION) >= Hashie::Extensions::RubyVersion.new('2.4.0')
563
- Integer
564
- else
565
- Fixnum # rubocop:disable Lint/UnifiedInteger
566
- end
567
-
568
- subject.coerce_value type, Symbol
569
- expect { instance[:hi] = 1 }.to raise_error(Hashie::CoercionError, /Cannot coerce property :hi from #{type} to Symbol/)
570
- end
571
-
572
- it 'coerces Integer to String' do
573
- type =
574
- if Hashie::Extensions::RubyVersion.new(RUBY_VERSION) >= Hashie::Extensions::RubyVersion.new('2.4.0')
575
- Integer
576
- else
577
- Fixnum # rubocop:disable Lint/UnifiedInteger
578
- end
579
-
580
- subject.coerce_value type, String
581
-
582
- {
583
- fixnum: 2,
584
- bignum: 12_345_667_890_987_654_321,
585
- float: 2.7,
586
- rational: Rational(2, 3),
587
- complex: Complex(1)
588
- }.each do |k, v|
589
- instance[k] = v
590
- if v.is_a? type
591
- expect(instance[k]).to be_a(String)
592
- expect(instance[k]).to eq(v.to_s)
593
- else
594
- expect(instance[k]).to_not be_a(String)
595
- expect(instance[k]).to eq(v)
596
- end
597
- end
598
- end
599
-
600
- it 'coerces Numeric to String' do
601
- subject.coerce_value Numeric, String
602
-
603
- {
604
- fixnum: 2,
605
- bignum: 12_345_667_890_987_654_321,
606
- float: 2.7,
607
- rational: Rational(2, 3),
608
- complex: Complex(1)
609
- }.each do |k, v|
610
- instance[k] = v
611
- expect(instance[k]).to be_a(String)
612
- expect(instance[k]).to eq(v.to_s)
613
- end
614
- end
615
-
616
- it 'can coerce via a proc' do
617
- subject.coerce_value(String, lambda do |v|
618
- return !!(v =~ /^(true|t|yes|y|1)$/i)
619
- end)
620
-
621
- true_values = %w[true t yes y 1]
622
- false_values = %w[false f no n 0]
623
-
624
- true_values.each do |v|
625
- instance[:foo] = v
626
- expect(instance[:foo]).to be_a(TrueClass)
627
- end
628
- false_values.each do |v|
629
- instance[:foo] = v
630
- expect(instance[:foo]).to be_a(FalseClass)
631
- end
632
- end
633
- end
634
- end
635
-
636
- after(:each) do
637
- Object.send(:remove_const, :ExampleCoercableHash)
638
- end
639
- end