hashie 3.4.2 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +518 -122
- data/CONTRIBUTING.md +24 -7
- data/LICENSE +1 -1
- data/README.md +455 -48
- data/Rakefile +18 -1
- data/UPGRADING.md +157 -7
- data/hashie.gemspec +14 -7
- data/lib/hashie/array.rb +21 -0
- data/lib/hashie/clash.rb +24 -12
- data/lib/hashie/dash.rb +56 -31
- data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
- data/lib/hashie/extensions/array/pretty_inspect.rb +19 -0
- data/lib/hashie/extensions/coercion.rb +91 -52
- data/lib/hashie/extensions/dash/coercion.rb +25 -0
- data/lib/hashie/extensions/dash/indifferent_access.rb +30 -1
- data/lib/hashie/extensions/dash/predefined_values.rb +88 -0
- data/lib/hashie/extensions/dash/property_translation.rb +59 -30
- data/lib/hashie/extensions/deep_fetch.rb +5 -3
- data/lib/hashie/extensions/deep_find.rb +14 -5
- data/lib/hashie/extensions/deep_locate.rb +40 -21
- data/lib/hashie/extensions/deep_merge.rb +28 -10
- data/lib/hashie/extensions/ignore_undeclared.rb +6 -4
- data/lib/hashie/extensions/indifferent_access.rb +49 -8
- data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
- data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
- data/lib/hashie/extensions/mash/keep_original_keys.rb +53 -0
- data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
- data/lib/hashie/extensions/mash/safe_assignment.rb +3 -1
- data/lib/hashie/extensions/mash/symbolize_keys.rb +38 -0
- data/lib/hashie/extensions/method_access.rb +77 -19
- data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +29 -5
- data/lib/hashie/extensions/ruby_version.rb +60 -0
- data/lib/hashie/extensions/ruby_version_check.rb +21 -0
- data/lib/hashie/extensions/strict_key_access.rb +77 -0
- data/lib/hashie/extensions/stringify_keys.rb +8 -5
- data/lib/hashie/extensions/symbolize_keys.rb +21 -7
- data/lib/hashie/hash.rb +18 -11
- data/lib/hashie/logger.rb +18 -0
- data/lib/hashie/mash.rb +196 -55
- data/lib/hashie/railtie.rb +21 -0
- data/lib/hashie/rash.rb +7 -7
- data/lib/hashie/utils.rb +44 -0
- data/lib/hashie/version.rb +1 -1
- data/lib/hashie.rb +34 -16
- metadata +30 -79
- data/spec/hashie/clash_spec.rb +0 -48
- data/spec/hashie/dash_spec.rb +0 -513
- data/spec/hashie/extensions/autoload_spec.rb +0 -24
- data/spec/hashie/extensions/coercion_spec.rb +0 -625
- 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 -45
- data/spec/hashie/extensions/deep_locate_spec.rb +0 -124
- data/spec/hashie/extensions/deep_merge_spec.rb +0 -45
- data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -46
- data/spec/hashie/extensions/indifferent_access_spec.rb +0 -219
- 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/safe_assignment_spec.rb +0 -23
- data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
- data/spec/hashie/extensions/method_access_spec.rb +0 -184
- data/spec/hashie/extensions/stringify_keys_spec.rb +0 -101
- data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -106
- data/spec/hashie/hash_spec.rb +0 -84
- data/spec/hashie/mash_spec.rb +0 -683
- data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -29
- data/spec/hashie/rash_spec.rb +0 -77
- data/spec/hashie/trash_spec.rb +0 -268
- data/spec/hashie/version_spec.rb +0 -7
- data/spec/spec_helper.rb +0 -15
- data/spec/support/module_context.rb +0 -11
- data/spec/support/ruby_version.rb +0 -10
data/README.md
CHANGED
@@ -1,14 +1,56 @@
|
|
1
1
|
# Hashie
|
2
2
|
|
3
|
-
[![Join the chat at https://gitter.im/
|
4
|
-
|
3
|
+
[![Join the chat at https://gitter.im/hashie/hashie](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hashie/hashie?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
5
4
|
[![Gem Version](http://img.shields.io/gem/v/hashie.svg)](http://badge.fury.io/rb/hashie)
|
6
|
-
[![Build Status](
|
7
|
-
|
8
|
-
[![
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
[![Build Status](https://github.com/hashie/hashie/actions/workflows/main.yml/badge.svg)](https://github.com/hashie/hashie/actions/workflows/main.yml)
|
6
|
+
|
7
|
+
[![eierlegende Wollmilchsau](./mascot.svg)](#mascot) Hashie is a growing collection of tools that extend Hashes and make them more useful.
|
8
|
+
|
9
|
+
# Table of Contents
|
10
|
+
|
11
|
+
- [Hashie](#hashie)
|
12
|
+
- [Table of Contents](#table-of-contents)
|
13
|
+
- [Installation](#installation)
|
14
|
+
- [Stable Release](#stable-release)
|
15
|
+
- [Hash Extensions](#hash-extensions)
|
16
|
+
- [Logging](#logging)
|
17
|
+
- [Coercion](#coercion)
|
18
|
+
- [Coercing Collections](#coercing-collections)
|
19
|
+
- [Coercing Hashes](#coercing-hashes)
|
20
|
+
- [Coercing Core Types](#coercing-core-types)
|
21
|
+
- [Coercion Proc](#coercion-proc)
|
22
|
+
- [A note on circular coercion](#a-note-on-circular-coercion)
|
23
|
+
- [KeyConversion](#keyconversion)
|
24
|
+
- [MergeInitializer](#mergeinitializer)
|
25
|
+
- [MethodAccess](#methodaccess)
|
26
|
+
- [MethodAccessWithOverride](#methodaccesswithoverride)
|
27
|
+
- [MethodOverridingInitializer](#methodoverridinginitializer)
|
28
|
+
- [IndifferentAccess](#indifferentaccess)
|
29
|
+
- [IgnoreUndeclared](#ignoreundeclared)
|
30
|
+
- [DeepMerge](#deepmerge)
|
31
|
+
- [DeepFetch](#deepfetch)
|
32
|
+
- [DeepFind](#deepfind)
|
33
|
+
- [DeepLocate](#deeplocate)
|
34
|
+
- [StrictKeyAccess](#strictkeyaccess)
|
35
|
+
- [Mash](#mash)
|
36
|
+
- [KeepOriginalKeys](#keeporiginalkeys)
|
37
|
+
- [PermissiveRespondTo](#permissiverespondto)
|
38
|
+
- [SafeAssignment](#safeassignment)
|
39
|
+
- [SymbolizeKeys](#symbolizekeys)
|
40
|
+
- [DefineAccessors](#defineaccessors)
|
41
|
+
- [Dash](#dash)
|
42
|
+
- [Potential Gotchas](#potential-gotchas)
|
43
|
+
- [PropertyTranslation](#propertytranslation)
|
44
|
+
- [Mash and Rails 4 Strong Parameters](#mash-and-rails-4-strong-parameters)
|
45
|
+
- [Coercion](#coercion-1)
|
46
|
+
- [PredefinedValues](#predefinedvalues)
|
47
|
+
- [Trash](#trash)
|
48
|
+
- [Clash](#clash)
|
49
|
+
- [Rash](#rash)
|
50
|
+
- [Auto-Optimized](#auto-optimized)
|
51
|
+
- [Mascot](#mascot)
|
52
|
+
- [Contributing](#contributing)
|
53
|
+
- [Copyright](#copyright)
|
12
54
|
|
13
55
|
## Installation
|
14
56
|
|
@@ -18,16 +60,25 @@ Hashie is available as a RubyGem:
|
|
18
60
|
$ gem install hashie
|
19
61
|
```
|
20
62
|
|
21
|
-
##
|
63
|
+
## Stable Release
|
22
64
|
|
23
|
-
You're reading the documentation for the stable release of Hashie,
|
65
|
+
You're reading the documentation for the stable release of Hashie, v5.0.0.
|
24
66
|
|
25
67
|
## Hash Extensions
|
26
68
|
|
27
|
-
The library is broken up into a number of atomically
|
69
|
+
The library is broken up into a number of atomically includable Hash extension modules as described below. This provides maximum flexibility for users to mix and match functionality while maintaining feature parity with earlier versions of Hashie.
|
28
70
|
|
29
71
|
Any of the extensions listed below can be mixed into a class by `include`-ing `Hashie::Extensions::ExtensionName`.
|
30
72
|
|
73
|
+
## Logging
|
74
|
+
|
75
|
+
Hashie has a built-in logger that you can override. By default, it logs to `STDOUT` but can be replaced by any `Logger` class. The logger is accessible on the Hashie module, as shown below:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
# Set the logger to the Rails logger
|
79
|
+
Hashie.logger = Rails.logger
|
80
|
+
```
|
81
|
+
|
31
82
|
### Coercion
|
32
83
|
|
33
84
|
Coercions allow you to set up "coercion rules" based either on the key or the value type to massage data as it's being inserted into the Hash. Key coercions might be used, for example, in lightweight data modeling applications such as an API client:
|
@@ -35,6 +86,7 @@ Coercions allow you to set up "coercion rules" based either on the key or the va
|
|
35
86
|
```ruby
|
36
87
|
class Tweet < Hash
|
37
88
|
include Hashie::Extensions::Coercion
|
89
|
+
include Hashie::Extensions::MergeInitializer
|
38
90
|
coerce_key :user, User
|
39
91
|
end
|
40
92
|
|
@@ -123,7 +175,7 @@ You can also use coerce from the following supertypes with `coerce_value`:
|
|
123
175
|
- Integer
|
124
176
|
- Numeric
|
125
177
|
|
126
|
-
Hashie does not have built-in support for
|
178
|
+
Hashie does not have built-in support for coercing boolean values, since Ruby does not have a built-in boolean type or standard method for coercing to a boolean. You can coerce to booleans using a custom proc.
|
127
179
|
|
128
180
|
### Coercion Proc
|
129
181
|
|
@@ -135,11 +187,11 @@ class Tweet < Hash
|
|
135
187
|
coerce_key :retweeted, ->(v) do
|
136
188
|
case v
|
137
189
|
when String
|
138
|
-
|
190
|
+
!!(v =~ /\A(true|t|yes|y|1)\z/i)
|
139
191
|
when Numeric
|
140
|
-
|
192
|
+
!v.to_i.zero?
|
141
193
|
else
|
142
|
-
|
194
|
+
v == true
|
143
195
|
end
|
144
196
|
end
|
145
197
|
end
|
@@ -187,8 +239,8 @@ The KeyConversion extension gives you the convenience methods of `symbolize_keys
|
|
187
239
|
Hashie also has a utility method for converting keys on a Hash without a mixin:
|
188
240
|
|
189
241
|
```ruby
|
190
|
-
Hashie.symbolize_keys! hash # => Symbolizes keys of hash.
|
191
|
-
Hashie.symbolize_keys hash # => Returns a copy of hash with keys symbolized.
|
242
|
+
Hashie.symbolize_keys! hash # => Symbolizes all string keys of hash.
|
243
|
+
Hashie.symbolize_keys hash # => Returns a copy of hash with string keys symbolized.
|
192
244
|
Hashie.stringify_keys! hash # => Stringifies keys of hash.
|
193
245
|
Hashie.stringify_keys hash # => Returns a copy of hash with keys stringified.
|
194
246
|
```
|
@@ -235,11 +287,62 @@ overriding.zip #=> 'a-dee-doo-dah'
|
|
235
287
|
overriding.__zip #=> [[['zip', 'a-dee-doo-dah']]]
|
236
288
|
```
|
237
289
|
|
290
|
+
### MethodOverridingInitializer
|
291
|
+
|
292
|
+
The MethodOverridingInitializer extension will override hash methods if you pass in a normal hash to the constructor. It aliases any overridden method with two leading underscores. To include only this initializing functionality, you can include the single module `Hashie::Extensions::MethodOverridingInitializer`.
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
class MyHash < Hash
|
296
|
+
end
|
297
|
+
|
298
|
+
class MyOverridingHash < Hash
|
299
|
+
include Hashie::Extensions::MethodOverridingInitializer
|
300
|
+
end
|
301
|
+
|
302
|
+
non_overriding = MyHash.new(zip: 'a-dee-doo-dah')
|
303
|
+
non_overriding.zip #=> []
|
304
|
+
|
305
|
+
overriding = MyOverridingHash.new(zip: 'a-dee-doo-dah')
|
306
|
+
overriding.zip #=> 'a-dee-doo-dah'
|
307
|
+
overriding.__zip #=> [[['zip', 'a-dee-doo-dah']]]
|
308
|
+
```
|
309
|
+
|
238
310
|
### IndifferentAccess
|
239
311
|
|
240
|
-
This extension can be mixed in to
|
312
|
+
This extension can be mixed in to your Hash subclass to allow you to use Strings or Symbols interchangeably as keys; similar to the `params` hash in Rails.
|
313
|
+
|
314
|
+
In addition, IndifferentAccess will also inject itself into sub-hashes so they behave the same.
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
class MyHash < Hash
|
318
|
+
include Hashie::Extensions::MergeInitializer
|
319
|
+
include Hashie::Extensions::IndifferentAccess
|
320
|
+
end
|
321
|
+
|
322
|
+
myhash = MyHash.new(:cat => 'meow', 'dog' => 'woof')
|
323
|
+
myhash['cat'] # => "meow"
|
324
|
+
myhash[:cat] # => "meow"
|
325
|
+
myhash[:dog] # => "woof"
|
326
|
+
myhash['dog'] # => "woof"
|
327
|
+
|
328
|
+
# Auto-Injecting into sub-hashes.
|
329
|
+
myhash['fishes'] = {}
|
330
|
+
myhash['fishes'].class # => Hash
|
331
|
+
myhash['fishes'][:food] = 'flakes'
|
332
|
+
myhash['fishes']['food'] # => "flakes"
|
333
|
+
```
|
241
334
|
|
242
|
-
|
335
|
+
To get back a normal, not-indifferent Hash, you can use `#to_hash` on the indifferent hash. It exports the keys as strings, not symbols:
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
myhash = MyHash.new
|
339
|
+
myhash["foo"] = "bar"
|
340
|
+
myhash[:foo] #=> "bar"
|
341
|
+
|
342
|
+
normal_hash = myhash.to_hash
|
343
|
+
myhash["foo"] #=> "bar"
|
344
|
+
myhash[:foo] #=> nil
|
345
|
+
```
|
243
346
|
|
244
347
|
### IgnoreUndeclared
|
245
348
|
|
@@ -267,8 +370,8 @@ p.email # => NoMethodError
|
|
267
370
|
|
268
371
|
### DeepMerge
|
269
372
|
|
270
|
-
This extension
|
271
|
-
system
|
373
|
+
This extension allows you to easily include a recursive merging
|
374
|
+
system into any Hash descendant:
|
272
375
|
|
273
376
|
```ruby
|
274
377
|
class MyHash < Hash
|
@@ -392,12 +495,27 @@ books.deep_locate -> (key, value, object) { key == :pages && value <= 120 }
|
|
392
495
|
# => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"CSS for intermediates", :pages=>80}]
|
393
496
|
```
|
394
497
|
|
498
|
+
## StrictKeyAccess
|
499
|
+
|
500
|
+
This extension can be mixed in to allow a Hash to raise an error when attempting to extract a value using a non-existent key.
|
501
|
+
|
502
|
+
```ruby
|
503
|
+
class StrictKeyAccessHash < Hash
|
504
|
+
include Hashie::Extensions::StrictKeyAccess
|
505
|
+
end
|
506
|
+
|
507
|
+
>> hash = StrictKeyAccessHash[foo: "bar"]
|
508
|
+
=> {:foo=>"bar"}
|
509
|
+
>> hash[:foo]
|
510
|
+
=> "bar"
|
511
|
+
>> hash[:cow]
|
512
|
+
KeyError: key not found: :cow
|
513
|
+
```
|
514
|
+
|
395
515
|
## Mash
|
396
516
|
|
397
517
|
Mash is an extended Hash that gives simple pseudo-object functionality that can be built from hashes and easily extended. It is intended to give the user easier access to the objects within the Mash through a property-like syntax, while still retaining all Hash functionality.
|
398
518
|
|
399
|
-
### Example:
|
400
|
-
|
401
519
|
```ruby
|
402
520
|
mash = Hashie::Mash.new
|
403
521
|
mash.name? # => false
|
@@ -420,9 +538,9 @@ mash.inspect # => <Hashie::Mash>
|
|
420
538
|
|
421
539
|
**Note:** The `?` method will return false if a key has been set to false or nil. In order to check if a key has been set at all, use the `mash.key?('some_key')` method instead.
|
422
540
|
|
423
|
-
|
541
|
+
_How does Mash handle conflicts with pre-existing methods?_
|
424
542
|
|
425
|
-
|
543
|
+
Please note that a Mash will not override methods through the use of the property-like syntax. This can lead to confusion if you expect to be able to access a Mash value through the property-like syntax for a key that conflicts with a method name. However, it protects users of your library from the unexpected behavior of those methods being overridden behind the scenes.
|
426
544
|
|
427
545
|
```ruby
|
428
546
|
mash = Hashie::Mash.new
|
@@ -431,9 +549,96 @@ mash.zip = "Method Override?"
|
|
431
549
|
mash.zip # => [[["name", "My Mash"]], [["zip", "Method Override?"]]]
|
432
550
|
```
|
433
551
|
|
434
|
-
Mash
|
552
|
+
Since Mash gives you the ability to set arbitrary keys that then act as methods, Hashie logs when there is a conflict between a key and a pre-existing method. You can set the logger that this logs message to via the global Hashie logger:
|
435
553
|
|
436
|
-
|
554
|
+
```ruby
|
555
|
+
Hashie.logger = Rails.logger
|
556
|
+
```
|
557
|
+
|
558
|
+
You can also disable the logging in subclasses of Mash:
|
559
|
+
|
560
|
+
```ruby
|
561
|
+
class Response < Hashie::Mash
|
562
|
+
disable_warnings
|
563
|
+
end
|
564
|
+
```
|
565
|
+
|
566
|
+
The default is to disable logging for all methods that conflict. If you would like to only disable the logging for specific methods, you can include an array of method keys:
|
567
|
+
|
568
|
+
```ruby
|
569
|
+
class Response < Hashie::Mash
|
570
|
+
disable_warnings :zip, :zap
|
571
|
+
end
|
572
|
+
```
|
573
|
+
|
574
|
+
This behavior is cumulative. The examples above and below behave identically.
|
575
|
+
|
576
|
+
```ruby
|
577
|
+
class Response < Hashie::Mash
|
578
|
+
disable_warnings :zip
|
579
|
+
disable_warnings :zap
|
580
|
+
end
|
581
|
+
```
|
582
|
+
|
583
|
+
Disable warnings will honor the last `disable_warnings` call. Calling without parameters will override the ignored methods list, and calling with parameters will create a new ignored methods list. This includes child classes that inherit from a class that disables warnings.
|
584
|
+
|
585
|
+
```ruby
|
586
|
+
class Message < Hashie::Mash
|
587
|
+
disable_warnings :zip, :zap
|
588
|
+
disable_warnings
|
589
|
+
end
|
590
|
+
|
591
|
+
# No errors will be logged
|
592
|
+
Message.new(merge: 'true', compact: true)
|
593
|
+
```
|
594
|
+
|
595
|
+
```ruby
|
596
|
+
class Message < Hashie::Mash
|
597
|
+
disable_warnings
|
598
|
+
end
|
599
|
+
|
600
|
+
class Response < Message
|
601
|
+
disable_warnings :zip, :zap
|
602
|
+
end
|
603
|
+
|
604
|
+
# 2 errors will be logged
|
605
|
+
Response.new(merge: 'true', compact: true, zip: '90210', zap: 'electric')
|
606
|
+
```
|
607
|
+
|
608
|
+
If you would like to create an anonymous subclass of a Hashie::Mash with key conflict warnings disabled:
|
609
|
+
|
610
|
+
```ruby
|
611
|
+
Hashie::Mash.quiet.new(zip: '90210', compact: true) # no errors logged
|
612
|
+
Hashie::Mash.quiet(:zip).new(zip: '90210', compact: true) # error logged for compact
|
613
|
+
```
|
614
|
+
|
615
|
+
_How does the wrapping of Mash sub-Hashes work?_
|
616
|
+
|
617
|
+
Mash duplicates any sub-Hashes that you add to it and wraps them in a Mash. This allows for infinite chaining of nested Hashes within a Mash without modifying the object(s) that are passed into the Mash. When you subclass Mash, the subclass wraps any sub-Hashes in its own class. This preserves any extensions that you mixed into the Mash subclass and allows them to work within the sub-Hashes, in addition to the main containing Mash.
|
618
|
+
|
619
|
+
```ruby
|
620
|
+
mash = Hashie::Mash.new(name: "Hashie", dependencies: { rake: "< 11", rspec: "~> 3.0" })
|
621
|
+
mash.dependencies.class #=> Hashie::Mash
|
622
|
+
|
623
|
+
class MyGem < Hashie::Mash; end
|
624
|
+
my_gem = MyGem.new(name: "Hashie", dependencies: { rake: "< 11", rspec: "~> 3.0" })
|
625
|
+
my_gem.dependencies.class #=> MyGem
|
626
|
+
```
|
627
|
+
|
628
|
+
_How does Mash handle key types which cannot be symbolized?_
|
629
|
+
|
630
|
+
Mash preserves keys which cannot be converted *directly* to both a string and a symbol, such as numeric keys. Since Mash is conceived to provide psuedo-object functionality, handling keys which cannot represent a method call falls outside its scope of value.
|
631
|
+
|
632
|
+
```ruby
|
633
|
+
Hashie::Mash.new('1' => 'one string', :'1' => 'one sym', 1 => 'one num')
|
634
|
+
# => {"1"=>"one sym", 1=>"one num"}
|
635
|
+
```
|
636
|
+
|
637
|
+
The symbol key `:'1'` is converted the string `'1'` to support indifferent access and consequently its value `'one sym'` will override the previously set `'one string'`. However, the subsequent key of `1` cannot directly convert to a symbol and therefore **not** converted to the string `'1'` that would otherwise override the previously set value of `'one sym'`.
|
638
|
+
|
639
|
+
_What else can Mash do?_
|
640
|
+
|
641
|
+
Mash allows you also to transform any files into a Mash objects.
|
437
642
|
|
438
643
|
```yml
|
439
644
|
#/etc/config/settings/twitter.yml
|
@@ -450,6 +655,13 @@ mash.development.api_key = "foo" # => <# RuntimeError can't modify frozen ...>
|
|
450
655
|
mash.development.api_key? # => true
|
451
656
|
```
|
452
657
|
|
658
|
+
You can also load with a `Pathname` object:
|
659
|
+
|
660
|
+
```ruby
|
661
|
+
mash = Mash.load(Pathname 'settings/twitter.yml')
|
662
|
+
mash.development.api_key # => 'localhost'
|
663
|
+
```
|
664
|
+
|
453
665
|
You can access a Mash from another class:
|
454
666
|
|
455
667
|
```ruby
|
@@ -458,7 +670,7 @@ Twitter.extend mash.to_module # NOTE: if you want another name than settings, ca
|
|
458
670
|
Twitter.settings.api_key # => 'abcd'
|
459
671
|
```
|
460
672
|
|
461
|
-
You can use another parser (by default: YamlErbParser):
|
673
|
+
You can use another parser (by default: [YamlErbParser](lib/hashie/extensions/parsers/yaml_erb_parser.rb)):
|
462
674
|
|
463
675
|
```
|
464
676
|
#/etc/data/user.csv
|
@@ -474,11 +686,60 @@ mash = Mash.load('data/user.csv', parser: MyCustomCsvParser)
|
|
474
686
|
mash[1] #=> { name: 'John', lastname: 'Doe' }
|
475
687
|
```
|
476
688
|
|
477
|
-
|
689
|
+
The `Mash#load` method calls `YAML.safe_load(path, [], [], true)`.
|
478
690
|
|
479
|
-
|
691
|
+
Specify `permitted_symbols`, `permitted_classes` and `aliases` options as needed.
|
692
|
+
|
693
|
+
```ruby
|
694
|
+
Mash.load('data/user.csv', permitted_classes: [Symbol], permitted_symbols: [], aliases: false)
|
695
|
+
```
|
480
696
|
|
481
|
-
|
697
|
+
### KeepOriginalKeys
|
698
|
+
|
699
|
+
This extension can be mixed into a Mash to keep the form of any keys passed directly into the Mash. By default, Mash converts symbol keys to strings to give indifferent access. This extension still allows indifferent access, but keeps the form of the keys to eliminate confusion when you're not expecting the keys to change.
|
700
|
+
|
701
|
+
```ruby
|
702
|
+
class KeepingMash < ::Hashie::Mash
|
703
|
+
include Hashie::Extensions::Mash::KeepOriginalKeys
|
704
|
+
end
|
705
|
+
|
706
|
+
mash = KeepingMash.new(:symbol_key => :symbol, 'string_key' => 'string')
|
707
|
+
mash.to_hash == { :symbol_key => :symbol, 'string_key' => 'string' } #=> true
|
708
|
+
mash.symbol_key #=> :symbol
|
709
|
+
mash[:symbol_key] #=> :symbol
|
710
|
+
mash['symbol_key'] #=> :symbol
|
711
|
+
mash.string_key #=> 'string'
|
712
|
+
mash['string_key'] #=> 'string'
|
713
|
+
mash[:string_key] #=> 'string'
|
714
|
+
```
|
715
|
+
|
716
|
+
### PermissiveRespondTo
|
717
|
+
|
718
|
+
By default, Mash only states that it responds to built-in methods, affixed methods (e.g. setters, underbangs, etc.), and keys that it currently contains. That means it won't state that it responds to a getter for an unset key, as in the following example:
|
719
|
+
|
720
|
+
```ruby
|
721
|
+
mash = Hashie::Mash.new(a: 1)
|
722
|
+
mash.respond_to? :b #=> false
|
723
|
+
```
|
724
|
+
|
725
|
+
This means that by default Mash is not a perfect match for use with a SimpleDelegator since the delegator will not forward messages for unset keys to the Mash even though it can handle them.
|
726
|
+
|
727
|
+
In order to have a SimpleDelegator-compatible Mash, you can use the `PermissiveRespondTo` extension to make Mash respond to anything.
|
728
|
+
|
729
|
+
```ruby
|
730
|
+
class PermissiveMash < Hashie::Mash
|
731
|
+
include Hashie::Extensions::Mash::PermissiveRespondTo
|
732
|
+
end
|
733
|
+
|
734
|
+
mash = PermissiveMash.new(a: 1)
|
735
|
+
mash.respond_to? :b #=> true
|
736
|
+
```
|
737
|
+
|
738
|
+
This comes at the cost of approximately 20% performance for initialization and setters and 19KB of permanent memory growth for each such class that you create.
|
739
|
+
|
740
|
+
### SafeAssignment
|
741
|
+
|
742
|
+
This extension can be mixed into a Mash to guard the attempted overwriting of methods by property setters. When mixed in, the Mash will raise an `ArgumentError` if you attempt to write a property with the same name as an existing method.
|
482
743
|
|
483
744
|
```ruby
|
484
745
|
class SafeMash < ::Hashie::Mash
|
@@ -490,14 +751,70 @@ safe_mash.zip = 'Test' # => ArgumentError
|
|
490
751
|
safe_mash[:zip] = 'test' # => still ArgumentError
|
491
752
|
```
|
492
753
|
|
754
|
+
### SymbolizeKeys
|
755
|
+
|
756
|
+
This extension can be mixed into a Mash to change the default behavior of converting keys to strings. After mixing this extension into a Mash, the Mash will convert all string keys to symbols. It can be useful to use with keywords argument, which required symbol keys.
|
757
|
+
|
758
|
+
```ruby
|
759
|
+
class SymbolizedMash < ::Hashie::Mash
|
760
|
+
include Hashie::Extensions::Mash::SymbolizeKeys
|
761
|
+
end
|
762
|
+
|
763
|
+
symbol_mash = SymbolizedMash.new
|
764
|
+
symbol_mash['test'] = 'value'
|
765
|
+
symbol_mash.test #=> 'value'
|
766
|
+
symbol_mash.to_h #=> {test: 'value'}
|
767
|
+
|
768
|
+
def example(test:)
|
769
|
+
puts test
|
770
|
+
end
|
771
|
+
|
772
|
+
example(symbol_mash) #=> value
|
773
|
+
```
|
774
|
+
|
775
|
+
There is a major benefit and coupled with a major trade-off to this decision (at least on older Rubies). As a benefit, by using symbols as keys, you will be able to use the implicit conversion of a Mash via the `#to_hash` method to destructure (or splat) the contents of a Mash out to a block. This can be handy for doing iterations through the Mash's keys and values, as follows:
|
776
|
+
|
777
|
+
```ruby
|
778
|
+
symbol_mash = SymbolizedMash.new(id: 123, name: 'Rey')
|
779
|
+
symbol_mash.each do |key, value|
|
780
|
+
# key is :id, then :name
|
781
|
+
# value is 123, then 'Rey'
|
782
|
+
end
|
783
|
+
```
|
784
|
+
|
785
|
+
However, on Rubies less than 2.0, this means that every key you send to the Mash will generate a symbol. Since symbols are not garbage-collected on older versions of Ruby, this can cause a slow memory leak when using a symbolized Mash with data generated from user input.
|
786
|
+
|
787
|
+
### DefineAccessors
|
788
|
+
|
789
|
+
This extension can be mixed into a Mash so it makes it behave like `OpenStruct`. It reduces the overhead of `method_missing?` magic by lazily defining field accessors when they're requested.
|
790
|
+
|
791
|
+
```ruby
|
792
|
+
class MyHash < ::Hashie::Mash
|
793
|
+
include Hashie::Extensions::Mash::DefineAccessors
|
794
|
+
end
|
795
|
+
|
796
|
+
mash = MyHash.new
|
797
|
+
MyHash.method_defined?(:foo=) #=> false
|
798
|
+
mash.foo = 123
|
799
|
+
MyHash.method_defined?(:foo=) #=> true
|
800
|
+
|
801
|
+
MyHash.method_defined?(:foo) #=> false
|
802
|
+
mash.foo #=> 123
|
803
|
+
MyHash.method_defined?(:foo) #=> true
|
804
|
+
```
|
805
|
+
|
806
|
+
You can also extend the existing mash without defining a class:
|
807
|
+
|
808
|
+
```ruby
|
809
|
+
mash = ::Hashie::Mash.new.with_accessors!
|
810
|
+
```
|
811
|
+
|
493
812
|
## Dash
|
494
813
|
|
495
814
|
Dash is an extended Hash that has a discrete set of defined properties and only those properties may be set on the hash. Additionally, you can set defaults for each property. You can also flag a property as required. Required properties will raise an exception if unset. Another option is message for required properties, which allow you to add custom messages for required property.
|
496
815
|
|
497
816
|
You can also conditionally require certain properties by passing a Proc or Symbol. If a Proc is provided, it will be run in the context of the Dash instance. If a Symbol is provided, the value returned for the property or method of the same name will be evaluated. The property will be required if the result of the conditional is truthy.
|
498
817
|
|
499
|
-
### Example:
|
500
|
-
|
501
818
|
```ruby
|
502
819
|
class Person < Hashie::Dash
|
503
820
|
property :name, required: true
|
@@ -534,8 +851,6 @@ p.occupation # => 'Rubyist'
|
|
534
851
|
|
535
852
|
Properties defined as symbols are not the same thing as properties defined as strings.
|
536
853
|
|
537
|
-
### Example:
|
538
|
-
|
539
854
|
```ruby
|
540
855
|
class Tricky < Hashie::Dash
|
541
856
|
property :trick
|
@@ -559,13 +874,64 @@ p = Tricky.new('trick' => 'two')
|
|
559
874
|
p.trick # => NoMethodError
|
560
875
|
```
|
561
876
|
|
562
|
-
|
877
|
+
If you would like to update a Dash and use any default values set in the case of a `nil` value, use `#update_attributes!`.
|
878
|
+
|
879
|
+
```ruby
|
880
|
+
class WithDefaults < Hashie::Dash
|
881
|
+
property :description, default: 'none'
|
882
|
+
end
|
883
|
+
|
884
|
+
dash = WithDefaults.new
|
885
|
+
dash.description #=> 'none'
|
886
|
+
|
887
|
+
dash.description = 'You committed one of the classic blunders!'
|
888
|
+
dash.description #=> 'You committed one of the classic blunders!'
|
889
|
+
|
890
|
+
dash.description = nil
|
891
|
+
dash.description #=> nil
|
892
|
+
|
893
|
+
dash.description = 'Only slightly less known is ...'
|
894
|
+
dash.update_attributes!(description: nil)
|
895
|
+
dash.description #=> 'none'
|
896
|
+
```
|
897
|
+
|
898
|
+
### Potential Gotchas
|
899
|
+
|
900
|
+
Because Dashes are subclasses of the built-in Ruby Hash class, the double-splat operator takes the Dash as-is without any conversion. This can lead to strange behavior when you use the double-splat operator on a Dash as the first part of a keyword list or built Hash. For example:
|
901
|
+
|
902
|
+
```ruby
|
903
|
+
class Foo < Hashie::Dash
|
904
|
+
property :bar
|
905
|
+
end
|
906
|
+
|
907
|
+
foo = Foo.new(bar: 'baz') #=> {:bar=>"baz"}
|
908
|
+
qux = { **foo, quux: 'corge' } #=> {:bar=> "baz", :quux=>"corge"}
|
909
|
+
qux.is_a?(Foo) #=> true
|
910
|
+
qux[:quux]
|
911
|
+
#=> raise NoMethodError, "The property 'quux' is not defined for Foo."
|
912
|
+
qux.key?(:quux) #=> true
|
913
|
+
```
|
914
|
+
|
915
|
+
You can work around this problem in two ways:
|
916
|
+
|
917
|
+
1. Call `#to_h` on the resulting object to convert it into a Hash.
|
918
|
+
2. Use the double-splat operator on the Dash as the last argument in the Hash literal. This will cause the resulting object to be a Hash instead of a Dash, thereby circumventing the problem.
|
919
|
+
|
920
|
+
```ruby
|
921
|
+
qux = { **foo, quux: 'corge' }.to_h #=> {:bar=> "baz", :quux=>"corge"}
|
922
|
+
qux.is_a?(Hash) #=> true
|
923
|
+
qux[:quux] #=> "corge"
|
924
|
+
|
925
|
+
qux = { quux: 'corge', **foo } #=> {:quux=>"corge", :bar=> "baz"}
|
926
|
+
qux.is_a?(Hash) #=> true
|
927
|
+
qux[:quux] #=> "corge"
|
928
|
+
```
|
929
|
+
|
930
|
+
### PropertyTranslation
|
563
931
|
|
564
932
|
The `Hashie::Extensions::Dash::PropertyTranslation` mixin extends a Dash with
|
565
933
|
the ability to remap keys from a source hash.
|
566
934
|
|
567
|
-
### Example from inconsistent APIs
|
568
|
-
|
569
935
|
Property translation is useful when you need to read data from another
|
570
936
|
application -- such as a Java API -- where the keys are named differently from
|
571
937
|
Ruby conventions.
|
@@ -585,8 +951,6 @@ person[:first_name] #=> 'Michael'
|
|
585
951
|
person[:last_name] #=> 'Bleigh
|
586
952
|
```
|
587
953
|
|
588
|
-
### Example using translation lambdas
|
589
|
-
|
590
954
|
You can also use a lambda to translate the value. This is particularly useful
|
591
955
|
when you want to ensure the type of data you're wrapping.
|
592
956
|
|
@@ -599,7 +963,7 @@ class DataModelHash < Hashie::Dash
|
|
599
963
|
end
|
600
964
|
|
601
965
|
model = DataModelHash.new(id: '123', created: '2014-04-25 22:35:28')
|
602
|
-
model.id.class #=> Fixnum
|
966
|
+
model.id.class #=> Integer (Fixnum if you are using Ruby 2.3 or lower)
|
603
967
|
model.created_at.class #=> Time
|
604
968
|
```
|
605
969
|
|
@@ -607,6 +971,50 @@ model.created_at.class #=> Time
|
|
607
971
|
|
608
972
|
To enable compatibility with Rails 4 use the [hashie-forbidden_attributes](https://github.com/Maxim-Filimonov/hashie-forbidden_attributes) gem.
|
609
973
|
|
974
|
+
### Coercion
|
975
|
+
|
976
|
+
If you want to use `Hashie::Extensions::Coercion` together with `Dash` then
|
977
|
+
you may probably want to use `Hashie::Extensions::Dash::Coercion` instead.
|
978
|
+
This extension automatically includes `Hashie::Extensions::Coercion`
|
979
|
+
and also adds a convenient `:coerce` option to `property` so you can define coercion in one line
|
980
|
+
instead of using `property` and `coerce_key` separate:
|
981
|
+
|
982
|
+
```ruby
|
983
|
+
class UserHash < Hashie::Dash
|
984
|
+
include Hashie::Extensions::Coercion
|
985
|
+
|
986
|
+
property :id
|
987
|
+
property :posts
|
988
|
+
|
989
|
+
coerce_key :posts, Array[PostHash]
|
990
|
+
end
|
991
|
+
```
|
992
|
+
|
993
|
+
This is the same as:
|
994
|
+
|
995
|
+
```ruby
|
996
|
+
class UserHash < Hashie::Dash
|
997
|
+
include Hashie::Extensions::Dash::Coercion
|
998
|
+
|
999
|
+
property :id
|
1000
|
+
property :posts, coerce: Array[PostHash]
|
1001
|
+
end
|
1002
|
+
```
|
1003
|
+
|
1004
|
+
### PredefinedValues
|
1005
|
+
|
1006
|
+
The `Hashie::Extensions::Dash::PredefinedValues` mixin extends a Dash with
|
1007
|
+
the ability to accept predefined values on a property.
|
1008
|
+
|
1009
|
+
```ruby
|
1010
|
+
class UserHash < Hashie::Dash
|
1011
|
+
include Hashie::Extensions::Dash::PredefinedValues
|
1012
|
+
|
1013
|
+
property :gender, values: %i[male female prefer_not_to_say]
|
1014
|
+
property :age, values: (0..150)
|
1015
|
+
end
|
1016
|
+
```
|
1017
|
+
|
610
1018
|
## Trash
|
611
1019
|
|
612
1020
|
A Trash is a Dash that allows you to translate keys on initialization. It mixes
|
@@ -638,7 +1046,7 @@ this will produce the following
|
|
638
1046
|
|
639
1047
|
```ruby
|
640
1048
|
result = Result.new(id: '123', creation_date: '2012-03-30 17:23:28')
|
641
|
-
result.id.class # => Fixnum
|
1049
|
+
result.id.class # => Integer (Fixnum if you are using Ruby 2.3 or lower)
|
642
1050
|
result.created_at.class # => Time
|
643
1051
|
```
|
644
1052
|
|
@@ -648,8 +1056,6 @@ Clash is a Chainable Lazy Hash that allows you to easily construct complex hashe
|
|
648
1056
|
|
649
1057
|
Essentially, a Clash is a generalized way to provide much of the same kind of "chainability" that libraries like Arel or Rails 2.x's named_scopes provide.
|
650
1058
|
|
651
|
-
### Example:
|
652
|
-
|
653
1059
|
```ruby
|
654
1060
|
c = Hashie::Clash.new
|
655
1061
|
c.where(abc: 'def').order(:created_at)
|
@@ -675,8 +1081,6 @@ A good use case for the Rash is an URL router for a web framework, where URLs ne
|
|
675
1081
|
|
676
1082
|
If the Rash's value is a `proc`, the `proc` will be automatically called with the regexp's MatchData (matched groups) as a block argument.
|
677
1083
|
|
678
|
-
### Example:
|
679
|
-
|
680
1084
|
```ruby
|
681
1085
|
|
682
1086
|
# Mapping names to appropriate greetings
|
@@ -693,18 +1097,21 @@ mapper["I like traffic lights"] # => "Who DOESN'T like traffic lights?!"
|
|
693
1097
|
mapper["Get off my lawn!"] # => "Forget your lawn, old man!"
|
694
1098
|
```
|
695
1099
|
|
696
|
-
### Auto-
|
1100
|
+
### Auto-Optimized
|
697
1101
|
|
698
1102
|
**Note:** The Rash is automatically optimized every 500 accesses (which means that it sorts the list of Regexps, putting the most frequently matched ones at the beginning).
|
699
1103
|
|
700
1104
|
If this value is too low or too high for your needs, you can tune it by setting: `rash.optimize_every = n`.
|
701
1105
|
|
1106
|
+
## Mascot
|
1107
|
+
[![eierlegende Wollmilchsau](./mascot.svg)](https://en.wiktionary.org/wiki/eierlegende_Wollmilchsau) Meet Hashie's "offical" mascot, the [eierlegende Wollmilchsau](https://en.wiktionary.org/wiki/eierlegende_Wollmilchsau)!
|
1108
|
+
|
702
1109
|
## Contributing
|
703
1110
|
|
704
1111
|
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
705
1112
|
|
706
1113
|
## Copyright
|
707
1114
|
|
708
|
-
Copyright (c) 2009-
|
1115
|
+
Copyright (c) 2009-2020 [Intridea, Inc.](http://intridea.com), and [contributors](https://github.com/hashie/hashie/graphs/contributors).
|
709
1116
|
|
710
1117
|
MIT License. See [LICENSE](LICENSE) for details.
|