hashie 5.0.0 → 5.2.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 +77 -2
- data/CONTRIBUTING.md +38 -1
- data/README.md +849 -213
- data/Rakefile +32 -1
- data/UPGRADING.md +18 -0
- data/hashie.gemspec +10 -5
- data/lib/hashie/array.rb +15 -12
- data/lib/hashie/clash.rb +59 -4
- data/lib/hashie/dash.rb +145 -17
- data/lib/hashie/extensions/active_support/core_ext/hash.rb +38 -0
- data/lib/hashie/extensions/array/pretty_inspect.rb +20 -0
- data/lib/hashie/extensions/block_access.rb +71 -0
- data/lib/hashie/extensions/coercion.rb +180 -26
- data/lib/hashie/extensions/dash/coercion.rb +34 -3
- data/lib/hashie/extensions/dash/indifferent_access.rb +83 -5
- data/lib/hashie/extensions/dash/lazy.rb +170 -0
- data/lib/hashie/extensions/dash/predefined_values.rb +69 -9
- data/lib/hashie/extensions/dash/property_translation.rb +134 -16
- data/lib/hashie/extensions/deep_fetch.rb +21 -9
- data/lib/hashie/extensions/deep_find.rb +20 -0
- data/lib/hashie/extensions/deep_grep.rb +69 -0
- data/lib/hashie/extensions/deep_locate.rb +16 -29
- data/lib/hashie/extensions/deep_merge.rb +16 -0
- data/lib/hashie/extensions/deep_merge_initializer.rb +42 -0
- data/lib/hashie/extensions/ignore_undeclared.rb +21 -3
- data/lib/hashie/extensions/indifferent_access.rb +100 -14
- data/lib/hashie/extensions/kernel_coercion.rb +58 -0
- data/lib/hashie/extensions/key_conflict_warning.rb +39 -4
- data/lib/hashie/extensions/key_conversion.rb +9 -0
- data/lib/hashie/extensions/mash/define_accessors.rb +71 -8
- data/lib/hashie/extensions/mash/keep_original_keys.rb +13 -9
- data/lib/hashie/extensions/mash/permissive_respond_to.rb +2 -0
- data/lib/hashie/extensions/mash/required_keys.rb +111 -0
- data/lib/hashie/extensions/mash/safe_assignment.rb +22 -4
- data/lib/hashie/extensions/mash/strict_key_access.rb +65 -0
- data/lib/hashie/extensions/mash/symbolize_keys.rb +6 -2
- data/lib/hashie/extensions/mash/transform_keys.rb +86 -0
- data/lib/hashie/extensions/mash/underscore_keys.rb +32 -0
- data/lib/hashie/extensions/merge_initializer.rb +9 -0
- data/lib/hashie/extensions/method_access.rb +94 -8
- data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +28 -35
- data/lib/hashie/extensions/parsers/yaml_parser.rb +81 -0
- data/lib/hashie/extensions/persistable.rb +137 -0
- data/lib/hashie/extensions/pretty_inspect.rb +14 -0
- data/lib/hashie/extensions/ruby_version.rb +18 -1
- data/lib/hashie/extensions/ruby_version_check.rb +17 -0
- data/lib/hashie/extensions/strict_key_access.rb +59 -5
- data/lib/hashie/extensions/stringify_keys.rb +72 -17
- data/lib/hashie/extensions/symbolize_keys.rb +72 -16
- data/lib/hashie/hash.rb +16 -0
- data/lib/hashie/logger.rb +18 -1
- data/lib/hashie/mash.rb +302 -75
- data/lib/hashie/railtie.rb +27 -1
- data/lib/hashie/rash.rb +64 -4
- data/lib/hashie/trash.rb +8 -0
- data/lib/hashie/utils.rb +17 -6
- data/lib/hashie/version.rb +3 -1
- data/lib/hashie.rb +45 -0
- metadata +18 -6
data/README.md
CHANGED
|
@@ -1,68 +1,88 @@
|
|
|
1
1
|
# Hashie
|
|
2
2
|
|
|
3
3
|
[](https://gitter.im/hashie/hashie?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
4
|
-
[](https://rubygems.org/gems/hashie)
|
|
5
|
+
[](https://github.com/hashie/hashie/actions/workflows/test.yml)
|
|
6
|
+
[](https://coveralls.io/github/hashie/hashie)
|
|
6
7
|
|
|
7
8
|
[](#mascot) Hashie is a growing collection of tools that extend Hashes and make them more useful.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
## Table of Contents
|
|
10
11
|
|
|
11
|
-
- [
|
|
12
|
-
- [
|
|
13
|
-
|
|
14
|
-
- [
|
|
15
|
-
- [Hash Extensions](#hash-extensions)
|
|
16
|
-
- [Logging](#logging)
|
|
17
|
-
- [Coercion](#coercion)
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Stable Release](#stable-release)
|
|
14
|
+
- [Hash Extensions](#hash-extensions)
|
|
15
|
+
- [Coercion](#coercion)
|
|
18
16
|
- [Coercing Collections](#coercing-collections)
|
|
19
17
|
- [Coercing Hashes](#coercing-hashes)
|
|
20
18
|
- [Coercing Core Types](#coercing-core-types)
|
|
21
19
|
- [Coercion Proc](#coercion-proc)
|
|
22
|
-
- [
|
|
23
|
-
- [
|
|
24
|
-
- [
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
- [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
- [Circular Coercion](#circular-coercion)
|
|
21
|
+
- [Nil Values and Defaults](#nil-values-and-defaults)
|
|
22
|
+
- [KernelCoercion](#kernelcoercion)
|
|
23
|
+
- [Callable Coercion](#callable-coercion)
|
|
24
|
+
- [Coercion with dry-types](#coercion-with-dry-types)
|
|
25
|
+
- [Coercion with ActiveModel](#coercion-with-activemodel)
|
|
26
|
+
- [KeyConversion](#keyconversion)
|
|
27
|
+
- [MergeInitializer](#mergeinitializer)
|
|
28
|
+
- [DeepMergeInitializer](#deepmergeinitializer)
|
|
29
|
+
- [MethodAccess](#methodaccess)
|
|
30
|
+
- [BlockAccess](#blockaccess)
|
|
31
|
+
- [Persistable](#persistable)
|
|
32
|
+
- [MethodAccessWithOverride](#methodaccesswithoverride)
|
|
33
|
+
- [MethodOverridingInitializer](#methodoverridinginitializer)
|
|
34
|
+
- [IndifferentAccess](#indifferentaccess)
|
|
35
|
+
- [IgnoreUndeclared](#ignoreundeclared)
|
|
36
|
+
- [DeepMerge](#deepmerge)
|
|
37
|
+
- [DeepFetch](#deepfetch)
|
|
38
|
+
- [DeepFind](#deepfind)
|
|
39
|
+
- [DeepGrep](#deepgrep)
|
|
40
|
+
- [DeepLocate](#deeplocate)
|
|
34
41
|
- [StrictKeyAccess](#strictkeyaccess)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- [
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- [
|
|
48
|
-
- [
|
|
49
|
-
- [
|
|
50
|
-
|
|
51
|
-
- [
|
|
52
|
-
- [
|
|
53
|
-
- [
|
|
42
|
+
- [Mash](#mash)
|
|
43
|
+
- [Method Conflicts](#method-conflicts)
|
|
44
|
+
- [Nested Hashes](#nested-hashes)
|
|
45
|
+
- [Key Types](#key-types)
|
|
46
|
+
- [Loading Files](#loading-files)
|
|
47
|
+
- [Mash and Rails 4 Strong Parameters](#mash-and-rails-4-strong-parameters)
|
|
48
|
+
- [KeepOriginalKeys](#keeporiginalkeys)
|
|
49
|
+
- [PermissiveRespondTo](#permissiverespondto)
|
|
50
|
+
- [SafeAssignment](#safeassignment)
|
|
51
|
+
- [TransformKeys](#transformkeys)
|
|
52
|
+
- [UnderscoreKeys](#underscorekeys)
|
|
53
|
+
- [SymbolizeKeys](#symbolizekeys)
|
|
54
|
+
- [DefineAccessors](#defineaccessors)
|
|
55
|
+
- [StrictKeyAccess](#strictkeyaccess-1)
|
|
56
|
+
- [RequiredKeys](#requiredkeys)
|
|
57
|
+
- [Dash](#dash)
|
|
58
|
+
- [Lazy Property Loading](#lazy-property-loading)
|
|
59
|
+
- [Validations with ActiveModel](#validations-with-activemodel)
|
|
60
|
+
- [Potential Gotchas](#potential-gotchas)
|
|
61
|
+
- [PropertyTranslation](#propertytranslation)
|
|
62
|
+
- [Coercion](#coercion-1)
|
|
63
|
+
- [PredefinedValues](#predefinedvalues)
|
|
64
|
+
- [Trash](#trash)
|
|
65
|
+
- [Clash](#clash)
|
|
66
|
+
- [Rash](#rash)
|
|
67
|
+
- [Auto-Optimized](#auto-optimized)
|
|
68
|
+
- [Logging](#logging)
|
|
69
|
+
- [Mascot](#mascot)
|
|
70
|
+
- [Contributing](#contributing)
|
|
71
|
+
- [Copyright](#copyright)
|
|
54
72
|
|
|
55
73
|
## Installation
|
|
56
74
|
|
|
75
|
+
Hashie requires Ruby 3.0 or newer.
|
|
76
|
+
|
|
57
77
|
Hashie is available as a RubyGem:
|
|
58
78
|
|
|
59
79
|
```bash
|
|
60
|
-
|
|
80
|
+
gem install hashie
|
|
61
81
|
```
|
|
62
82
|
|
|
63
83
|
## Stable Release
|
|
64
84
|
|
|
65
|
-
You're reading the documentation for the stable release of Hashie,
|
|
85
|
+
You're reading the documentation for the stable release of Hashie, 5.2.0.
|
|
66
86
|
|
|
67
87
|
## Hash Extensions
|
|
68
88
|
|
|
@@ -70,20 +90,16 @@ The library is broken up into a number of atomically includable Hash extension m
|
|
|
70
90
|
|
|
71
91
|
Any of the extensions listed below can be mixed into a class by `include`-ing `Hashie::Extensions::ExtensionName`.
|
|
72
92
|
|
|
73
|
-
|
|
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
|
-
```
|
|
93
|
+
Hashie's extensions and classes can add method-style access to keys ([Mash](#mash)), define allowed properties with defaults and required values ([Dash](#dash)), rename or transform incoming properties ([Trash](#trash)), build nested option hashes through chained calls ([Clash](#clash)), or match keys using regular expressions and ranges ([Rash](#rash)). Use these ready-made classes or mix individual extensions into your own Hash subclass to add only the behavior you need.
|
|
81
94
|
|
|
82
95
|
### Coercion
|
|
83
96
|
|
|
84
|
-
|
|
97
|
+
Include `Hashie::Extensions::Coercion` to convert values on assignment. Use `coerce_key` to choose a conversion by key, for example when wrapping API data:
|
|
85
98
|
|
|
86
99
|
```ruby
|
|
100
|
+
class User < Hashie::Mash
|
|
101
|
+
end
|
|
102
|
+
|
|
87
103
|
class Tweet < Hash
|
|
88
104
|
include Hashie::Extensions::Coercion
|
|
89
105
|
include Hashie::Extensions::MergeInitializer
|
|
@@ -91,9 +107,10 @@ class Tweet < Hash
|
|
|
91
107
|
end
|
|
92
108
|
|
|
93
109
|
user_hash = { name: "Bob" }
|
|
94
|
-
Tweet.new(user: user_hash)
|
|
95
|
-
#
|
|
96
|
-
|
|
110
|
+
tweet = Tweet.new(user: user_hash)
|
|
111
|
+
# Automatically calls User.coerce(user_hash), or User.new(user_hash) if absent.
|
|
112
|
+
tweet[:user].class # => User
|
|
113
|
+
tweet[:user].name # => "Bob"
|
|
97
114
|
```
|
|
98
115
|
|
|
99
116
|
Value coercions, on the other hand, will coerce values based on the type of the value being inserted. This is useful if you are trying to build a Hash-like class that is self-propagating.
|
|
@@ -110,31 +127,45 @@ class SpecialHash < Hash
|
|
|
110
127
|
end
|
|
111
128
|
end
|
|
112
129
|
end
|
|
130
|
+
|
|
131
|
+
hash = SpecialHash.new(child: { name: 'Bob' })
|
|
132
|
+
hash[:child].class # => SpecialHash
|
|
133
|
+
hash[:child][:name] # => 'Bob'
|
|
113
134
|
```
|
|
114
135
|
|
|
115
|
-
|
|
136
|
+
#### Coercing Collections
|
|
116
137
|
|
|
117
138
|
```ruby
|
|
139
|
+
require 'set'
|
|
140
|
+
|
|
141
|
+
class User < Hashie::Mash
|
|
142
|
+
end
|
|
143
|
+
|
|
118
144
|
class Tweet < Hash
|
|
119
145
|
include Hashie::Extensions::Coercion
|
|
146
|
+
include Hashie::Extensions::MergeInitializer
|
|
147
|
+
include Hashie::Extensions::MethodAccess
|
|
120
148
|
coerce_key :mentions, Array[User]
|
|
121
149
|
coerce_key :friends, Set[User]
|
|
122
150
|
end
|
|
123
151
|
|
|
124
152
|
user_hash = { name: "Bob" }
|
|
125
|
-
mentions_hash= [user_hash, user_hash]
|
|
153
|
+
mentions_hash = [user_hash, user_hash]
|
|
126
154
|
friends_hash = [user_hash]
|
|
127
155
|
tweet = Tweet.new(mentions: mentions_hash, friends: friends_hash)
|
|
128
|
-
#
|
|
129
|
-
#
|
|
156
|
+
# Automatically calls User.coerce(user_hash), or User.new(user_hash) if absent,
|
|
157
|
+
# on each element of the array.
|
|
130
158
|
|
|
131
159
|
tweet.mentions.map(&:class) # => [User, User]
|
|
132
160
|
tweet.friends.class # => Set
|
|
133
161
|
```
|
|
134
162
|
|
|
135
|
-
|
|
163
|
+
#### Coercing Hashes
|
|
136
164
|
|
|
137
165
|
```ruby
|
|
166
|
+
class User < Hashie::Mash
|
|
167
|
+
end
|
|
168
|
+
|
|
138
169
|
class Relation
|
|
139
170
|
def initialize(string)
|
|
140
171
|
@relation = string
|
|
@@ -143,20 +174,22 @@ end
|
|
|
143
174
|
|
|
144
175
|
class Tweet < Hash
|
|
145
176
|
include Hashie::Extensions::Coercion
|
|
177
|
+
include Hashie::Extensions::MergeInitializer
|
|
178
|
+
include Hashie::Extensions::MethodAccess
|
|
146
179
|
coerce_key :relations, Hash[User => Relation]
|
|
147
180
|
end
|
|
148
181
|
|
|
149
182
|
user_hash = { name: "Bob" }
|
|
150
|
-
relations_hash= { user_hash => "father",
|
|
183
|
+
relations_hash = { user_hash => "father", { name: "Alice" } => "friend" }
|
|
151
184
|
tweet = Tweet.new(relations: relations_hash)
|
|
152
185
|
tweet.relations.map { |k,v| [k.class, v.class] } # => [[User, Relation], [User, Relation]]
|
|
153
186
|
tweet.relations.class # => Hash
|
|
154
187
|
|
|
155
|
-
#
|
|
156
|
-
#
|
|
188
|
+
# Automatically calls User.coerce(user_hash) or User.new(user_hash) on each key
|
|
189
|
+
# and Relation.new on each value since Relation doesn't define `coerce`.
|
|
157
190
|
```
|
|
158
191
|
|
|
159
|
-
|
|
192
|
+
#### Coercing Core Types
|
|
160
193
|
|
|
161
194
|
Hashie handles coercion to the following by using standard conversion methods:
|
|
162
195
|
|
|
@@ -171,19 +204,18 @@ Hashie handles coercion to the following by using standard conversion methods:
|
|
|
171
204
|
|
|
172
205
|
**Note**: The standard Ruby conversion methods are less strict than you may assume. For example, `:foo.to_i` raises an error but `"foo".to_i` returns 0.
|
|
173
206
|
|
|
174
|
-
|
|
175
|
-
- Integer
|
|
176
|
-
- Numeric
|
|
207
|
+
`coerce_value` also supports the supertypes `Integer` and `Numeric`.
|
|
177
208
|
|
|
178
|
-
Hashie does not
|
|
209
|
+
Hashie does not choose a built-in boolean coercion policy. You can coerce to booleans using a custom proc or an external callable coercer.
|
|
179
210
|
|
|
180
|
-
|
|
211
|
+
#### Coercion Proc
|
|
181
212
|
|
|
182
213
|
You can use a custom coercion proc on either `#coerce_key` or `#coerce_value`. This is useful for coercing to booleans or other simple types without creating a new class and `coerce` method. For example:
|
|
183
214
|
|
|
184
215
|
```ruby
|
|
185
216
|
class Tweet < Hash
|
|
186
217
|
include Hashie::Extensions::Coercion
|
|
218
|
+
include Hashie::Extensions::MergeInitializer
|
|
187
219
|
coerce_key :retweeted, ->(v) do
|
|
188
220
|
case v
|
|
189
221
|
when String
|
|
@@ -195,43 +227,199 @@ class Tweet < Hash
|
|
|
195
227
|
end
|
|
196
228
|
end
|
|
197
229
|
end
|
|
230
|
+
|
|
231
|
+
Tweet.new(retweeted: 'yes')[:retweeted] # => true
|
|
232
|
+
Tweet.new(retweeted: 'false')[:retweeted] # => false
|
|
198
233
|
```
|
|
199
234
|
|
|
200
|
-
|
|
235
|
+
##### Circular Coercion
|
|
201
236
|
|
|
202
237
|
Since `coerce_key` is a class-level method, you cannot have circular coercion without the use of a proc. For example:
|
|
203
238
|
|
|
239
|
+
```ruby
|
|
240
|
+
def define_category
|
|
241
|
+
Class.new(Hash) do
|
|
242
|
+
include Hashie::Extensions::Coercion
|
|
243
|
+
include Hashie::Extensions::MergeInitializer
|
|
244
|
+
|
|
245
|
+
coerce_key :products, Array[ProductHash]
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
begin
|
|
250
|
+
define_category
|
|
251
|
+
rescue NameError
|
|
252
|
+
# Expected: NameError, /ProductHash/
|
|
253
|
+
end
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
This fails with a `NameError` for `ProductHash` because `ProductHash` is not defined at the point that `coerce_key` is happening.
|
|
257
|
+
|
|
258
|
+
To work around this, you can use a coercion proc. For example, you could do:
|
|
259
|
+
|
|
204
260
|
```ruby
|
|
205
261
|
class CategoryHash < Hash
|
|
206
262
|
include Hashie::Extensions::Coercion
|
|
207
263
|
include Hashie::Extensions::MergeInitializer
|
|
208
264
|
|
|
209
|
-
coerce_key :products,
|
|
265
|
+
coerce_key :products, ->(value) do
|
|
266
|
+
return value.map { |v| ProductHash.new(v) } if value.respond_to?(:map)
|
|
267
|
+
|
|
268
|
+
ProductHash.new(value)
|
|
269
|
+
end
|
|
210
270
|
end
|
|
211
271
|
|
|
212
272
|
class ProductHash < Hash
|
|
213
273
|
include Hashie::Extensions::Coercion
|
|
214
274
|
include Hashie::Extensions::MergeInitializer
|
|
215
275
|
|
|
216
|
-
coerce_key :categories, Array[
|
|
276
|
+
coerce_key :categories, Array[CategoryHash]
|
|
217
277
|
end
|
|
278
|
+
|
|
279
|
+
category = CategoryHash.new(products: [{ categories: [{}] }])
|
|
280
|
+
category[:products].first.class # => ProductHash
|
|
281
|
+
category[:products].first[:categories].first.class # => CategoryHash
|
|
218
282
|
```
|
|
219
283
|
|
|
220
|
-
|
|
284
|
+
#### Nil Values and Defaults
|
|
221
285
|
|
|
222
|
-
|
|
286
|
+
Top-level `nil` values bypass coercion: `coerce_key :description, String` does not turn `nil` into `''`. A coercion rule converts supplied non-nil values; it does not define a default. Use a Dash property default when missing or nil input should become an empty string:
|
|
223
287
|
|
|
224
288
|
```ruby
|
|
225
|
-
class
|
|
226
|
-
|
|
227
|
-
coerce_key :products, ->(value) do
|
|
228
|
-
return value.map { |v| ProductHash.new(v) } if value.respond_to?(:map)
|
|
289
|
+
class DescriptionParams < Hashie::Dash
|
|
290
|
+
include Hashie::Extensions::Coercion
|
|
229
291
|
|
|
230
|
-
|
|
231
|
-
|
|
292
|
+
property :description, default: ''
|
|
293
|
+
coerce_key :description, String
|
|
232
294
|
end
|
|
295
|
+
|
|
296
|
+
params = DescriptionParams.new(description: nil)
|
|
297
|
+
params.description # => ''
|
|
298
|
+
DescriptionParams.new.description # => ''
|
|
299
|
+
DescriptionParams.new(description: 123).description # => '123'
|
|
300
|
+
|
|
301
|
+
params.description = nil
|
|
302
|
+
params.description # => nil
|
|
303
|
+
params.update_attributes!(description: nil)
|
|
304
|
+
params.description # => ''
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Dash initialization and `update_attributes!` apply property defaults to nil values. Direct assignments through a property writer or `[]=` retain nil and do not reapply defaults. These rules also apply to subclasses. Dash already initializes attributes; it does not need `MergeInitializer`.
|
|
308
|
+
|
|
309
|
+
#### KernelCoercion
|
|
310
|
+
|
|
311
|
+
By default, `Hashie::Extensions::Coercion` coerces core types using lenient methods like `#to_i` and `#to_f`, which can silently produce undesirable results:
|
|
312
|
+
|
|
313
|
+
```ruby
|
|
314
|
+
'abcd'.to_f # => 0.0
|
|
233
315
|
```
|
|
234
316
|
|
|
317
|
+
The `Hashie::Extensions::KernelCoercion` mixin extends `Coercion` to use the stricter `Kernel` methods (e.g. `Kernel#Integer`, `Kernel#Float`) instead, which raise an error for invalid input rather than silently coercing it:
|
|
318
|
+
|
|
319
|
+
```ruby
|
|
320
|
+
class StrictHash < Hash
|
|
321
|
+
include Hashie::Extensions::Coercion
|
|
322
|
+
extend Hashie::Extensions::KernelCoercion
|
|
323
|
+
|
|
324
|
+
coerce_key :count, Integer
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
hash = StrictHash.new
|
|
328
|
+
hash[:count] = '5'
|
|
329
|
+
hash[:count] # => 5
|
|
330
|
+
begin
|
|
331
|
+
hash[:count] = 'abcd'
|
|
332
|
+
rescue Hashie::CoercionError
|
|
333
|
+
# Expected: Hashie::CoercionError, /cannot coerce property `:count`/
|
|
334
|
+
end
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
This applies to `Integer`, `Float`, `Complex`, `Rational` and `String`. There is no strict `Kernel` method equivalent for `Symbol`, so `Symbol` coercion always uses the default, lenient `#to_sym` behavior.
|
|
338
|
+
|
|
339
|
+
#### Callable Coercion
|
|
340
|
+
|
|
341
|
+
Coercion also accepts bound methods and other objects responding to `call`. Existing collection and class/module dispatch takes precedence over `call` for non-Proc targets; other callable objects use `call` even if they also expose `coerce` or `new`. Callable coercers work with `coerce_key`, `coerce_value`, collection members, and Dash's `coerce:` property option.
|
|
342
|
+
|
|
343
|
+
Hashie does not load or depend on external type libraries. The callable determines conversion and validation; no additional output validation or empty-string normalization is applied. Hashie preserves its existing `nil` bypass for top-level assignments, even when the callable would reject `nil`; nested collection members are passed to their coercers. As with existing coercers, `TypeError`, `ArgumentError`, and `NoMethodError` during assignment become `Hashie::CoercionError`; other exceptions propagate unchanged.
|
|
344
|
+
|
|
345
|
+
#### Coercion with dry-types
|
|
346
|
+
|
|
347
|
+
Add `dry-types` to your application's Gemfile:
|
|
348
|
+
|
|
349
|
+
```gemfile
|
|
350
|
+
gem 'dry-types'
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Pass a [dry-types](https://dry-rb.org/gems/dry-types/main/) object directly as the coercer:
|
|
354
|
+
|
|
355
|
+
```ruby
|
|
356
|
+
require 'dry-types'
|
|
357
|
+
require 'hashie'
|
|
358
|
+
|
|
359
|
+
class Settings < Hashie::Dash
|
|
360
|
+
include Hashie::Extensions::Dash::Coercion
|
|
361
|
+
|
|
362
|
+
property :enabled, coerce: Dry::Types['params.bool']
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
Settings.new(enabled: 'false').enabled # => false
|
|
366
|
+
Settings.new(enabled: nil).enabled # => nil
|
|
367
|
+
begin
|
|
368
|
+
Settings.new(enabled: 'unknown')
|
|
369
|
+
rescue Dry::Types::CoercionError
|
|
370
|
+
# Expected: Dry::Types::CoercionError
|
|
371
|
+
end
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
The `params.bool` type converts recognized boolean representations and rejects unknown values and empty strings. Use `Dry::Types['strict.bool']` for validation without conversion: it accepts `true` and `false`, but rejects strings such as `'false'`. Top-level `nil` assignments still bypass coercion.
|
|
375
|
+
|
|
376
|
+
The bypass applies only to top-level assignments, not collection members:
|
|
377
|
+
|
|
378
|
+
```ruby
|
|
379
|
+
require 'dry-types'
|
|
380
|
+
|
|
381
|
+
class Flags < Hashie::Dash
|
|
382
|
+
include Hashie::Extensions::Dash::Coercion
|
|
383
|
+
|
|
384
|
+
property :values, coerce: Array[Dry::Types['strict.bool']]
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
Flags.new(values: nil).values # => nil
|
|
388
|
+
begin
|
|
389
|
+
Flags.new(values: [true, nil])
|
|
390
|
+
rescue Dry::Types::ConstraintError
|
|
391
|
+
# Expected: Dry::Types::ConstraintError
|
|
392
|
+
end
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
#### Coercion with ActiveModel
|
|
396
|
+
|
|
397
|
+
Add `activemodel` to your application's Gemfile if it is not already provided by Rails:
|
|
398
|
+
|
|
399
|
+
```gemfile
|
|
400
|
+
gem 'activemodel'
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Pass a bound [ActiveModel type](https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html) `cast` method as the coercer:
|
|
404
|
+
|
|
405
|
+
```ruby
|
|
406
|
+
require 'active_model'
|
|
407
|
+
require 'hashie'
|
|
408
|
+
|
|
409
|
+
class Settings < Hashie::Dash
|
|
410
|
+
include Hashie::Extensions::Dash::Coercion
|
|
411
|
+
|
|
412
|
+
property :enabled, coerce: ActiveModel::Type::Boolean.new.method(:cast)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
Settings.new(enabled: 'off').enabled # => false
|
|
416
|
+
Settings.new(enabled: nil).enabled # => nil
|
|
417
|
+
Settings.new(enabled: '').enabled # => nil
|
|
418
|
+
Settings.new(enabled: 'unknown').enabled # => true
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
ActiveModel's boolean policy differs from dry-types: it treats an empty string as `nil`, recognized false values as false, and most other values as true. Choose the library whose conversion policy matches your application.
|
|
422
|
+
|
|
235
423
|
### KeyConversion
|
|
236
424
|
|
|
237
425
|
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`.
|
|
@@ -239,16 +427,51 @@ The KeyConversion extension gives you the convenience methods of `symbolize_keys
|
|
|
239
427
|
Hashie also has a utility method for converting keys on a Hash without a mixin:
|
|
240
428
|
|
|
241
429
|
```ruby
|
|
242
|
-
|
|
243
|
-
Hashie.symbolize_keys
|
|
244
|
-
|
|
245
|
-
Hashie.
|
|
430
|
+
hash = { 'name' => 'Ruby' }
|
|
431
|
+
Hashie.symbolize_keys(hash) # => { name: 'Ruby' }
|
|
432
|
+
hash # => { 'name' => 'Ruby' }
|
|
433
|
+
Hashie.symbolize_keys!(hash) # => { name: 'Ruby' }
|
|
434
|
+
Hashie.stringify_keys(hash) # => { 'name' => 'Ruby' }
|
|
435
|
+
hash # => { name: 'Ruby' }
|
|
436
|
+
Hashie.stringify_keys!(hash) # => { 'name' => 'Ruby' }
|
|
246
437
|
```
|
|
247
438
|
|
|
248
439
|
### MergeInitializer
|
|
249
440
|
|
|
250
441
|
The MergeInitializer extension simply makes it possible to initialize a Hash subclass with another Hash, giving you a quick short-hand.
|
|
251
442
|
|
|
443
|
+
### DeepMergeInitializer
|
|
444
|
+
|
|
445
|
+
The `DeepMergeInitializer` extension initializes a `Hash` subclass from another hash, like `MergeInitializer`, but also converts nested hashes to the same concrete class. This includes hashes inside arrays, even when arrays are nested.
|
|
446
|
+
|
|
447
|
+
<!-- hashie:example deep-merge-initializer -->
|
|
448
|
+
```ruby
|
|
449
|
+
class MyHash < Hash
|
|
450
|
+
include Hashie::Extensions::DeepMergeInitializer
|
|
451
|
+
include Hashie::Extensions::IndifferentAccess
|
|
452
|
+
include Hashie::Extensions::MethodAccess
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
hash = MyHash.new(foo: { bar: 1 }, children: [{ name: 'Bob' }])
|
|
456
|
+
hash.foo.bar # => 1
|
|
457
|
+
hash['foo']['bar'] # => 1
|
|
458
|
+
hash.children.first.name # => 'Bob'
|
|
459
|
+
hash.foo.class # => MyHash
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Use this extension instead of `MergeInitializer` on plain `Hash` subclasses; Mash already provides recursive conversion, and Dash/Trash have their own property-aware initializers. The subclass must accept the same initializer arguments. Conversion happens only during initialization, not on later assignment or merge, and does not change `MethodAccess` itself.
|
|
463
|
+
|
|
464
|
+
An optional second argument supplies a default value, including `false`; alternatively, a block supplies a default proc. The same default is passed to every converted hash. As with `Hash`, a non-nil default value cannot be combined with a block.
|
|
465
|
+
|
|
466
|
+
<!-- hashie:example deep-merge-initializer -->
|
|
467
|
+
```ruby
|
|
468
|
+
hash = MyHash.new({ foo: {} }, false)
|
|
469
|
+
hash[:missing] # => false
|
|
470
|
+
hash.foo[:missing] # => false
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Input hashes and arrays are rebuilt without modifying the source; keys and non-container values are retained. Use acyclic input data.
|
|
474
|
+
|
|
252
475
|
### MethodAccess
|
|
253
476
|
|
|
254
477
|
The MethodAccess extension allows you to quickly build method-based reading, writing, and querying into your Hash descendant. It can also be included as individual modules, i.e. `Hashie::Extensions::MethodReader`, `Hashie::Extensions::MethodWriter` and `Hashie::Extensions::MethodQuery`.
|
|
@@ -264,6 +487,56 @@ h.abc # => 'def'
|
|
|
264
487
|
h.abc? # => true
|
|
265
488
|
```
|
|
266
489
|
|
|
490
|
+
### BlockAccess
|
|
491
|
+
|
|
492
|
+
`Hashie::Extensions::BlockAccess` builds nested hashes using blocks. It works with Hash subclasses, including Mash and Dash, without changing calls made without a block. For a plain Hash subclass, include `MethodAccess` first if you also want method-style readers and writers.
|
|
493
|
+
|
|
494
|
+
```ruby
|
|
495
|
+
class Configuration < Hash
|
|
496
|
+
include Hashie::Extensions::MethodAccess
|
|
497
|
+
include Hashie::Extensions::BlockAccess
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
config = Configuration.new
|
|
501
|
+
config.author do |author|
|
|
502
|
+
author.name = 'Michael Bleigh'
|
|
503
|
+
end
|
|
504
|
+
config.author.name #=> 'Michael Bleigh'
|
|
505
|
+
|
|
506
|
+
config.test(:one) { |one| one.foo = 'bar' }
|
|
507
|
+
config.test(:two) { |two| two.hurp = 'derp' }
|
|
508
|
+
config.test.one.foo #=> 'bar'
|
|
509
|
+
config.test.two.hurp #=> 'derp'
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Include only `BlockAccess` in a Mash subclass; Mash already supplies method access. Bracket calls also accept a block: `config['author'] { |author| author.name = 'Bob' }`. Each call returns the child, not the block's result. Existing hashes are reused; missing children are constructed using the containing hash's class, which must accept no arguments. Existing non-hash values, including `nil` and `false`, raise `TypeError` rather than being overwritten.
|
|
513
|
+
|
|
514
|
+
Dash properties must be declared, and its constructor and property validations still apply. Real methods (such as `each`) and Mash's `?`, `!`, `=`, and `_` suffixes keep their existing behavior.
|
|
515
|
+
|
|
516
|
+
### Persistable
|
|
517
|
+
|
|
518
|
+
`Hashie::Extensions::Persistable` adds opt-in YAML file persistence to classes whose constructors accept a hash. Plain Hash subclasses can include `MergeInitializer` first.
|
|
519
|
+
|
|
520
|
+
```ruby
|
|
521
|
+
class Configuration < Hashie::Mash
|
|
522
|
+
include Hashie::Extensions::Persistable
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
config = Configuration.new(name: 'example')
|
|
526
|
+
config.save('config.yaml') # => File.expand_path('config.yaml')
|
|
527
|
+
loaded = Configuration.load('config.yaml')
|
|
528
|
+
loaded.name # => 'example'
|
|
529
|
+
loaded.name = 'updated'
|
|
530
|
+
loaded.save # writes back to config.yaml
|
|
531
|
+
Configuration.load('config.yaml').name # => 'updated'
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
`save` returns and remembers the absolute path only after a successful write. It atomically replaces the destination using a temporary file in the same directory, preserves existing file permissions, and creates new files with private permissions. A failed write leaves the remembered path unchanged.
|
|
535
|
+
|
|
536
|
+
Files contain plain YAML data, not Ruby class tags or persistence metadata. Loading uses `Psych.safe_load`, permits symbols for declared Dash/Trash properties, rejects aliases and non-hash roots, and never evaluates ERB. String and symbol keys retain their types; Dash/Trash files must use keys accepted by their constructors. Nested Hashie classes are not encoded: constructors such as Mash may reconstruct their own nested types, while Dash retains nested plain hashes. Supported values are hashes, arrays, strings, symbols, numbers, booleans, and `nil`; cycles and unsupported objects are rejected.
|
|
537
|
+
|
|
538
|
+
Including this extension explicitly replaces an inherited class-level `load` on that subclass. Ordinary `Hashie::Mash.load` retains its existing caching and YAML/ERB behavior. No autosave or other serialization formats are provided.
|
|
539
|
+
|
|
267
540
|
### MethodAccessWithOverride
|
|
268
541
|
|
|
269
542
|
The MethodAccessWithOverride extension is like the MethodAccess extension, except that it allows you to override Hash methods. It aliases any overridden method with two leading underscores. To include only this overriding functionality, you can include the single module `Hashie::Extensions::MethodOverridingWriter`.
|
|
@@ -313,6 +586,7 @@ This extension can be mixed in to your Hash subclass to allow you to use Strings
|
|
|
313
586
|
|
|
314
587
|
In addition, IndifferentAccess will also inject itself into sub-hashes so they behave the same.
|
|
315
588
|
|
|
589
|
+
<!-- hashie:example indifferent-access -->
|
|
316
590
|
```ruby
|
|
317
591
|
class MyHash < Hash
|
|
318
592
|
include Hashie::Extensions::MergeInitializer
|
|
@@ -334,14 +608,15 @@ myhash['fishes']['food'] # => "flakes"
|
|
|
334
608
|
|
|
335
609
|
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
610
|
|
|
611
|
+
<!-- hashie:example indifferent-access -->
|
|
337
612
|
```ruby
|
|
338
613
|
myhash = MyHash.new
|
|
339
614
|
myhash["foo"] = "bar"
|
|
340
615
|
myhash[:foo] #=> "bar"
|
|
341
616
|
|
|
342
617
|
normal_hash = myhash.to_hash
|
|
343
|
-
|
|
344
|
-
|
|
618
|
+
normal_hash["foo"] #=> "bar"
|
|
619
|
+
normal_hash[:foo] #=> nil
|
|
345
620
|
```
|
|
346
621
|
|
|
347
622
|
### IgnoreUndeclared
|
|
@@ -349,7 +624,7 @@ myhash[:foo] #=> nil
|
|
|
349
624
|
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.
|
|
350
625
|
|
|
351
626
|
```ruby
|
|
352
|
-
class Person < Trash
|
|
627
|
+
class Person < Hashie::Trash
|
|
353
628
|
include Hashie::Extensions::IgnoreUndeclared
|
|
354
629
|
property :first_name
|
|
355
630
|
property :last_name
|
|
@@ -365,13 +640,16 @@ p = Person.new(user_data) # 'email' is silently ignored
|
|
|
365
640
|
|
|
366
641
|
p.first_name # => 'Freddy'
|
|
367
642
|
p.last_name # => 'Nostrils'
|
|
368
|
-
|
|
643
|
+
begin
|
|
644
|
+
p.email
|
|
645
|
+
rescue NoMethodError
|
|
646
|
+
# Expected: NoMethodError
|
|
647
|
+
end
|
|
369
648
|
```
|
|
370
649
|
|
|
371
650
|
### DeepMerge
|
|
372
651
|
|
|
373
|
-
|
|
374
|
-
system into any Hash descendant:
|
|
652
|
+
Include `DeepMerge` to merge nested hashes recursively:
|
|
375
653
|
|
|
376
654
|
```ruby
|
|
377
655
|
class MyHash < Hash
|
|
@@ -395,11 +673,9 @@ end
|
|
|
395
673
|
h1 = MyHash[{ a: 100, b: 200, c: { c1: 100 } }]
|
|
396
674
|
h2 = MyHash[{ b: 250, c: { c1: 200 } }]
|
|
397
675
|
|
|
398
|
-
h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
|
|
399
|
-
# => { a: 100, b: 450, c: { c1: 300 } }
|
|
676
|
+
h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val } # => { a: 100, b: 450, c: { c1: 300 } }
|
|
400
677
|
```
|
|
401
678
|
|
|
402
|
-
|
|
403
679
|
### DeepFetch
|
|
404
680
|
|
|
405
681
|
This extension can be mixed in to provide for safe and concise retrieval of deeply nested hash values. In the event that the requested key does not exist a block can be provided and its value will be returned.
|
|
@@ -418,7 +694,11 @@ user = {
|
|
|
418
694
|
user.extend Hashie::Extensions::DeepFetch
|
|
419
695
|
|
|
420
696
|
user.deep_fetch :name, :first # => 'Bob'
|
|
421
|
-
|
|
697
|
+
begin
|
|
698
|
+
user.deep_fetch :name, :middle
|
|
699
|
+
rescue Hashie::Extensions::DeepFetch::UndefinedPathError
|
|
700
|
+
# Expected: Hashie::Extensions::DeepFetch::UndefinedPathError, /could not fetch path .* at `middle`/
|
|
701
|
+
end
|
|
422
702
|
|
|
423
703
|
# using a default block
|
|
424
704
|
user.deep_fetch(:name, :middle) { |key| 'default' } # => 'default'
|
|
@@ -453,11 +733,39 @@ user.deep_find_all(:name) #=> [{ first: 'Bob', last: 'Boberts' }, 'Rubyists', 'O
|
|
|
453
733
|
user.deep_select(:name) #=> [{ first: 'Bob', last: 'Boberts' }, 'Rubyists', 'Open source enthusiasts']
|
|
454
734
|
```
|
|
455
735
|
|
|
456
|
-
###
|
|
736
|
+
### DeepGrep
|
|
737
|
+
|
|
738
|
+
Search nested hashes and arrays for string or symbol keys and values matching a regular expression. Include `Hashie::Extensions::DeepGrep` to call `deep_grep(pattern)`.
|
|
739
|
+
|
|
740
|
+
```ruby
|
|
741
|
+
class Library < Hash
|
|
742
|
+
include Hashie::Extensions::DeepGrep
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
books = Library[
|
|
746
|
+
'Uncategorized' => [
|
|
747
|
+
{ title: 'Ruby for beginners', pages: 120 },
|
|
748
|
+
{ title: 'CSS for intermediates', pages: 80 }
|
|
749
|
+
],
|
|
750
|
+
'Collection of Ruby books' => [{ title: 'That other language by Matz', pages: 578 }]
|
|
751
|
+
]
|
|
752
|
+
|
|
753
|
+
matches = [
|
|
754
|
+
{ title: 'Ruby for beginners', pages: 120 },
|
|
755
|
+
{ 'Collection of Ruby books' => [{ title: 'That other language by Matz', pages: 578 }] }
|
|
756
|
+
]
|
|
757
|
+
books.deep_grep(/ruby/i) # => matches
|
|
457
758
|
|
|
458
|
-
|
|
759
|
+
books.deep_grep(/missing/) # => []
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
For plain hashes or arrays, call `Hashie::Extensions::DeepGrep.deep_grep(pattern, object)` directly.
|
|
763
|
+
|
|
764
|
+
A matching hash key returns a new one-entry hash containing that key and its original subtree; that subtree is not searched further. A matching scalar hash value returns its containing hash once, preserving sibling context. A matching scalar array element returns just that element, not the whole array. Non-text scalars and container keys are not searched. Results follow depth-first insertion order; shared containers can appear at multiple input locations, and cycles are skipped. Input data is not modified, but returned containers share references with the input.
|
|
765
|
+
|
|
766
|
+
### DeepLocate
|
|
459
767
|
|
|
460
|
-
|
|
768
|
+
`DeepLocate` searches nested Enumerables depth-first and returns each container with at least one element matching the comparator. The comparator receives the key, value, and containing object.
|
|
461
769
|
|
|
462
770
|
Because the container objects are returned, the result elements can be modified in place. This way, one can perform modifications on deeply nested hashes without the need to know the exact paths.
|
|
463
771
|
|
|
@@ -485,17 +793,14 @@ books = [
|
|
|
485
793
|
|
|
486
794
|
books.extend(Hashie::Extensions::DeepLocate)
|
|
487
795
|
|
|
488
|
-
|
|
489
|
-
# http://ruby-journal.com/becareful-with-space-in-lambda-hash-rocket-syntax-between-ruby-1-dot-9-and-2-dot-0/
|
|
796
|
+
books.deep_locate ->(key, value, _object) { key == :title && value.include?("Ruby") } # => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"Ruby for the rest of us", :pages=>576}]
|
|
490
797
|
|
|
491
|
-
books.deep_locate ->
|
|
492
|
-
# => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"Ruby for the rest of us", :pages=>576}]
|
|
493
|
-
|
|
494
|
-
books.deep_locate -> (key, value, object) { key == :pages && value <= 120 }
|
|
495
|
-
# => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"CSS for intermediates", :pages=>80}]
|
|
798
|
+
books.deep_locate ->(key, value, _object) { key == :pages && value <= 120 } # => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"CSS for intermediates", :pages=>80}]
|
|
496
799
|
```
|
|
497
800
|
|
|
498
|
-
|
|
801
|
+
Traversal enumerates nested objects, including files and other stateful Enumerables, and can consume their contents. Use this extension only when enumerating every nested Enumerable is safe. A matching comparator does not stop traversal into that container.
|
|
802
|
+
|
|
803
|
+
### StrictKeyAccess
|
|
499
804
|
|
|
500
805
|
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
806
|
|
|
@@ -504,17 +809,18 @@ class StrictKeyAccessHash < Hash
|
|
|
504
809
|
include Hashie::Extensions::StrictKeyAccess
|
|
505
810
|
end
|
|
506
811
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
812
|
+
hash = StrictKeyAccessHash[foo: "bar"] # => {:foo=>"bar"}
|
|
813
|
+
hash[:foo] # => "bar"
|
|
814
|
+
begin
|
|
815
|
+
hash[:cow]
|
|
816
|
+
rescue KeyError
|
|
817
|
+
# Expected: KeyError, "key not found: :cow"
|
|
818
|
+
end
|
|
513
819
|
```
|
|
514
820
|
|
|
515
821
|
## Mash
|
|
516
822
|
|
|
517
|
-
Mash
|
|
823
|
+
Mash adds method-style access to Hash keys and recursively wraps nested hashes:
|
|
518
824
|
|
|
519
825
|
```ruby
|
|
520
826
|
mash = Hashie::Mash.new
|
|
@@ -523,37 +829,34 @@ mash.name # => nil
|
|
|
523
829
|
mash.name = "My Mash"
|
|
524
830
|
mash.name # => "My Mash"
|
|
525
831
|
mash.name? # => true
|
|
526
|
-
mash.inspect # =>
|
|
832
|
+
mash.inspect # => '#<Hashie::Mash name="My Mash">'
|
|
527
833
|
|
|
528
834
|
mash = Hashie::Mash.new
|
|
529
835
|
# use bang methods for multi-level assignment
|
|
530
836
|
mash.author!.name = "Michael Bleigh"
|
|
531
|
-
mash.author # =>
|
|
837
|
+
mash.author.to_h # => { 'name' => 'Michael Bleigh' }
|
|
532
838
|
|
|
533
839
|
mash = Hashie::Mash.new
|
|
534
840
|
# use under-bang methods for multi-level testing
|
|
535
841
|
mash.author_.name? # => false
|
|
536
|
-
mash.inspect # =>
|
|
842
|
+
mash.inspect # => '#<Hashie::Mash>'
|
|
537
843
|
```
|
|
538
844
|
|
|
539
845
|
**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.
|
|
540
846
|
|
|
541
|
-
|
|
847
|
+
### Method Conflicts
|
|
542
848
|
|
|
543
|
-
|
|
849
|
+
Mash does not override existing methods. Use brackets to read a key that conflicts with a method name:
|
|
544
850
|
|
|
545
851
|
```ruby
|
|
546
852
|
mash = Hashie::Mash.new
|
|
547
853
|
mash.name = "My Mash"
|
|
548
854
|
mash.zip = "Method Override?"
|
|
549
855
|
mash.zip # => [[["name", "My Mash"]], [["zip", "Method Override?"]]]
|
|
856
|
+
mash[:zip] # => 'Method Override?'
|
|
550
857
|
```
|
|
551
858
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
```ruby
|
|
555
|
-
Hashie.logger = Rails.logger
|
|
556
|
-
```
|
|
859
|
+
Hashie logs a warning when a key conflicts with an existing method. See [Logging](#logging) to configure the logger.
|
|
557
860
|
|
|
558
861
|
You can also disable the logging in subclasses of Mash:
|
|
559
862
|
|
|
@@ -561,14 +864,19 @@ You can also disable the logging in subclasses of Mash:
|
|
|
561
864
|
class Response < Hashie::Mash
|
|
562
865
|
disable_warnings
|
|
563
866
|
end
|
|
867
|
+
|
|
868
|
+
Response.new(zip: '90210').zip # => [[['zip', '90210']]]
|
|
869
|
+
Response.disable_warnings? # => true
|
|
564
870
|
```
|
|
565
871
|
|
|
566
|
-
|
|
872
|
+
To suppress warnings only for specific methods, pass their names to `disable_warnings`:
|
|
567
873
|
|
|
568
874
|
```ruby
|
|
569
875
|
class Response < Hashie::Mash
|
|
570
876
|
disable_warnings :zip, :zap
|
|
571
877
|
end
|
|
878
|
+
|
|
879
|
+
Response.disabled_warnings # => [:zip, :zap]
|
|
572
880
|
```
|
|
573
881
|
|
|
574
882
|
This behavior is cumulative. The examples above and below behave identically.
|
|
@@ -578,21 +886,29 @@ class Response < Hashie::Mash
|
|
|
578
886
|
disable_warnings :zip
|
|
579
887
|
disable_warnings :zap
|
|
580
888
|
end
|
|
889
|
+
|
|
890
|
+
Response.disabled_warnings # => [:zip, :zap]
|
|
581
891
|
```
|
|
582
892
|
|
|
583
|
-
|
|
893
|
+
Calling `disable_warnings` without arguments suppresses all warnings. A subsequent call with method names replaces that setting with a list of specific exceptions, including in subclasses:
|
|
584
894
|
|
|
585
895
|
```ruby
|
|
896
|
+
require 'stringio'
|
|
897
|
+
|
|
586
898
|
class Message < Hashie::Mash
|
|
587
899
|
disable_warnings :zip, :zap
|
|
588
900
|
disable_warnings
|
|
589
901
|
end
|
|
590
902
|
|
|
591
|
-
|
|
903
|
+
log = StringIO.new
|
|
904
|
+
Hashie.logger = Logger.new(log)
|
|
592
905
|
Message.new(merge: 'true', compact: true)
|
|
906
|
+
log.string # => ''
|
|
593
907
|
```
|
|
594
908
|
|
|
595
909
|
```ruby
|
|
910
|
+
require 'stringio'
|
|
911
|
+
|
|
596
912
|
class Message < Hashie::Mash
|
|
597
913
|
disable_warnings
|
|
598
914
|
end
|
|
@@ -601,20 +917,31 @@ class Response < Message
|
|
|
601
917
|
disable_warnings :zip, :zap
|
|
602
918
|
end
|
|
603
919
|
|
|
604
|
-
|
|
920
|
+
log = StringIO.new
|
|
921
|
+
Hashie.logger = Logger.new(log)
|
|
605
922
|
Response.new(merge: 'true', compact: true, zip: '90210', zap: 'electric')
|
|
923
|
+
log.string.lines.size # => 2
|
|
924
|
+
log.string.include?('#merge ') # => true
|
|
925
|
+
log.string.include?('#compact ') # => true
|
|
606
926
|
```
|
|
607
927
|
|
|
608
928
|
If you would like to create an anonymous subclass of a Hashie::Mash with key conflict warnings disabled:
|
|
609
929
|
|
|
610
930
|
```ruby
|
|
611
|
-
|
|
612
|
-
|
|
931
|
+
require 'stringio'
|
|
932
|
+
|
|
933
|
+
log = StringIO.new
|
|
934
|
+
Hashie.logger = Logger.new(log)
|
|
935
|
+
Hashie::Mash.quiet.new(zip: '90210', compact: true)
|
|
936
|
+
log.string # => ''
|
|
937
|
+
Hashie::Mash.quiet(:zip).new(zip: '90210', compact: true)
|
|
938
|
+
log.string.lines.size # => 1
|
|
939
|
+
log.string.include?('#compact ') # => true
|
|
613
940
|
```
|
|
614
941
|
|
|
615
|
-
|
|
942
|
+
### Nested Hashes
|
|
616
943
|
|
|
617
|
-
Mash
|
|
944
|
+
Mash wraps nested plain hashes in new Mashes without modifying the input. A Mash subclass uses its own class for those nested hashes, preserving the extensions you included:
|
|
618
945
|
|
|
619
946
|
```ruby
|
|
620
947
|
mash = Hashie::Mash.new(name: "Hashie", dependencies: { rake: "< 11", rspec: "~> 3.0" })
|
|
@@ -625,75 +952,127 @@ my_gem = MyGem.new(name: "Hashie", dependencies: { rake: "< 11", rspec: "~> 3.0"
|
|
|
625
952
|
my_gem.dependencies.class #=> MyGem
|
|
626
953
|
```
|
|
627
954
|
|
|
628
|
-
|
|
955
|
+
### Key Types
|
|
629
956
|
|
|
630
|
-
Mash
|
|
957
|
+
Mash uses string keys for indifferent string/symbol access. Other key types, such as integers, are preserved and accessed with brackets:
|
|
631
958
|
|
|
632
959
|
```ruby
|
|
633
|
-
Hashie::Mash.new('1' => 'one string', :'1' => 'one sym', 1 => 'one num')
|
|
634
|
-
# => {"1"=>"one sym", 1=>"one num"}
|
|
960
|
+
Hashie::Mash.new('1' => 'one string', :'1' => 'one sym', 1 => 'one num') # => {"1"=>"one sym", 1=>"one num"}
|
|
635
961
|
```
|
|
636
962
|
|
|
637
|
-
The symbol
|
|
963
|
+
The symbol `:'1'` and string `'1'` share a key, so the later value wins. The integer `1` remains a separate key.
|
|
638
964
|
|
|
639
|
-
|
|
965
|
+
### Loading Files
|
|
640
966
|
|
|
641
|
-
Mash
|
|
967
|
+
Use `Mash.load` to read a YAML configuration file:
|
|
642
968
|
|
|
643
969
|
```yml
|
|
644
|
-
|
|
970
|
+
# settings/twitter.yml
|
|
645
971
|
development:
|
|
646
972
|
api_key: 'api_key'
|
|
647
973
|
production:
|
|
648
|
-
api_key: <%= ENV
|
|
974
|
+
api_key: <%= ENV.fetch('HASHIE_README_API_KEY') %> # set to 'abcd' below
|
|
649
975
|
```
|
|
650
976
|
|
|
977
|
+
<!-- hashie:example mash-load -->
|
|
651
978
|
```ruby
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
979
|
+
require 'fileutils'
|
|
980
|
+
require 'pathname'
|
|
981
|
+
|
|
982
|
+
FileUtils.mkdir_p('settings')
|
|
983
|
+
File.write('settings/twitter.yml', <<~YAML)
|
|
984
|
+
development:
|
|
985
|
+
api_key: 'api_key'
|
|
986
|
+
production:
|
|
987
|
+
api_key: <%= ENV.fetch('HASHIE_README_API_KEY') %>
|
|
988
|
+
YAML
|
|
989
|
+
ENV['HASHIE_README_API_KEY'] = 'abcd'
|
|
990
|
+
|
|
991
|
+
mash = Hashie::Mash.load('settings/twitter.yml')
|
|
992
|
+
mash.development.api_key # => 'api_key'
|
|
993
|
+
begin
|
|
994
|
+
mash.production = {}
|
|
995
|
+
rescue FrozenError
|
|
996
|
+
# Expected: FrozenError, /can't modify frozen/
|
|
997
|
+
end
|
|
655
998
|
mash.development.api_key? # => true
|
|
999
|
+
mash.development.api_key = "foo"
|
|
1000
|
+
mash.development.api_key # => 'foo'
|
|
656
1001
|
```
|
|
657
1002
|
|
|
658
1003
|
You can also load with a `Pathname` object:
|
|
659
1004
|
|
|
1005
|
+
<!-- hashie:example mash-load -->
|
|
660
1006
|
```ruby
|
|
661
|
-
mash = Mash.load(Pathname 'settings/twitter.yml')
|
|
662
|
-
mash.development.api_key # => '
|
|
1007
|
+
mash = Hashie::Mash.load(Pathname 'settings/twitter.yml')
|
|
1008
|
+
mash.development.api_key # => 'api_key'
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
The loaded root is frozen, but nested Mashes remain mutable. Repeated loads with the same path object return the cached Mash.
|
|
1012
|
+
|
|
1013
|
+
By default, `Mash.load` evaluates ERB before parsing YAML. Only use it with trusted files; `YAML.safe_load` does not make ERB evaluation safe. To parse untrusted or plain YAML without evaluating ERB, explicitly select `YamlParser`:
|
|
1014
|
+
|
|
1015
|
+
```ruby
|
|
1016
|
+
File.write('data.yml', "api_key: <%= not_evaluated %>\n")
|
|
1017
|
+
mash = Hashie::Mash.load('data.yml', parser: Hashie::Extensions::Parsers::YamlParser)
|
|
1018
|
+
mash.api_key # => '<%= not_evaluated %>'
|
|
663
1019
|
```
|
|
664
1020
|
|
|
665
1021
|
You can access a Mash from another class:
|
|
666
1022
|
|
|
1023
|
+
<!-- hashie:example mash-load -->
|
|
667
1024
|
```ruby
|
|
668
|
-
|
|
1025
|
+
class Twitter
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
mash = Hashie::Mash.load('settings/twitter.yml')['production']
|
|
669
1029
|
Twitter.extend mash.to_module # NOTE: if you want another name than settings, call: to_module('my_settings')
|
|
670
1030
|
Twitter.settings.api_key # => 'abcd'
|
|
1031
|
+
ENV.delete('HASHIE_README_API_KEY')
|
|
671
1032
|
```
|
|
672
1033
|
|
|
673
|
-
You can use another parser (by default: [YamlErbParser](lib/hashie/extensions/parsers/yaml_erb_parser.rb)):
|
|
1034
|
+
You can use another parser (by default: [YamlErbParser](lib/hashie/extensions/parsers/yaml_erb_parser.rb), which evaluates ERB):
|
|
674
1035
|
|
|
675
|
-
```
|
|
676
|
-
|
|
677
|
-
id
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
2 |Laurent | Garnier
|
|
1036
|
+
```text
|
|
1037
|
+
# data/user.csv
|
|
1038
|
+
id,name,lastname
|
|
1039
|
+
1,John,Doe
|
|
1040
|
+
2,Laurent,Garnier
|
|
681
1041
|
```
|
|
682
1042
|
|
|
683
1043
|
```ruby
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
1044
|
+
require 'csv'
|
|
1045
|
+
require 'fileutils'
|
|
1046
|
+
|
|
1047
|
+
class MyCustomCsvParser
|
|
1048
|
+
def self.perform(path, _options = {})
|
|
1049
|
+
CSV.read(path, headers: true).to_h do |row|
|
|
1050
|
+
[row['id'].to_i, { 'name' => row['name'], 'lastname' => row['lastname'] }]
|
|
1051
|
+
end
|
|
1052
|
+
end
|
|
1053
|
+
end
|
|
1054
|
+
|
|
1055
|
+
FileUtils.mkdir_p('data')
|
|
1056
|
+
File.write('data/user.csv', "id,name,lastname\n1,John,Doe\n2,Laurent,Garnier\n")
|
|
1057
|
+
mash = Hashie::Mash.load('data/user.csv', parser: MyCustomCsvParser)
|
|
1058
|
+
mash.to_h # => { 1 => { 'name' => 'John', 'lastname' => 'Doe' }, 2 => { 'name' => 'Laurent', 'lastname' => 'Garnier' } }
|
|
1059
|
+
mash[1].to_h #=> { 'name' => 'John', 'lastname' => 'Doe' }
|
|
687
1060
|
```
|
|
688
1061
|
|
|
689
|
-
|
|
1062
|
+
Both built-in YAML parsers use `YAML.safe_load`.
|
|
690
1063
|
|
|
691
1064
|
Specify `permitted_symbols`, `permitted_classes` and `aliases` options as needed.
|
|
692
1065
|
|
|
693
1066
|
```ruby
|
|
694
|
-
|
|
1067
|
+
File.write('symbols.yml', ":name: Ruby\n")
|
|
1068
|
+
mash = Hashie::Mash.load('symbols.yml', permitted_classes: [Symbol], permitted_symbols: [], aliases: false)
|
|
1069
|
+
mash.name # => 'Ruby'
|
|
695
1070
|
```
|
|
696
1071
|
|
|
1072
|
+
### Mash and Rails 4 Strong Parameters
|
|
1073
|
+
|
|
1074
|
+
To enable compatibility with Rails 4 use the [hashie-forbidden_attributes](https://github.com/Maxim-Filimonov/hashie-forbidden_attributes) gem.
|
|
1075
|
+
|
|
697
1076
|
### KeepOriginalKeys
|
|
698
1077
|
|
|
699
1078
|
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.
|
|
@@ -735,7 +1114,7 @@ mash = PermissiveMash.new(a: 1)
|
|
|
735
1114
|
mash.respond_to? :b #=> true
|
|
736
1115
|
```
|
|
737
1116
|
|
|
738
|
-
|
|
1117
|
+
Use this only when the delegator needs to forward reads for keys that have not been set.
|
|
739
1118
|
|
|
740
1119
|
### SafeAssignment
|
|
741
1120
|
|
|
@@ -747,14 +1126,58 @@ class SafeMash < ::Hashie::Mash
|
|
|
747
1126
|
end
|
|
748
1127
|
|
|
749
1128
|
safe_mash = SafeMash.new
|
|
750
|
-
|
|
751
|
-
safe_mash
|
|
1129
|
+
begin
|
|
1130
|
+
safe_mash.zip = 'Test'
|
|
1131
|
+
rescue ArgumentError
|
|
1132
|
+
# Expected: ArgumentError, /clashes/
|
|
1133
|
+
end
|
|
1134
|
+
begin
|
|
1135
|
+
safe_mash[:zip] = 'test'
|
|
1136
|
+
rescue ArgumentError
|
|
1137
|
+
# Expected: ArgumentError, /clashes/
|
|
1138
|
+
end
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
### TransformKeys
|
|
1142
|
+
|
|
1143
|
+
Include `Hashie::Extensions::Mash::TransformKeys` and configure `transform_keys_with` with a callable or block to normalize string and symbol keys. The transformer receives a string and must return a string; other key types are unchanged.
|
|
1144
|
+
|
|
1145
|
+
```ruby
|
|
1146
|
+
class LowercaseMash < Hashie::Mash
|
|
1147
|
+
include Hashie::Extensions::Mash::TransformKeys
|
|
1148
|
+
transform_keys_with(&:downcase)
|
|
1149
|
+
end
|
|
1150
|
+
|
|
1151
|
+
mash = LowercaseMash.new('User' => { 'NAME' => 'Ada' })
|
|
1152
|
+
mash.user.name # => 'Ada'
|
|
1153
|
+
mash.USER.NAME # => 'Ada'
|
|
1154
|
+
```
|
|
1155
|
+
|
|
1156
|
+
Normalization applies to reads and writes, including nested Mashes and hashes inside arrays. Subclasses inherit the callable but can replace it without changing their parent or siblings. Without a callable, ordinary Mash key conversion is retained. Configure the transformer before creating instances, keep it free of side effects, and make it idempotent: transforming an already-normalized key must return the same key. When input keys normalize to the same key, the last assignment wins. Transformer exceptions propagate; non-string results raise `TypeError`.
|
|
1157
|
+
|
|
1158
|
+
### UnderscoreKeys
|
|
1159
|
+
|
|
1160
|
+
Include `Hashie::Extensions::Mash::UnderscoreKeys` to normalize camelCase, PascalCase, and hyphenated keys to lowercase underscore names. It uses `TransformKeys`, including its nested conversion and subclass configuration behavior.
|
|
1161
|
+
|
|
1162
|
+
```ruby
|
|
1163
|
+
class UnderscoreMash < Hashie::Mash
|
|
1164
|
+
include Hashie::Extensions::Mash::UnderscoreKeys
|
|
1165
|
+
end
|
|
1166
|
+
|
|
1167
|
+
mash = UnderscoreMash.new('updatedAt' => 'today', 'created-at' => 'tomorrow')
|
|
1168
|
+
mash.updatedAt # => 'today'
|
|
1169
|
+
mash.updated_at # => 'today'
|
|
1170
|
+
mash.created_at # => 'tomorrow'
|
|
1171
|
+
mash.to_h # => { 'updated_at' => 'today', 'created_at' => 'tomorrow' }
|
|
752
1172
|
```
|
|
753
1173
|
|
|
1174
|
+
Acronym boundaries are split: `HTTPServer` becomes `http_server`, and `GitHub` becomes `git_hub`. There is no acronym registry or ActiveSupport dependency. Spaces and punctuation other than hyphens are retained, so this does not guarantee method-safe names. Non-text keys are unchanged. If keys normalize to the same name, the last assignment wins. Use `transform_keys_with` to replace this policy with custom normalization.
|
|
1175
|
+
|
|
754
1176
|
### SymbolizeKeys
|
|
755
1177
|
|
|
756
|
-
|
|
1178
|
+
Include `Hashie::Extensions::Mash::SymbolizeKeys` to store string keys as symbols instead of converting symbols to strings. This is useful when passing a Mash as keyword arguments:
|
|
757
1179
|
|
|
1180
|
+
<!-- hashie:example symbolized-mash -->
|
|
758
1181
|
```ruby
|
|
759
1182
|
class SymbolizedMash < ::Hashie::Mash
|
|
760
1183
|
include Hashie::Extensions::Mash::SymbolizeKeys
|
|
@@ -766,27 +1189,27 @@ symbol_mash.test #=> 'value'
|
|
|
766
1189
|
symbol_mash.to_h #=> {test: 'value'}
|
|
767
1190
|
|
|
768
1191
|
def example(test:)
|
|
769
|
-
|
|
1192
|
+
test
|
|
770
1193
|
end
|
|
771
1194
|
|
|
772
|
-
example(symbol_mash) #=> value
|
|
1195
|
+
example(**symbol_mash) #=> 'value'
|
|
773
1196
|
```
|
|
774
1197
|
|
|
775
|
-
|
|
1198
|
+
Iteration also returns symbol keys:
|
|
776
1199
|
|
|
1200
|
+
<!-- hashie:example symbolized-mash -->
|
|
777
1201
|
```ruby
|
|
778
1202
|
symbol_mash = SymbolizedMash.new(id: 123, name: 'Rey')
|
|
1203
|
+
entries = []
|
|
779
1204
|
symbol_mash.each do |key, value|
|
|
780
|
-
|
|
781
|
-
# value is 123, then 'Rey'
|
|
1205
|
+
entries << [key, value]
|
|
782
1206
|
end
|
|
1207
|
+
entries # => [[:id, 123], [:name, 'Rey']]
|
|
783
1208
|
```
|
|
784
1209
|
|
|
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
1210
|
### DefineAccessors
|
|
788
1211
|
|
|
789
|
-
|
|
1212
|
+
Include `Hashie::Extensions::Mash::DefineAccessors` to lazily define readers and writers when first used, reducing repeated dispatch through `method_missing`.
|
|
790
1213
|
|
|
791
1214
|
```ruby
|
|
792
1215
|
class MyHash < ::Hashie::Mash
|
|
@@ -807,15 +1230,76 @@ You can also extend the existing mash without defining a class:
|
|
|
807
1230
|
|
|
808
1231
|
```ruby
|
|
809
1232
|
mash = ::Hashie::Mash.new.with_accessors!
|
|
1233
|
+
mash.foo = 123
|
|
1234
|
+
mash.singleton_class.method_defined?(:foo=) # => true
|
|
1235
|
+
mash.foo # => 123
|
|
1236
|
+
mash.singleton_class.method_defined?(:foo) # => true
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
### StrictKeyAccess
|
|
1240
|
+
|
|
1241
|
+
This extension can be mixed into a Mash to raise a `KeyError` for any key that is not set, instead of returning `nil`. It's aware of Mash's special suffixed methods (`?`, `!`, `_` and `=`), which all continue to work as normal. Since nested Hashes are converted into instances of the same Mash class, this behavior automatically applies to nested Mashes as well.
|
|
1242
|
+
|
|
1243
|
+
```ruby
|
|
1244
|
+
class StrictMash < Hashie::Mash
|
|
1245
|
+
include Hashie::Extensions::Mash::StrictKeyAccess
|
|
1246
|
+
end
|
|
1247
|
+
|
|
1248
|
+
mash = StrictMash.new(name: 'Bob', address: { city: 'Springfield' })
|
|
1249
|
+
mash.name #=> 'Bob'
|
|
1250
|
+
begin
|
|
1251
|
+
mash.age
|
|
1252
|
+
rescue KeyError
|
|
1253
|
+
# Expected: KeyError, 'key not found: `age`'
|
|
1254
|
+
end
|
|
1255
|
+
|
|
1256
|
+
mash.name? #=> true
|
|
1257
|
+
mash.age? #=> false
|
|
1258
|
+
|
|
1259
|
+
mash.address.city #=> 'Springfield'
|
|
1260
|
+
begin
|
|
1261
|
+
mash.address.postcode
|
|
1262
|
+
rescue KeyError
|
|
1263
|
+
# Expected: KeyError, 'key not found: `postcode`'
|
|
1264
|
+
end
|
|
1265
|
+
```
|
|
1266
|
+
|
|
1267
|
+
|
|
1268
|
+
### RequiredKeys
|
|
1269
|
+
|
|
1270
|
+
This extension can be mixed into a Mash to require specific keys to be set (non-nil) when constructing an instance, while still allowing arbitrary, undeclared keys to be assigned - unlike `Hashie::Dash`, which only allows setting pre-declared properties.
|
|
1271
|
+
|
|
1272
|
+
```ruby
|
|
1273
|
+
class PersonMash < ::Hashie::Mash
|
|
1274
|
+
include Hashie::Extensions::Mash::RequiredKeys
|
|
1275
|
+
|
|
1276
|
+
required_keys :name, :email
|
|
1277
|
+
end
|
|
1278
|
+
|
|
1279
|
+
begin
|
|
1280
|
+
PersonMash.new(email: 'bob@example.com')
|
|
1281
|
+
rescue ArgumentError
|
|
1282
|
+
# Expected: ArgumentError, 'the following keys are required: `name`'
|
|
1283
|
+
end
|
|
1284
|
+
|
|
1285
|
+
person = PersonMash.new(name: 'Bob', email: 'bob@example.com')
|
|
1286
|
+
person.age = 42 # arbitrary, undeclared keys are still allowed
|
|
1287
|
+
person.age # => 42
|
|
810
1288
|
```
|
|
811
1289
|
|
|
812
1290
|
## Dash
|
|
813
1291
|
|
|
814
|
-
Dash
|
|
1292
|
+
Dash accepts only declared properties. Use `default:` for initial values, `required: true` to reject missing or nil values, and `message:` to customize a required-property error. A proc assigned as a property's value is evaluated when read; proc defaults are evaluated during initialization.
|
|
815
1293
|
|
|
816
1294
|
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.
|
|
817
1295
|
|
|
818
1296
|
```ruby
|
|
1297
|
+
class Genome
|
|
1298
|
+
def self.sequence
|
|
1299
|
+
'GATTACA'
|
|
1300
|
+
end
|
|
1301
|
+
end
|
|
1302
|
+
|
|
819
1303
|
class Person < Hashie::Dash
|
|
820
1304
|
property :name, required: true
|
|
821
1305
|
property :age, required: true, message: 'must be set.'
|
|
@@ -823,30 +1307,53 @@ class Person < Hashie::Dash
|
|
|
823
1307
|
property :phone, required: -> { email.nil? }, message: 'is required if email is not set.'
|
|
824
1308
|
property :pants, required: :weekday?, message: 'are only required on weekdays.'
|
|
825
1309
|
property :occupation, default: 'Rubyist'
|
|
1310
|
+
property :genome
|
|
826
1311
|
|
|
827
1312
|
def weekday?
|
|
828
1313
|
[ Time.now.saturday?, Time.now.sunday? ].none?
|
|
829
1314
|
end
|
|
830
1315
|
end
|
|
831
1316
|
|
|
832
|
-
|
|
833
|
-
p = Person.new
|
|
1317
|
+
begin
|
|
1318
|
+
p = Person.new
|
|
1319
|
+
rescue ArgumentError
|
|
1320
|
+
# Expected: ArgumentError, /the property `name` is required for/
|
|
1321
|
+
end
|
|
1322
|
+
begin
|
|
1323
|
+
p = Person.new(name: 'Bob')
|
|
1324
|
+
rescue ArgumentError
|
|
1325
|
+
# Expected: ArgumentError, "the property `age` must be set."
|
|
1326
|
+
end
|
|
834
1327
|
|
|
835
|
-
p = Person.new(name: "Bob", age: 18)
|
|
1328
|
+
p = Person.new(name: "Bob", age: 18, email: 'bob@example.com', pants: true)
|
|
836
1329
|
p.name # => 'Bob'
|
|
837
|
-
|
|
1330
|
+
begin
|
|
1331
|
+
p.name = nil
|
|
1332
|
+
rescue ArgumentError
|
|
1333
|
+
# Expected: ArgumentError, /the property `name` is required for/
|
|
1334
|
+
end
|
|
838
1335
|
p.age # => 18
|
|
839
|
-
|
|
1336
|
+
begin
|
|
1337
|
+
p.age = nil
|
|
1338
|
+
rescue ArgumentError
|
|
1339
|
+
# Expected: ArgumentError, "the property `age` must be set."
|
|
1340
|
+
end
|
|
840
1341
|
p.email = 'abc@def.com'
|
|
841
1342
|
p.occupation # => 'Rubyist'
|
|
842
1343
|
p.email # => 'abc@def.com'
|
|
843
|
-
|
|
1344
|
+
begin
|
|
1345
|
+
p[:awesome]
|
|
1346
|
+
rescue NoMethodError
|
|
1347
|
+
# Expected: NoMethodError
|
|
1348
|
+
end
|
|
844
1349
|
p[:occupation] # => 'Rubyist'
|
|
845
1350
|
p.update_attributes!(name: 'Trudy', occupation: 'Evil')
|
|
846
1351
|
p.occupation # => 'Evil'
|
|
847
1352
|
p.name # => 'Trudy'
|
|
848
1353
|
p.update_attributes!(occupation: nil)
|
|
849
1354
|
p.occupation # => 'Rubyist'
|
|
1355
|
+
p.genome = -> { Genome.sequence } # Some expensive operation
|
|
1356
|
+
p.genome # => 'GATTACA'
|
|
850
1357
|
```
|
|
851
1358
|
|
|
852
1359
|
Properties defined as symbols are not the same thing as properties defined as strings.
|
|
@@ -858,12 +1365,12 @@ class Tricky < Hashie::Dash
|
|
|
858
1365
|
end
|
|
859
1366
|
|
|
860
1367
|
p = Tricky.new(trick: 'one', 'trick' => 'two')
|
|
861
|
-
p.trick # => '
|
|
1368
|
+
p.trick # => 'two'
|
|
862
1369
|
p[:trick] # => 'one'
|
|
863
1370
|
p['trick'] # => 'two'
|
|
864
1371
|
```
|
|
865
1372
|
|
|
866
|
-
|
|
1373
|
+
Bracket access distinguishes symbol and string properties. When both declarations generate the same method name, the last declaration supplies the method accessor.
|
|
867
1374
|
|
|
868
1375
|
```ruby
|
|
869
1376
|
class Tricky < Hashie::Dash
|
|
@@ -871,7 +1378,7 @@ class Tricky < Hashie::Dash
|
|
|
871
1378
|
end
|
|
872
1379
|
|
|
873
1380
|
p = Tricky.new('trick' => 'two')
|
|
874
|
-
p.trick # =>
|
|
1381
|
+
p.trick # => 'two'
|
|
875
1382
|
```
|
|
876
1383
|
|
|
877
1384
|
If you would like to update a Dash and use any default values set in the case of a `nil` value, use `#update_attributes!`.
|
|
@@ -895,10 +1402,98 @@ dash.update_attributes!(description: nil)
|
|
|
895
1402
|
dash.description #=> 'none'
|
|
896
1403
|
```
|
|
897
1404
|
|
|
1405
|
+
The names `required` and `required?` can themselves be used as properties, for example when modeling a Swagger parameter. Their values are independent of Dash's `required: true` declaration option; `false` is a valid non-nil value even for a required property.
|
|
1406
|
+
|
|
1407
|
+
```ruby
|
|
1408
|
+
class SwaggerParameter < Hashie::Dash
|
|
1409
|
+
property :name, required: true
|
|
1410
|
+
property :required, default: false
|
|
1411
|
+
property :required?
|
|
1412
|
+
end
|
|
1413
|
+
|
|
1414
|
+
parameter = SwaggerParameter.new(name: 'limit', required: true, required?: false)
|
|
1415
|
+
parameter.required # => true
|
|
1416
|
+
parameter.required? # => false
|
|
1417
|
+
parameter.required = false
|
|
1418
|
+
parameter[:required] # => false
|
|
1419
|
+
SwaggerParameter.required?(:name) # => true
|
|
1420
|
+
SwaggerParameter.required?(:required) # => false
|
|
1421
|
+
```
|
|
1422
|
+
|
|
1423
|
+
### Lazy Property Loading
|
|
1424
|
+
|
|
1425
|
+
Include `Hashie::Extensions::Dash::Lazy` after other Dash extensions to load properties on demand. The initializer block receives an input key and runs only when that property is first read. Results are cached, including `nil` and `false`; other properties remain unloaded.
|
|
1426
|
+
|
|
1427
|
+
```ruby
|
|
1428
|
+
class LazyPerson < Hashie::Trash
|
|
1429
|
+
include Hashie::Extensions::Dash::Coercion
|
|
1430
|
+
include Hashie::Extensions::Dash::Lazy
|
|
1431
|
+
|
|
1432
|
+
property :name, from: :fullName, required: true
|
|
1433
|
+
property :age, coerce: Integer
|
|
1434
|
+
end
|
|
1435
|
+
|
|
1436
|
+
source = { fullName: 'Ada', age: '37' }
|
|
1437
|
+
person = LazyPerson.new do |key|
|
|
1438
|
+
# Fetch this property from an expensive source, such as a remote API.
|
|
1439
|
+
source[key]
|
|
1440
|
+
end
|
|
1441
|
+
|
|
1442
|
+
person.name # => 'Ada' (loads :fullName)
|
|
1443
|
+
person.name # => 'Ada' (cached)
|
|
1444
|
+
person.age # => 37 (loads :age)
|
|
1445
|
+
```
|
|
1446
|
+
|
|
1447
|
+
The loader can query XML or another source instead of a hash. With Trash, it receives the original input key (`:fullName` above); transformations and coercion run only for the property being read. Nested objects need their own loaders to remain lazy.
|
|
1448
|
+
|
|
1449
|
+
Return `nil` for missing input to use the property's default. Defaults and required-property checks run on access, not construction; conditional requirements may load other properties. Errors propagate without caching the failed load, and circular dependencies raise `ArgumentError`.
|
|
1450
|
+
|
|
1451
|
+
On these classes, the initializer block is a property loader, not a Hash default block. Without a block, initialization remains eager. Initial attributes take precedence over the loader, with canonical keys preferred over translated keys. Assignment overrides a pending load and validates normally.
|
|
1452
|
+
|
|
1453
|
+
Getters, `[]`, `fetch`, and `dig` load individual properties. `to_h`, `to_hash`, and `freeze` load all declared properties. Other Hash operations, including enumeration and inspection, see only loaded entries. Synchronize concurrent access externally.
|
|
1454
|
+
|
|
1455
|
+
### Validations with ActiveModel
|
|
1456
|
+
|
|
1457
|
+
Include [ActiveModel::Validations](https://api.rubyonrails.org/classes/ActiveModel/Validations.html) in a Dash subclass to validate property values with standard validators or custom `validate` methods. Add `gem 'activemodel'` to your application's Gemfile unless Rails already provides it; ActiveModel is optional and is not a Hashie runtime dependency.
|
|
1458
|
+
|
|
1459
|
+
```ruby
|
|
1460
|
+
require 'logger'
|
|
1461
|
+
require 'active_model'
|
|
1462
|
+
require 'hashie'
|
|
1463
|
+
|
|
1464
|
+
class Article < Hashie::Dash
|
|
1465
|
+
include ActiveModel::Validations
|
|
1466
|
+
|
|
1467
|
+
property :title, required: true
|
|
1468
|
+
|
|
1469
|
+
validates :title, presence: true, length: { maximum: 15 }
|
|
1470
|
+
end
|
|
1471
|
+
|
|
1472
|
+
article = Article.new(title: 'Hello')
|
|
1473
|
+
article.valid? # => true
|
|
1474
|
+
|
|
1475
|
+
article.title = 'A title that is too long' # assignment succeeds
|
|
1476
|
+
article.valid? # => false
|
|
1477
|
+
article.errors[:title] # => ["is too long (maximum is 15 characters)"]
|
|
1478
|
+
|
|
1479
|
+
article.title = ''
|
|
1480
|
+
article.valid? # => false
|
|
1481
|
+
article.errors[:title] # => ["can't be blank"]
|
|
1482
|
+
|
|
1483
|
+
article.title = 'Short title'
|
|
1484
|
+
article.valid? # => true
|
|
1485
|
+
article.errors.empty? # => true
|
|
1486
|
+
```
|
|
1487
|
+
|
|
1488
|
+
Call `valid?` explicitly after constructing or changing the Dash. Including `ActiveModel::Validations` does not automatically validate on initialization or assignment, reject invalid values, or roll back changes; `valid?` returns a boolean and populates `errors`.
|
|
1489
|
+
|
|
1490
|
+
Dash's `required: true` is a separate non-nil constraint: omitting `title` during initialization or assigning `nil` raises `ArgumentError` immediately, without calling `valid?`. It permits an empty string, whereas ActiveModel's `presence` validator reports blank values when `valid?` runs. Leave off `required: true` if missing or nil values should be collected as validation errors instead of raising.
|
|
1491
|
+
|
|
898
1492
|
### Potential Gotchas
|
|
899
1493
|
|
|
900
|
-
Because Dashes are subclasses of the built-in Ruby Hash class,
|
|
1494
|
+
Because Dashes are subclasses of the built-in Ruby Hash class, older Ruby versions could preserve the Dash subclass when using it as the first double-splat in a hash literal. The additional keys then existed without declared properties, and property access could raise `NoMethodError`. On Ruby 3.3 and newer the resulting object is a plain Hash:
|
|
901
1495
|
|
|
1496
|
+
<!-- hashie:example dash-double-splat -->
|
|
902
1497
|
```ruby
|
|
903
1498
|
class Foo < Hashie::Dash
|
|
904
1499
|
property :bar
|
|
@@ -906,17 +1501,17 @@ end
|
|
|
906
1501
|
|
|
907
1502
|
foo = Foo.new(bar: 'baz') #=> {:bar=>"baz"}
|
|
908
1503
|
qux = { **foo, quux: 'corge' } #=> {:bar=> "baz", :quux=>"corge"}
|
|
909
|
-
qux.is_a?(Foo) #=>
|
|
910
|
-
qux[:quux]
|
|
911
|
-
#=> raise NoMethodError, "The property 'quux' is not defined for Foo."
|
|
1504
|
+
qux.is_a?(Foo) #=> false
|
|
1505
|
+
qux[:quux] #=> 'corge'
|
|
912
1506
|
qux.key?(:quux) #=> true
|
|
913
1507
|
```
|
|
914
1508
|
|
|
915
|
-
|
|
1509
|
+
For code that also targets older Rubies, you can avoid preserving the subclass in two ways:
|
|
916
1510
|
|
|
917
1511
|
1. Call `#to_h` on the resulting object to convert it into a Hash.
|
|
918
1512
|
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
1513
|
|
|
1514
|
+
<!-- hashie:example dash-double-splat -->
|
|
920
1515
|
```ruby
|
|
921
1516
|
qux = { **foo, quux: 'corge' }.to_h #=> {:bar=> "baz", :quux=>"corge"}
|
|
922
1517
|
qux.is_a?(Hash) #=> true
|
|
@@ -929,12 +1524,7 @@ qux[:quux] #=> "corge"
|
|
|
929
1524
|
|
|
930
1525
|
### PropertyTranslation
|
|
931
1526
|
|
|
932
|
-
|
|
933
|
-
the ability to remap keys from a source hash.
|
|
934
|
-
|
|
935
|
-
Property translation is useful when you need to read data from another
|
|
936
|
-
application -- such as a Java API -- where the keys are named differently from
|
|
937
|
-
Ruby conventions.
|
|
1527
|
+
Include `Hashie::Extensions::Dash::PropertyTranslation` to map incoming keys to declared properties, for example when an API uses different naming conventions:
|
|
938
1528
|
|
|
939
1529
|
```ruby
|
|
940
1530
|
class PersonHash < Hashie::Dash
|
|
@@ -948,13 +1538,14 @@ end
|
|
|
948
1538
|
|
|
949
1539
|
person = PersonHash.new(firstName: 'Michael', l_name: 'Bleigh')
|
|
950
1540
|
person[:first_name] #=> 'Michael'
|
|
951
|
-
person[:last_name] #=> 'Bleigh
|
|
1541
|
+
person[:last_name] #=> 'Bleigh'
|
|
952
1542
|
```
|
|
953
1543
|
|
|
954
|
-
|
|
955
|
-
when you want to ensure the type of data you're wrapping.
|
|
1544
|
+
Use a lambda to transform an incoming value:
|
|
956
1545
|
|
|
957
1546
|
```ruby
|
|
1547
|
+
require 'time'
|
|
1548
|
+
|
|
958
1549
|
class DataModelHash < Hashie::Dash
|
|
959
1550
|
include Hashie::Extensions::Dash::PropertyTranslation
|
|
960
1551
|
|
|
@@ -963,23 +1554,19 @@ class DataModelHash < Hashie::Dash
|
|
|
963
1554
|
end
|
|
964
1555
|
|
|
965
1556
|
model = DataModelHash.new(id: '123', created: '2014-04-25 22:35:28')
|
|
966
|
-
model.id.class #=> Integer
|
|
1557
|
+
model.id.class #=> Integer
|
|
967
1558
|
model.created_at.class #=> Time
|
|
968
1559
|
```
|
|
969
1560
|
|
|
970
|
-
### Mash and Rails 4 Strong Parameters
|
|
971
|
-
|
|
972
|
-
To enable compatibility with Rails 4 use the [hashie-forbidden_attributes](https://github.com/Maxim-Filimonov/hashie-forbidden_attributes) gem.
|
|
973
|
-
|
|
974
1561
|
### Coercion
|
|
975
1562
|
|
|
976
|
-
|
|
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:
|
|
1563
|
+
Use `Hashie::Extensions::Dash::Coercion` to declare coercion rules with `property ..., coerce:`. It includes the general `Coercion` extension and replaces separate `property` and `coerce_key` declarations:
|
|
981
1564
|
|
|
982
1565
|
```ruby
|
|
1566
|
+
class PostHash < Hashie::Dash
|
|
1567
|
+
property :title
|
|
1568
|
+
end
|
|
1569
|
+
|
|
983
1570
|
class UserHash < Hashie::Dash
|
|
984
1571
|
include Hashie::Extensions::Coercion
|
|
985
1572
|
|
|
@@ -988,23 +1575,30 @@ class UserHash < Hashie::Dash
|
|
|
988
1575
|
|
|
989
1576
|
coerce_key :posts, Array[PostHash]
|
|
990
1577
|
end
|
|
1578
|
+
|
|
1579
|
+
UserHash.new(posts: [{ title: 'Hello' }]).posts.first.title # => 'Hello'
|
|
991
1580
|
```
|
|
992
1581
|
|
|
993
1582
|
This is the same as:
|
|
994
1583
|
|
|
995
1584
|
```ruby
|
|
1585
|
+
class PostHash < Hashie::Dash
|
|
1586
|
+
property :title
|
|
1587
|
+
end
|
|
1588
|
+
|
|
996
1589
|
class UserHash < Hashie::Dash
|
|
997
1590
|
include Hashie::Extensions::Dash::Coercion
|
|
998
1591
|
|
|
999
1592
|
property :id
|
|
1000
1593
|
property :posts, coerce: Array[PostHash]
|
|
1001
1594
|
end
|
|
1595
|
+
|
|
1596
|
+
UserHash.new(posts: [{ title: 'Hello' }]).posts.first.title # => 'Hello'
|
|
1002
1597
|
```
|
|
1003
1598
|
|
|
1004
1599
|
### PredefinedValues
|
|
1005
1600
|
|
|
1006
|
-
The `Hashie::Extensions::Dash::PredefinedValues` mixin extends a Dash with
|
|
1007
|
-
the ability to accept predefined values on a property.
|
|
1601
|
+
The `Hashie::Extensions::Dash::PredefinedValues` mixin extends a Dash with the ability to accept predefined values on a property.
|
|
1008
1602
|
|
|
1009
1603
|
```ruby
|
|
1010
1604
|
class UserHash < Hashie::Dash
|
|
@@ -1013,29 +1607,39 @@ class UserHash < Hashie::Dash
|
|
|
1013
1607
|
property :gender, values: %i[male female prefer_not_to_say]
|
|
1014
1608
|
property :age, values: (0..150)
|
|
1015
1609
|
end
|
|
1610
|
+
|
|
1611
|
+
UserHash.new(gender: :male, age: 42).age # => 42
|
|
1612
|
+
begin
|
|
1613
|
+
UserHash.new(age: -1)
|
|
1614
|
+
rescue ArgumentError
|
|
1615
|
+
# Expected: ArgumentError, /value `-1` is not allowed for `age`/
|
|
1616
|
+
end
|
|
1016
1617
|
```
|
|
1017
1618
|
|
|
1018
1619
|
## Trash
|
|
1019
1620
|
|
|
1020
|
-
A Trash is a Dash that allows you to translate keys on initialization. It mixes
|
|
1021
|
-
in the PropertyTranslation mixin by default and is used like so:
|
|
1621
|
+
A Trash is a Dash that allows you to translate keys on initialization. It mixes in the PropertyTranslation mixin by default and is used like so:
|
|
1022
1622
|
|
|
1623
|
+
<!-- hashie:example trash -->
|
|
1023
1624
|
```ruby
|
|
1024
1625
|
class Person < Hashie::Trash
|
|
1025
1626
|
property :first_name, from: :firstName
|
|
1026
1627
|
end
|
|
1027
1628
|
```
|
|
1028
1629
|
|
|
1029
|
-
This will automatically translate the
|
|
1030
|
-
when it is initialized using a hash such as through:
|
|
1630
|
+
This will automatically translate the `firstName` key to `first_name` when it is initialized using a hash such as through:
|
|
1031
1631
|
|
|
1632
|
+
<!-- hashie:example trash -->
|
|
1032
1633
|
```ruby
|
|
1033
|
-
Person.new(firstName: 'Bob')
|
|
1634
|
+
Person.new(firstName: 'Bob').first_name # => 'Bob'
|
|
1034
1635
|
```
|
|
1035
1636
|
|
|
1036
1637
|
Trash also supports translations using lambda, this could be useful when dealing with external API's. You can use it in this way:
|
|
1037
1638
|
|
|
1639
|
+
<!-- hashie:example trash-result -->
|
|
1038
1640
|
```ruby
|
|
1641
|
+
require 'time'
|
|
1642
|
+
|
|
1039
1643
|
class Result < Hashie::Trash
|
|
1040
1644
|
property :id, transform_with: lambda { |v| v.to_i }
|
|
1041
1645
|
property :created_at, from: :creation_date, with: lambda { |v| Time.parse(v) }
|
|
@@ -1044,12 +1648,29 @@ end
|
|
|
1044
1648
|
|
|
1045
1649
|
this will produce the following
|
|
1046
1650
|
|
|
1651
|
+
<!-- hashie:example trash-result -->
|
|
1047
1652
|
```ruby
|
|
1048
1653
|
result = Result.new(id: '123', creation_date: '2012-03-30 17:23:28')
|
|
1049
|
-
result.id.class # => Integer
|
|
1654
|
+
result.id.class # => Integer
|
|
1050
1655
|
result.created_at.class # => Time
|
|
1051
1656
|
```
|
|
1052
1657
|
|
|
1658
|
+
Multiple properties can share a source key to flatten a nested hash. Combine indifferent access with `IgnoreUndeclared` to accept string or symbol input keys and ignore unrelated fields:
|
|
1659
|
+
|
|
1660
|
+
```ruby
|
|
1661
|
+
class FlattenedPerson < Hashie::Trash
|
|
1662
|
+
include Hashie::Extensions::IgnoreUndeclared
|
|
1663
|
+
include Hashie::Extensions::IndifferentAccess
|
|
1664
|
+
|
|
1665
|
+
property :first_name, from: :name, transform_with: ->(name) { name['first'] }
|
|
1666
|
+
property :last_name, from: :name, transform_with: ->(name) { name['last'] }
|
|
1667
|
+
end
|
|
1668
|
+
|
|
1669
|
+
person = FlattenedPerson.new('name' => { 'first' => 'First', 'last' => 'Last' }, 'extra' => true)
|
|
1670
|
+
person.first_name # => 'First'
|
|
1671
|
+
person.last_name # => 'Last'
|
|
1672
|
+
```
|
|
1673
|
+
|
|
1053
1674
|
## Clash
|
|
1054
1675
|
|
|
1055
1676
|
Clash is a Chainable Lazy Hash that allows you to easily construct complex hashes using method notation chaining. This will allow you to use a more action-oriented approach to building options hashes.
|
|
@@ -1073,6 +1694,8 @@ c.where(abc: 'def').where(hgi: 123)
|
|
|
1073
1694
|
c # => { where: { abc: 'def', hgi: 123 } }
|
|
1074
1695
|
```
|
|
1075
1696
|
|
|
1697
|
+
Clash supports YAML and Marshal round-trips, preserving stored data, named subclasses, and parent links used by `_end!`. Only deserialize trusted data. YAML requires Ruby object loading enabled (`Psych.unsafe_load` on modern Psych); ordinary safe loading does not permit arbitrary Ruby classes.
|
|
1698
|
+
|
|
1076
1699
|
## Rash
|
|
1077
1700
|
|
|
1078
1701
|
Rash is a Hash whose keys can be Regexps or Ranges, which will map many input keys to a value.
|
|
@@ -1084,7 +1707,7 @@ If the Rash's value is a `proc`, the `proc` will be automatically called with th
|
|
|
1084
1707
|
```ruby
|
|
1085
1708
|
|
|
1086
1709
|
# Mapping names to appropriate greetings
|
|
1087
|
-
greeting = Hashie::Rash.new( /^Mr
|
|
1710
|
+
greeting = Hashie::Rash.new( /^Mr\./ => "Hello sir!", /^Mrs\./ => "Evening, madame." )
|
|
1088
1711
|
greeting["Mr. Steve Austin"] # => "Hello sir!"
|
|
1089
1712
|
greeting["Mrs. Steve Austin"] # => "Evening, madame."
|
|
1090
1713
|
|
|
@@ -1103,8 +1726,21 @@ mapper["Get off my lawn!"] # => "Forget your lawn, old man!"
|
|
|
1103
1726
|
|
|
1104
1727
|
If this value is too low or too high for your needs, you can tune it by setting: `rash.optimize_every = n`.
|
|
1105
1728
|
|
|
1729
|
+
## Logging
|
|
1730
|
+
|
|
1731
|
+
Hashie logs to `STDOUT` by default. Assign `Hashie.logger` to use your application's logger:
|
|
1732
|
+
|
|
1733
|
+
```ruby
|
|
1734
|
+
require 'logger'
|
|
1735
|
+
require 'hashie'
|
|
1736
|
+
|
|
1737
|
+
Hashie.logger = Logger.new($stdout)
|
|
1738
|
+
```
|
|
1739
|
+
|
|
1740
|
+
In Rails, use `Hashie.logger = Rails.logger`; Hashie's Railtie sets this automatically during application initialization.
|
|
1741
|
+
|
|
1106
1742
|
## Mascot
|
|
1107
|
-
[](https://en.wiktionary.org/wiki/eierlegende_Wollmilchsau) Meet Hashie's "
|
|
1743
|
+
[](https://en.wiktionary.org/wiki/eierlegende_Wollmilchsau) Meet Hashie's "official" mascot, the [eierlegende Wollmilchsau](https://en.wiktionary.org/wiki/eierlegende_Wollmilchsau)!
|
|
1108
1744
|
|
|
1109
1745
|
## Contributing
|
|
1110
1746
|
|