hashie 3.4.2 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +518 -122
  3. data/CONTRIBUTING.md +24 -7
  4. data/LICENSE +1 -1
  5. data/README.md +455 -48
  6. data/Rakefile +18 -1
  7. data/UPGRADING.md +157 -7
  8. data/hashie.gemspec +14 -7
  9. data/lib/hashie/array.rb +21 -0
  10. data/lib/hashie/clash.rb +24 -12
  11. data/lib/hashie/dash.rb +56 -31
  12. data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
  13. data/lib/hashie/extensions/array/pretty_inspect.rb +19 -0
  14. data/lib/hashie/extensions/coercion.rb +91 -52
  15. data/lib/hashie/extensions/dash/coercion.rb +25 -0
  16. data/lib/hashie/extensions/dash/indifferent_access.rb +30 -1
  17. data/lib/hashie/extensions/dash/predefined_values.rb +88 -0
  18. data/lib/hashie/extensions/dash/property_translation.rb +59 -30
  19. data/lib/hashie/extensions/deep_fetch.rb +5 -3
  20. data/lib/hashie/extensions/deep_find.rb +14 -5
  21. data/lib/hashie/extensions/deep_locate.rb +40 -21
  22. data/lib/hashie/extensions/deep_merge.rb +28 -10
  23. data/lib/hashie/extensions/ignore_undeclared.rb +6 -4
  24. data/lib/hashie/extensions/indifferent_access.rb +49 -8
  25. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  26. data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
  27. data/lib/hashie/extensions/mash/keep_original_keys.rb +53 -0
  28. data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
  29. data/lib/hashie/extensions/mash/safe_assignment.rb +3 -1
  30. data/lib/hashie/extensions/mash/symbolize_keys.rb +38 -0
  31. data/lib/hashie/extensions/method_access.rb +77 -19
  32. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +29 -5
  33. data/lib/hashie/extensions/ruby_version.rb +60 -0
  34. data/lib/hashie/extensions/ruby_version_check.rb +21 -0
  35. data/lib/hashie/extensions/strict_key_access.rb +77 -0
  36. data/lib/hashie/extensions/stringify_keys.rb +8 -5
  37. data/lib/hashie/extensions/symbolize_keys.rb +21 -7
  38. data/lib/hashie/hash.rb +18 -11
  39. data/lib/hashie/logger.rb +18 -0
  40. data/lib/hashie/mash.rb +196 -55
  41. data/lib/hashie/railtie.rb +21 -0
  42. data/lib/hashie/rash.rb +7 -7
  43. data/lib/hashie/utils.rb +44 -0
  44. data/lib/hashie/version.rb +1 -1
  45. data/lib/hashie.rb +34 -16
  46. metadata +30 -79
  47. data/spec/hashie/clash_spec.rb +0 -48
  48. data/spec/hashie/dash_spec.rb +0 -513
  49. data/spec/hashie/extensions/autoload_spec.rb +0 -24
  50. data/spec/hashie/extensions/coercion_spec.rb +0 -625
  51. data/spec/hashie/extensions/dash/indifferent_access_spec.rb +0 -84
  52. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -97
  53. data/spec/hashie/extensions/deep_find_spec.rb +0 -45
  54. data/spec/hashie/extensions/deep_locate_spec.rb +0 -124
  55. data/spec/hashie/extensions/deep_merge_spec.rb +0 -45
  56. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -46
  57. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -219
  58. data/spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb +0 -208
  59. data/spec/hashie/extensions/key_conversion_spec.rb +0 -12
  60. data/spec/hashie/extensions/mash/safe_assignment_spec.rb +0 -23
  61. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  62. data/spec/hashie/extensions/method_access_spec.rb +0 -184
  63. data/spec/hashie/extensions/stringify_keys_spec.rb +0 -101
  64. data/spec/hashie/extensions/symbolize_keys_spec.rb +0 -106
  65. data/spec/hashie/hash_spec.rb +0 -84
  66. data/spec/hashie/mash_spec.rb +0 -683
  67. data/spec/hashie/parsers/yaml_erb_parser_spec.rb +0 -29
  68. data/spec/hashie/rash_spec.rb +0 -77
  69. data/spec/hashie/trash_spec.rb +0 -268
  70. data/spec/hashie/version_spec.rb +0 -7
  71. data/spec/spec_helper.rb +0 -15
  72. data/spec/support/module_context.rb +0 -11
  73. 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/intridea/hashie](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/intridea/hashie?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
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](http://img.shields.io/travis/intridea/hashie.svg)](https://travis-ci.org/intridea/hashie)
7
- [![Dependency Status](https://gemnasium.com/intridea/hashie.svg)](https://gemnasium.com/intridea/hashie)
8
- [![Code Climate](https://codeclimate.com/github/intridea/hashie.svg)](https://codeclimate.com/github/intridea/hashie)
9
- [![Coverage Status](https://codeclimate.com/github/intridea/hashie/badges/coverage.svg)](https://codeclimate.com/github/intridea/hashie)
10
-
11
- Hashie is a growing collection of tools that extend Hashes and make them more useful.
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
- ## Upgrading
63
+ ## Stable Release
22
64
 
23
- You're reading the documentation for the stable release of Hashie, which is 3.4.2. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
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 includeable 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.
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 coercion boolean values, since Ruby does not have a built-in boolean type or standard method for to a boolean. You can coerce to booleans using a custom proc.
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
- return !!(v =~ /^(true|t|yes|y|1)$/i)
190
+ !!(v =~ /\A(true|t|yes|y|1)\z/i)
139
191
  when Numeric
140
- return !v.to_i.zero?
192
+ !v.to_i.zero?
141
193
  else
142
- return v == true
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 instantly give you indifferent access to your Hash subclass. This works just like the params hash in Rails and other frameworks where whether you provide symbols or strings to access keys, you will get the same results.
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
- A unique feature of Hashie's IndifferentAccess mixin is that it will inject itself recursively into subhashes *without* reinitializing the hash in question. This means you can safely merge together indifferent and non-indifferent hashes arbitrarily deeply without worrying about whether you'll be able to `hash[:other][:another]` properly.
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 allow you to easily include a recursive merging
271
- system to any Hash descendant:
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
- 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.
541
+ _How does Mash handle conflicts with pre-existing methods?_
424
542
 
425
- ### Example:
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 allows you also to transform any files into a Mash objects.
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
- ### Example:
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
- ### Mash Extension: SafeAssignment
689
+ The `Mash#load` method calls `YAML.safe_load(path, [], [], true)`.
478
690
 
479
- 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.
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
- #### Example:
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
- ### Dash Extension: PropertyTranslation
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-optimized
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-2014 Intridea, Inc. (http://intridea.com/) and [contributors](https://github.com/intridea/hashie/graphs/contributors).
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.