hashie 4.1.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -53
- data/LICENSE +1 -1
- data/README.md +131 -51
- data/Rakefile +3 -0
- data/UPGRADING.md +38 -0
- data/hashie.gemspec +4 -0
- data/lib/hashie/dash.rb +17 -17
- data/lib/hashie/extensions/coercion.rb +1 -8
- data/lib/hashie/extensions/dash/indifferent_access.rb +9 -0
- data/lib/hashie/extensions/dash/predefined_values.rb +88 -0
- data/lib/hashie/extensions/dash/property_translation.rb +6 -1
- data/lib/hashie/extensions/deep_locate.rb +1 -1
- data/lib/hashie/extensions/ignore_undeclared.rb +4 -5
- data/lib/hashie/extensions/indifferent_access.rb +36 -3
- data/lib/hashie/extensions/mash/symbolize_keys.rb +5 -5
- data/lib/hashie/extensions/method_access.rb +1 -1
- data/lib/hashie/extensions/symbolize_keys.rb +12 -1
- data/lib/hashie/hash.rb +2 -2
- data/lib/hashie/mash.rb +26 -25
- data/lib/hashie/version.rb +1 -1
- data/lib/hashie.rb +1 -0
- metadata +22 -7
data/README.md
CHANGED
|
@@ -2,11 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://gitter.im/hashie/hashie?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
4
4
|
[](http://badge.fury.io/rb/hashie)
|
|
5
|
-
[](https://github.com/hashie/hashie/actions/workflows/test.yml)
|
|
6
|
+
|
|
7
|
+
[](#mascot) Hashie is a growing collection of tools that extend Hashes and make them more useful.
|
|
8
|
+
|
|
9
|
+
# Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Stable Release](#stable-release)
|
|
13
|
+
- [Hash Extensions](#hash-extensions)
|
|
14
|
+
- [Logging](#logging)
|
|
15
|
+
- [Coercion](#coercion)
|
|
16
|
+
- [Coercing Collections](#coercing-collections)
|
|
17
|
+
- [Coercing Hashes](#coercing-hashes)
|
|
18
|
+
- [Coercing Core Types](#coercing-core-types)
|
|
19
|
+
- [Coercion Proc](#coercion-proc)
|
|
20
|
+
- [A note on circular coercion](#a-note-on-circular-coercion)
|
|
21
|
+
- [KeyConversion](#keyconversion)
|
|
22
|
+
- [MergeInitializer](#mergeinitializer)
|
|
23
|
+
- [MethodAccess](#methodaccess)
|
|
24
|
+
- [MethodAccessWithOverride](#methodaccesswithoverride)
|
|
25
|
+
- [MethodOverridingInitializer](#methodoverridinginitializer)
|
|
26
|
+
- [IndifferentAccess](#indifferentaccess)
|
|
27
|
+
- [IgnoreUndeclared](#ignoreundeclared)
|
|
28
|
+
- [DeepMerge](#deepmerge)
|
|
29
|
+
- [DeepFetch](#deepfetch)
|
|
30
|
+
- [DeepFind](#deepfind)
|
|
31
|
+
- [DeepLocate](#deeplocate)
|
|
32
|
+
- [StrictKeyAccess](#strictkeyaccess)
|
|
33
|
+
- [Mash](#mash)
|
|
34
|
+
- [KeepOriginalKeys](#keeporiginalkeys)
|
|
35
|
+
- [PermissiveRespondTo](#permissiverespondto)
|
|
36
|
+
- [SafeAssignment](#safeassignment)
|
|
37
|
+
- [SymbolizeKeys](#symbolizekeys)
|
|
38
|
+
- [DefineAccessors](#defineaccessors)
|
|
39
|
+
- [Dash](#dash)
|
|
40
|
+
- [Potential Gotchas](#potential-gotchas)
|
|
41
|
+
- [PropertyTranslation](#propertytranslation)
|
|
42
|
+
- [Mash and Rails 4 Strong Parameters](#mash-and-rails-4-strong-parameters)
|
|
43
|
+
- [Coercion](#coercion-1)
|
|
44
|
+
- [PredefinedValues](#predefinedvalues)
|
|
45
|
+
- [Trash](#trash)
|
|
46
|
+
- [Clash](#clash)
|
|
47
|
+
- [Rash](#rash)
|
|
48
|
+
- [Auto-Optimized](#auto-optimized)
|
|
49
|
+
- [Mascot](#mascot)
|
|
50
|
+
- [Contributing](#contributing)
|
|
51
|
+
- [Copyright](#copyright)
|
|
10
52
|
|
|
11
53
|
## Installation
|
|
12
54
|
|
|
@@ -18,7 +60,7 @@ $ gem install hashie
|
|
|
18
60
|
|
|
19
61
|
## Stable Release
|
|
20
62
|
|
|
21
|
-
You're reading the documentation for the stable release of Hashie,
|
|
63
|
+
You're reading the documentation for the stable release of Hashie, 5.1.0.
|
|
22
64
|
|
|
23
65
|
## Hash Extensions
|
|
24
66
|
|
|
@@ -190,13 +232,13 @@ end
|
|
|
190
232
|
|
|
191
233
|
### KeyConversion
|
|
192
234
|
|
|
193
|
-
The KeyConversion extension gives you the convenience methods of `symbolize_keys` and `stringify_keys` along with their bang counterparts. You can also include just stringify or just symbolize with `Hashie::Extensions::StringifyKeys` or
|
|
235
|
+
The KeyConversion extension gives you the convenience methods of `symbolize_keys` and `stringify_keys` along with their bang counterparts. You can also include just stringify or just symbolize with `Hashie::Extensions::StringifyKeys` or `Hashie::Extensions::SymbolizeKeys`.
|
|
194
236
|
|
|
195
237
|
Hashie also has a utility method for converting keys on a Hash without a mixin:
|
|
196
238
|
|
|
197
239
|
```ruby
|
|
198
|
-
Hashie.symbolize_keys! hash # => Symbolizes keys of hash.
|
|
199
|
-
Hashie.symbolize_keys hash # => Returns a copy of hash with keys symbolized.
|
|
240
|
+
Hashie.symbolize_keys! hash # => Symbolizes all string keys of hash.
|
|
241
|
+
Hashie.symbolize_keys hash # => Returns a copy of hash with string keys symbolized.
|
|
200
242
|
Hashie.stringify_keys! hash # => Stringifies keys of hash.
|
|
201
243
|
Hashie.stringify_keys hash # => Returns a copy of hash with keys stringified.
|
|
202
244
|
```
|
|
@@ -269,8 +311,6 @@ This extension can be mixed in to your Hash subclass to allow you to use Strings
|
|
|
269
311
|
|
|
270
312
|
In addition, IndifferentAccess will also inject itself into sub-hashes so they behave the same.
|
|
271
313
|
|
|
272
|
-
Example:
|
|
273
|
-
|
|
274
314
|
```ruby
|
|
275
315
|
class MyHash < Hash
|
|
276
316
|
include Hashie::Extensions::MergeInitializer
|
|
@@ -290,6 +330,18 @@ myhash['fishes'][:food] = 'flakes'
|
|
|
290
330
|
myhash['fishes']['food'] # => "flakes"
|
|
291
331
|
```
|
|
292
332
|
|
|
333
|
+
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:
|
|
334
|
+
|
|
335
|
+
```ruby
|
|
336
|
+
myhash = MyHash.new
|
|
337
|
+
myhash["foo"] = "bar"
|
|
338
|
+
myhash[:foo] #=> "bar"
|
|
339
|
+
|
|
340
|
+
normal_hash = myhash.to_hash
|
|
341
|
+
myhash["foo"] #=> "bar"
|
|
342
|
+
myhash[:foo] #=> nil
|
|
343
|
+
```
|
|
344
|
+
|
|
293
345
|
### IgnoreUndeclared
|
|
294
346
|
|
|
295
347
|
This extension can be mixed in to silently ignore undeclared properties on initialization instead of raising an error. This is useful when using a Trash to capture a subset of a larger hash.
|
|
@@ -445,8 +497,6 @@ books.deep_locate -> (key, value, object) { key == :pages && value <= 120 }
|
|
|
445
497
|
|
|
446
498
|
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.
|
|
447
499
|
|
|
448
|
-
### Example:
|
|
449
|
-
|
|
450
500
|
```ruby
|
|
451
501
|
class StrictKeyAccessHash < Hash
|
|
452
502
|
include Hashie::Extensions::StrictKeyAccess
|
|
@@ -464,8 +514,6 @@ end
|
|
|
464
514
|
|
|
465
515
|
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.
|
|
466
516
|
|
|
467
|
-
### Example:
|
|
468
|
-
|
|
469
517
|
```ruby
|
|
470
518
|
mash = Hashie::Mash.new
|
|
471
519
|
mash.name? # => false
|
|
@@ -488,12 +536,10 @@ mash.inspect # => <Hashie::Mash>
|
|
|
488
536
|
|
|
489
537
|
**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.
|
|
490
538
|
|
|
491
|
-
|
|
539
|
+
_How does Mash handle conflicts with pre-existing methods?_
|
|
492
540
|
|
|
493
541
|
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.
|
|
494
542
|
|
|
495
|
-
#### Example:
|
|
496
|
-
|
|
497
543
|
```ruby
|
|
498
544
|
mash = Hashie::Mash.new
|
|
499
545
|
mash.name = "My Mash"
|
|
@@ -564,12 +610,10 @@ Hashie::Mash.quiet.new(zip: '90210', compact: true) # no errors logged
|
|
|
564
610
|
Hashie::Mash.quiet(:zip).new(zip: '90210', compact: true) # error logged for compact
|
|
565
611
|
```
|
|
566
612
|
|
|
567
|
-
|
|
613
|
+
_How does the wrapping of Mash sub-Hashes work?_
|
|
568
614
|
|
|
569
615
|
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.
|
|
570
616
|
|
|
571
|
-
#### Example:
|
|
572
|
-
|
|
573
617
|
```ruby
|
|
574
618
|
mash = Hashie::Mash.new(name: "Hashie", dependencies: { rake: "< 11", rspec: "~> 3.0" })
|
|
575
619
|
mash.dependencies.class #=> Hashie::Mash
|
|
@@ -579,11 +623,20 @@ my_gem = MyGem.new(name: "Hashie", dependencies: { rake: "< 11", rspec: "~> 3.0"
|
|
|
579
623
|
my_gem.dependencies.class #=> MyGem
|
|
580
624
|
```
|
|
581
625
|
|
|
582
|
-
|
|
626
|
+
_How does Mash handle key types which cannot be symbolized?_
|
|
583
627
|
|
|
584
|
-
Mash
|
|
628
|
+
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 pseudo-object functionality, handling keys which cannot represent a method call falls outside its scope of value.
|
|
629
|
+
|
|
630
|
+
```ruby
|
|
631
|
+
Hashie::Mash.new('1' => 'one string', :'1' => 'one sym', 1 => 'one num')
|
|
632
|
+
# => {"1"=>"one sym", 1=>"one num"}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
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'`.
|
|
585
636
|
|
|
586
|
-
|
|
637
|
+
_What else can Mash do?_
|
|
638
|
+
|
|
639
|
+
Mash allows you also to transform any files into a Mash objects.
|
|
587
640
|
|
|
588
641
|
```yml
|
|
589
642
|
#/etc/config/settings/twitter.yml
|
|
@@ -639,9 +692,9 @@ Specify `permitted_symbols`, `permitted_classes` and `aliases` options as needed
|
|
|
639
692
|
Mash.load('data/user.csv', permitted_classes: [Symbol], permitted_symbols: [], aliases: false)
|
|
640
693
|
```
|
|
641
694
|
|
|
642
|
-
###
|
|
695
|
+
### KeepOriginalKeys
|
|
643
696
|
|
|
644
|
-
This extension can be mixed into a Mash to keep the form of any keys passed directly into the Mash. By default, Mash converts 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.
|
|
697
|
+
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.
|
|
645
698
|
|
|
646
699
|
```ruby
|
|
647
700
|
class KeepingMash < ::Hashie::Mash
|
|
@@ -658,7 +711,7 @@ mash['string_key'] #=> 'string'
|
|
|
658
711
|
mash[:string_key] #=> 'string'
|
|
659
712
|
```
|
|
660
713
|
|
|
661
|
-
###
|
|
714
|
+
### PermissiveRespondTo
|
|
662
715
|
|
|
663
716
|
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:
|
|
664
717
|
|
|
@@ -682,12 +735,10 @@ mash.respond_to? :b #=> true
|
|
|
682
735
|
|
|
683
736
|
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.
|
|
684
737
|
|
|
685
|
-
###
|
|
738
|
+
### SafeAssignment
|
|
686
739
|
|
|
687
740
|
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.
|
|
688
741
|
|
|
689
|
-
#### Example:
|
|
690
|
-
|
|
691
742
|
```ruby
|
|
692
743
|
class SafeMash < ::Hashie::Mash
|
|
693
744
|
include Hashie::Extensions::Mash::SafeAssignment
|
|
@@ -698,9 +749,9 @@ safe_mash.zip = 'Test' # => ArgumentError
|
|
|
698
749
|
safe_mash[:zip] = 'test' # => still ArgumentError
|
|
699
750
|
```
|
|
700
751
|
|
|
701
|
-
###
|
|
752
|
+
### SymbolizeKeys
|
|
702
753
|
|
|
703
|
-
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 keys to symbols. It can be useful to use with keywords argument, which required symbol keys.
|
|
754
|
+
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.
|
|
704
755
|
|
|
705
756
|
```ruby
|
|
706
757
|
class SymbolizedMash < ::Hashie::Mash
|
|
@@ -731,7 +782,7 @@ end
|
|
|
731
782
|
|
|
732
783
|
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.
|
|
733
784
|
|
|
734
|
-
###
|
|
785
|
+
### DefineAccessors
|
|
735
786
|
|
|
736
787
|
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.
|
|
737
788
|
|
|
@@ -758,12 +809,10 @@ mash = ::Hashie::Mash.new.with_accessors!
|
|
|
758
809
|
|
|
759
810
|
## Dash
|
|
760
811
|
|
|
761
|
-
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.
|
|
812
|
+
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. A property with a proc value will be evaluated lazily upon retrieval.
|
|
762
813
|
|
|
763
814
|
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.
|
|
764
815
|
|
|
765
|
-
### Example:
|
|
766
|
-
|
|
767
816
|
```ruby
|
|
768
817
|
class Person < Hashie::Dash
|
|
769
818
|
property :name, required: true
|
|
@@ -772,6 +821,7 @@ class Person < Hashie::Dash
|
|
|
772
821
|
property :phone, required: -> { email.nil? }, message: 'is required if email is not set.'
|
|
773
822
|
property :pants, required: :weekday?, message: 'are only required on weekdays.'
|
|
774
823
|
property :occupation, default: 'Rubyist'
|
|
824
|
+
property :genome
|
|
775
825
|
|
|
776
826
|
def weekday?
|
|
777
827
|
[ Time.now.saturday?, Time.now.sunday? ].none?
|
|
@@ -796,12 +846,12 @@ p.occupation # => 'Evil'
|
|
|
796
846
|
p.name # => 'Trudy'
|
|
797
847
|
p.update_attributes!(occupation: nil)
|
|
798
848
|
p.occupation # => 'Rubyist'
|
|
849
|
+
p.genome = -> { Genome.sequence } # Some expensive operation
|
|
850
|
+
p.genome # => 'GATTACA'
|
|
799
851
|
```
|
|
800
852
|
|
|
801
853
|
Properties defined as symbols are not the same thing as properties defined as strings.
|
|
802
854
|
|
|
803
|
-
### Example:
|
|
804
|
-
|
|
805
855
|
```ruby
|
|
806
856
|
class Tricky < Hashie::Dash
|
|
807
857
|
property :trick
|
|
@@ -825,7 +875,28 @@ p = Tricky.new('trick' => 'two')
|
|
|
825
875
|
p.trick # => NoMethodError
|
|
826
876
|
```
|
|
827
877
|
|
|
828
|
-
|
|
878
|
+
If you would like to update a Dash and use any default values set in the case of a `nil` value, use `#update_attributes!`.
|
|
879
|
+
|
|
880
|
+
```ruby
|
|
881
|
+
class WithDefaults < Hashie::Dash
|
|
882
|
+
property :description, default: 'none'
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
dash = WithDefaults.new
|
|
886
|
+
dash.description #=> 'none'
|
|
887
|
+
|
|
888
|
+
dash.description = 'You committed one of the classic blunders!'
|
|
889
|
+
dash.description #=> 'You committed one of the classic blunders!'
|
|
890
|
+
|
|
891
|
+
dash.description = nil
|
|
892
|
+
dash.description #=> nil
|
|
893
|
+
|
|
894
|
+
dash.description = 'Only slightly less known is ...'
|
|
895
|
+
dash.update_attributes!(description: nil)
|
|
896
|
+
dash.description #=> 'none'
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
### Potential Gotchas
|
|
829
900
|
|
|
830
901
|
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:
|
|
831
902
|
|
|
@@ -857,13 +928,11 @@ qux.is_a?(Hash) #=> true
|
|
|
857
928
|
qux[:quux] #=> "corge"
|
|
858
929
|
```
|
|
859
930
|
|
|
860
|
-
###
|
|
931
|
+
### PropertyTranslation
|
|
861
932
|
|
|
862
933
|
The `Hashie::Extensions::Dash::PropertyTranslation` mixin extends a Dash with
|
|
863
934
|
the ability to remap keys from a source hash.
|
|
864
935
|
|
|
865
|
-
### Example from inconsistent APIs
|
|
866
|
-
|
|
867
936
|
Property translation is useful when you need to read data from another
|
|
868
937
|
application -- such as a Java API -- where the keys are named differently from
|
|
869
938
|
Ruby conventions.
|
|
@@ -883,8 +952,6 @@ person[:first_name] #=> 'Michael'
|
|
|
883
952
|
person[:last_name] #=> 'Bleigh
|
|
884
953
|
```
|
|
885
954
|
|
|
886
|
-
### Example using translation lambdas
|
|
887
|
-
|
|
888
955
|
You can also use a lambda to translate the value. This is particularly useful
|
|
889
956
|
when you want to ensure the type of data you're wrapping.
|
|
890
957
|
|
|
@@ -905,7 +972,7 @@ model.created_at.class #=> Time
|
|
|
905
972
|
|
|
906
973
|
To enable compatibility with Rails 4 use the [hashie-forbidden_attributes](https://github.com/Maxim-Filimonov/hashie-forbidden_attributes) gem.
|
|
907
974
|
|
|
908
|
-
###
|
|
975
|
+
### Coercion
|
|
909
976
|
|
|
910
977
|
If you want to use `Hashie::Extensions::Coercion` together with `Dash` then
|
|
911
978
|
you may probably want to use `Hashie::Extensions::Dash::Coercion` instead.
|
|
@@ -935,6 +1002,20 @@ class UserHash < Hashie::Dash
|
|
|
935
1002
|
end
|
|
936
1003
|
```
|
|
937
1004
|
|
|
1005
|
+
### PredefinedValues
|
|
1006
|
+
|
|
1007
|
+
The `Hashie::Extensions::Dash::PredefinedValues` mixin extends a Dash with
|
|
1008
|
+
the ability to accept predefined values on a property.
|
|
1009
|
+
|
|
1010
|
+
```ruby
|
|
1011
|
+
class UserHash < Hashie::Dash
|
|
1012
|
+
include Hashie::Extensions::Dash::PredefinedValues
|
|
1013
|
+
|
|
1014
|
+
property :gender, values: %i[male female prefer_not_to_say]
|
|
1015
|
+
property :age, values: (0..150)
|
|
1016
|
+
end
|
|
1017
|
+
```
|
|
1018
|
+
|
|
938
1019
|
## Trash
|
|
939
1020
|
|
|
940
1021
|
A Trash is a Dash that allows you to translate keys on initialization. It mixes
|
|
@@ -976,8 +1057,6 @@ Clash is a Chainable Lazy Hash that allows you to easily construct complex hashe
|
|
|
976
1057
|
|
|
977
1058
|
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.
|
|
978
1059
|
|
|
979
|
-
### Example:
|
|
980
|
-
|
|
981
1060
|
```ruby
|
|
982
1061
|
c = Hashie::Clash.new
|
|
983
1062
|
c.where(abc: 'def').order(:created_at)
|
|
@@ -1003,8 +1082,6 @@ A good use case for the Rash is an URL router for a web framework, where URLs ne
|
|
|
1003
1082
|
|
|
1004
1083
|
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.
|
|
1005
1084
|
|
|
1006
|
-
### Example:
|
|
1007
|
-
|
|
1008
1085
|
```ruby
|
|
1009
1086
|
|
|
1010
1087
|
# Mapping names to appropriate greetings
|
|
@@ -1021,18 +1098,21 @@ mapper["I like traffic lights"] # => "Who DOESN'T like traffic lights?!"
|
|
|
1021
1098
|
mapper["Get off my lawn!"] # => "Forget your lawn, old man!"
|
|
1022
1099
|
```
|
|
1023
1100
|
|
|
1024
|
-
### Auto-
|
|
1101
|
+
### Auto-Optimized
|
|
1025
1102
|
|
|
1026
1103
|
**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).
|
|
1027
1104
|
|
|
1028
1105
|
If this value is too low or too high for your needs, you can tune it by setting: `rash.optimize_every = n`.
|
|
1029
1106
|
|
|
1107
|
+
## Mascot
|
|
1108
|
+
[](https://en.wiktionary.org/wiki/eierlegende_Wollmilchsau) Meet Hashie's "official" mascot, the [eierlegende Wollmilchsau](https://en.wiktionary.org/wiki/eierlegende_Wollmilchsau)!
|
|
1109
|
+
|
|
1030
1110
|
## Contributing
|
|
1031
1111
|
|
|
1032
1112
|
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
1033
1113
|
|
|
1034
1114
|
## Copyright
|
|
1035
1115
|
|
|
1036
|
-
Copyright (c) 2009-
|
|
1116
|
+
Copyright (c) 2009-2020 [Intridea, Inc.](http://intridea.com), and [contributors](https://github.com/hashie/hashie/graphs/contributors).
|
|
1037
1117
|
|
|
1038
1118
|
MIT License. See [LICENSE](LICENSE) for details.
|
data/Rakefile
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'rubygems'
|
|
2
4
|
require 'bundler'
|
|
3
5
|
Bundler.setup
|
|
@@ -16,6 +18,7 @@ RuboCop::RakeTask.new(:rubocop)
|
|
|
16
18
|
require_relative 'spec/support/integration_specs'
|
|
17
19
|
task :integration_specs do
|
|
18
20
|
next if ENV['CI']
|
|
21
|
+
|
|
19
22
|
status_codes = []
|
|
20
23
|
handler = lambda do |status_code|
|
|
21
24
|
status_codes << status_code unless status_code.zero?
|
data/UPGRADING.md
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
Upgrading Hashie
|
|
2
2
|
================
|
|
3
3
|
|
|
4
|
+
### Upgrading to 5.0.0
|
|
5
|
+
|
|
6
|
+
#### Mash initialization key conversion
|
|
7
|
+
|
|
8
|
+
Mash initialization now only converts to string keys which can be represented as symbols.
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
Hashie::Mash.new(
|
|
12
|
+
{foo: "bar"} => "baz",
|
|
13
|
+
"1" => "one string",
|
|
14
|
+
:"1" => "one sym",
|
|
15
|
+
1 => "one num"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Before
|
|
19
|
+
{"{:foo=>\"bar\"}"=>"baz", "1"=>"one num"}
|
|
20
|
+
|
|
21
|
+
# After
|
|
22
|
+
{{:foo=>"bar"}=>"baz", "1"=>"one sym", 1=>"one num"}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
#### Mash#dig with numeric keys
|
|
26
|
+
|
|
27
|
+
`Hashie::Mash#dig` no longer considers numeric keys for indifferent access.
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
my_mash = Hashie::Mash.new("1" => "a") # => {"1"=>"a"}
|
|
31
|
+
|
|
32
|
+
my_mash.dig("1") # => "a"
|
|
33
|
+
my_mash.dig(:"1") # => "a"
|
|
34
|
+
|
|
35
|
+
# Before
|
|
36
|
+
my_mash.dig(1) # => "a"
|
|
37
|
+
|
|
38
|
+
# After
|
|
39
|
+
my_mash.dig(1) # => nil
|
|
40
|
+
```
|
|
41
|
+
|
|
4
42
|
### Upgrading to 4.0.0
|
|
5
43
|
|
|
6
44
|
#### Non-destructive Hash methods called on Mash
|
data/hashie.gemspec
CHANGED
data/lib/hashie/dash.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Hashie
|
|
|
10
10
|
# lightweight data object that needs even fewer options and
|
|
11
11
|
# resources than something like a DataMapper resource.
|
|
12
12
|
#
|
|
13
|
-
# It is
|
|
13
|
+
# It is preferable to a Struct because of the in-class
|
|
14
14
|
# API for defining properties as well as per-property defaults.
|
|
15
15
|
class Dash < Hash
|
|
16
16
|
include Hashie::Extensions::PrettyInspect
|
|
@@ -104,19 +104,6 @@ module Hashie
|
|
|
104
104
|
def initialize(attributes = {}, &block)
|
|
105
105
|
super(&block)
|
|
106
106
|
|
|
107
|
-
self.class.defaults.each_pair do |prop, value|
|
|
108
|
-
self[prop] = begin
|
|
109
|
-
val = value.dup
|
|
110
|
-
if val.is_a?(Proc)
|
|
111
|
-
val.arity == 1 ? val.call(self) : val.call
|
|
112
|
-
else
|
|
113
|
-
val
|
|
114
|
-
end
|
|
115
|
-
rescue TypeError
|
|
116
|
-
value
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
107
|
initialize_attributes(attributes)
|
|
121
108
|
assert_required_attributes_set!
|
|
122
109
|
end
|
|
@@ -169,17 +156,30 @@ module Hashie
|
|
|
169
156
|
self
|
|
170
157
|
end
|
|
171
158
|
|
|
159
|
+
def to_h
|
|
160
|
+
defaults = ::Hash[self.class.properties.map { |prop| [prop, self.class.defaults[prop]] }]
|
|
161
|
+
|
|
162
|
+
defaults.merge(self)
|
|
163
|
+
end
|
|
164
|
+
alias to_hash to_h
|
|
165
|
+
|
|
172
166
|
def update_attributes!(attributes)
|
|
173
167
|
update_attributes(attributes)
|
|
174
168
|
|
|
175
169
|
self.class.defaults.each_pair do |prop, value|
|
|
176
|
-
next unless
|
|
170
|
+
next unless fetch(prop, nil).nil?
|
|
177
171
|
self[prop] = begin
|
|
178
|
-
value.dup
|
|
172
|
+
val = value.dup
|
|
173
|
+
if val.is_a?(Proc)
|
|
174
|
+
val.arity == 1 ? val.call(self) : val.call
|
|
175
|
+
else
|
|
176
|
+
val
|
|
177
|
+
end
|
|
179
178
|
rescue TypeError
|
|
180
179
|
value
|
|
181
180
|
end
|
|
182
181
|
end
|
|
182
|
+
|
|
183
183
|
assert_required_attributes_set!
|
|
184
184
|
end
|
|
185
185
|
|
|
@@ -189,7 +189,7 @@ module Hashie
|
|
|
189
189
|
return unless attributes
|
|
190
190
|
|
|
191
191
|
cleaned_attributes = attributes.reject { |_attr, value| value.nil? }
|
|
192
|
-
update_attributes(cleaned_attributes)
|
|
192
|
+
update_attributes!(cleaned_attributes)
|
|
193
193
|
end
|
|
194
194
|
|
|
195
195
|
def update_attributes(attributes)
|
|
@@ -17,14 +17,7 @@ module Hashie
|
|
|
17
17
|
}.freeze
|
|
18
18
|
|
|
19
19
|
ABSTRACT_CORE_TYPES =
|
|
20
|
-
|
|
21
|
-
{ Numeric => [Integer, Float, Complex, Rational] }
|
|
22
|
-
else
|
|
23
|
-
{
|
|
24
|
-
Integer => [Fixnum, Bignum],
|
|
25
|
-
Numeric => [Fixnum, Bignum, Float, Complex, Rational]
|
|
26
|
-
}
|
|
27
|
-
end
|
|
20
|
+
{ Numeric => [Integer, Float, Complex, Rational] }
|
|
28
21
|
|
|
29
22
|
def self.included(base)
|
|
30
23
|
base.send :include, InstanceMethods
|
|
@@ -19,6 +19,15 @@ module Hashie
|
|
|
19
19
|
end
|
|
20
20
|
private_class_method :requires_class_methods?
|
|
21
21
|
|
|
22
|
+
def to_h
|
|
23
|
+
defaults = ::Hash[self.class.properties.map do |prop|
|
|
24
|
+
[Hashie::Extensions::IndifferentAccess.convert_key(prop), self.class.defaults[prop]]
|
|
25
|
+
end]
|
|
26
|
+
|
|
27
|
+
defaults.merge(self)
|
|
28
|
+
end
|
|
29
|
+
alias to_hash to_h
|
|
30
|
+
|
|
22
31
|
module ClassMethods
|
|
23
32
|
# Check to see if the specified property has already been
|
|
24
33
|
# defined.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module Hashie
|
|
2
|
+
module Extensions
|
|
3
|
+
module Dash
|
|
4
|
+
# Extends a Dash with the ability to accept only predefined values on a property.
|
|
5
|
+
#
|
|
6
|
+
# == Example
|
|
7
|
+
#
|
|
8
|
+
# class PersonHash < Hashie::Dash
|
|
9
|
+
# include Hashie::Extensions::Dash::PredefinedValues
|
|
10
|
+
#
|
|
11
|
+
# property :gender, values: [:male, :female, :prefer_not_to_say]
|
|
12
|
+
# property :age, values: (0..150) # a Range
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# person = PersonHash.new(gender: :male, age: -1)
|
|
16
|
+
# # => ArgumentError: The value '-1' is not accepted for property 'age'
|
|
17
|
+
module PredefinedValues
|
|
18
|
+
def self.included(base)
|
|
19
|
+
base.instance_variable_set(:@values_for_properties, {})
|
|
20
|
+
base.extend(ClassMethods)
|
|
21
|
+
base.include(InstanceMethods)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module ClassMethods
|
|
25
|
+
attr_reader :values_for_properties
|
|
26
|
+
|
|
27
|
+
def inherited(klass)
|
|
28
|
+
super
|
|
29
|
+
klass.instance_variable_set(:@values_for_properties, values_for_properties.dup)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def property(property_name, options = {})
|
|
33
|
+
super
|
|
34
|
+
|
|
35
|
+
return unless (predefined_values = options[:values])
|
|
36
|
+
|
|
37
|
+
assert_predefined_values!(predefined_values)
|
|
38
|
+
set_predefined_values(property_name, predefined_values)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def assert_predefined_values!(predefined_values)
|
|
44
|
+
return if supported_type?(predefined_values)
|
|
45
|
+
|
|
46
|
+
raise ArgumentError, %(`values` accepts an Array or a Range.)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def supported_type?(predefined_values)
|
|
50
|
+
[::Array, ::Range].any? { |klass| predefined_values.is_a?(klass) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def set_predefined_values(property_name, predefined_values)
|
|
54
|
+
@values_for_properties[property_name] = predefined_values
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
module InstanceMethods
|
|
59
|
+
def initialize(*)
|
|
60
|
+
super
|
|
61
|
+
|
|
62
|
+
assert_property_values!
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def assert_property_values!
|
|
68
|
+
self.class.values_for_properties.each_key do |property|
|
|
69
|
+
value = send(property)
|
|
70
|
+
|
|
71
|
+
if value && !values_for_properties(property).include?(value)
|
|
72
|
+
fail_property_value_error!(property)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def fail_property_value_error!(property)
|
|
78
|
+
raise ArgumentError, "Invalid value for property '#{property}'"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def values_for_properties(property)
|
|
82
|
+
self.class.values_for_properties[property]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -153,7 +153,12 @@ module Hashie
|
|
|
153
153
|
def []=(property, value)
|
|
154
154
|
if self.class.translation_exists? property
|
|
155
155
|
send("#{property}=", value)
|
|
156
|
-
|
|
156
|
+
|
|
157
|
+
if self.class.transformation_exists? property
|
|
158
|
+
super property, self.class.transformed_property(property, value)
|
|
159
|
+
elsif self.class.properties.include?(property)
|
|
160
|
+
super(property, value)
|
|
161
|
+
end
|
|
157
162
|
elsif self.class.transformation_exists? property
|
|
158
163
|
super property, self.class.transformed_property(property, value)
|
|
159
164
|
elsif property_exists? property
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Hashie
|
|
2
2
|
module Extensions
|
|
3
3
|
module DeepLocate
|
|
4
|
-
# The module level implementation of #deep_locate,
|
|
4
|
+
# The module level implementation of #deep_locate, in case you do not want
|
|
5
5
|
# to include/extend the base datastructure. For further examples please
|
|
6
6
|
# see #deep_locate.
|
|
7
7
|
#
|