hashie 5.1.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 +56 -0
- data/CONTRIBUTING.md +38 -1
- data/README.md +816 -181
- data/Rakefile +29 -1
- data/UPGRADING.md +18 -0
- data/hashie.gemspec +8 -7
- data/lib/hashie/array.rb +15 -12
- data/lib/hashie/clash.rb +59 -4
- data/lib/hashie/dash.rb +144 -16
- 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 -19
- 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 +15 -28
- 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 +93 -7
- 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 +293 -62
- 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 +15 -17
data/README.md
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
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
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
|
- [Installation](#installation)
|
|
12
13
|
- [Stable Release](#stable-release)
|
|
13
14
|
- [Hash Extensions](#hash-extensions)
|
|
14
|
-
- [Logging](#logging)
|
|
15
15
|
- [Coercion](#coercion)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
- [Coercing Collections](#coercing-collections)
|
|
17
|
+
- [Coercing Hashes](#coercing-hashes)
|
|
18
|
+
- [Coercing Core Types](#coercing-core-types)
|
|
19
|
+
- [Coercion Proc](#coercion-proc)
|
|
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)
|
|
21
26
|
- [KeyConversion](#keyconversion)
|
|
22
27
|
- [MergeInitializer](#mergeinitializer)
|
|
28
|
+
- [DeepMergeInitializer](#deepmergeinitializer)
|
|
23
29
|
- [MethodAccess](#methodaccess)
|
|
30
|
+
- [BlockAccess](#blockaccess)
|
|
31
|
+
- [Persistable](#persistable)
|
|
24
32
|
- [MethodAccessWithOverride](#methodaccesswithoverride)
|
|
25
33
|
- [MethodOverridingInitializer](#methodoverridinginitializer)
|
|
26
34
|
- [IndifferentAccess](#indifferentaccess)
|
|
@@ -28,39 +36,53 @@
|
|
|
28
36
|
- [DeepMerge](#deepmerge)
|
|
29
37
|
- [DeepFetch](#deepfetch)
|
|
30
38
|
- [DeepFind](#deepfind)
|
|
39
|
+
- [DeepGrep](#deepgrep)
|
|
31
40
|
- [DeepLocate](#deeplocate)
|
|
32
|
-
- [StrictKeyAccess](#strictkeyaccess)
|
|
41
|
+
- [StrictKeyAccess](#strictkeyaccess)
|
|
33
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)
|
|
34
48
|
- [KeepOriginalKeys](#keeporiginalkeys)
|
|
35
49
|
- [PermissiveRespondTo](#permissiverespondto)
|
|
36
50
|
- [SafeAssignment](#safeassignment)
|
|
51
|
+
- [TransformKeys](#transformkeys)
|
|
52
|
+
- [UnderscoreKeys](#underscorekeys)
|
|
37
53
|
- [SymbolizeKeys](#symbolizekeys)
|
|
38
54
|
- [DefineAccessors](#defineaccessors)
|
|
55
|
+
- [StrictKeyAccess](#strictkeyaccess-1)
|
|
56
|
+
- [RequiredKeys](#requiredkeys)
|
|
39
57
|
- [Dash](#dash)
|
|
58
|
+
- [Lazy Property Loading](#lazy-property-loading)
|
|
59
|
+
- [Validations with ActiveModel](#validations-with-activemodel)
|
|
40
60
|
- [Potential Gotchas](#potential-gotchas)
|
|
41
61
|
- [PropertyTranslation](#propertytranslation)
|
|
42
|
-
- [Mash and Rails 4 Strong Parameters](#mash-and-rails-4-strong-parameters)
|
|
43
62
|
- [Coercion](#coercion-1)
|
|
44
63
|
- [PredefinedValues](#predefinedvalues)
|
|
45
64
|
- [Trash](#trash)
|
|
46
65
|
- [Clash](#clash)
|
|
47
66
|
- [Rash](#rash)
|
|
48
67
|
- [Auto-Optimized](#auto-optimized)
|
|
68
|
+
- [Logging](#logging)
|
|
49
69
|
- [Mascot](#mascot)
|
|
50
70
|
- [Contributing](#contributing)
|
|
51
71
|
- [Copyright](#copyright)
|
|
52
72
|
|
|
53
73
|
## Installation
|
|
54
74
|
|
|
75
|
+
Hashie requires Ruby 3.0 or newer.
|
|
76
|
+
|
|
55
77
|
Hashie is available as a RubyGem:
|
|
56
78
|
|
|
57
79
|
```bash
|
|
58
|
-
|
|
80
|
+
gem install hashie
|
|
59
81
|
```
|
|
60
82
|
|
|
61
83
|
## Stable Release
|
|
62
84
|
|
|
63
|
-
You're reading the documentation for the stable release of Hashie, 5.
|
|
85
|
+
You're reading the documentation for the stable release of Hashie, 5.2.0.
|
|
64
86
|
|
|
65
87
|
## Hash Extensions
|
|
66
88
|
|
|
@@ -68,20 +90,16 @@ The library is broken up into a number of atomically includable Hash extension m
|
|
|
68
90
|
|
|
69
91
|
Any of the extensions listed below can be mixed into a class by `include`-ing `Hashie::Extensions::ExtensionName`.
|
|
70
92
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
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:
|
|
74
|
-
|
|
75
|
-
```ruby
|
|
76
|
-
# Set the logger to the Rails logger
|
|
77
|
-
Hashie.logger = Rails.logger
|
|
78
|
-
```
|
|
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.
|
|
79
94
|
|
|
80
95
|
### Coercion
|
|
81
96
|
|
|
82
|
-
|
|
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:
|
|
83
98
|
|
|
84
99
|
```ruby
|
|
100
|
+
class User < Hashie::Mash
|
|
101
|
+
end
|
|
102
|
+
|
|
85
103
|
class Tweet < Hash
|
|
86
104
|
include Hashie::Extensions::Coercion
|
|
87
105
|
include Hashie::Extensions::MergeInitializer
|
|
@@ -89,9 +107,10 @@ class Tweet < Hash
|
|
|
89
107
|
end
|
|
90
108
|
|
|
91
109
|
user_hash = { name: "Bob" }
|
|
92
|
-
Tweet.new(user: user_hash)
|
|
93
|
-
#
|
|
94
|
-
|
|
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"
|
|
95
114
|
```
|
|
96
115
|
|
|
97
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.
|
|
@@ -108,31 +127,45 @@ class SpecialHash < Hash
|
|
|
108
127
|
end
|
|
109
128
|
end
|
|
110
129
|
end
|
|
130
|
+
|
|
131
|
+
hash = SpecialHash.new(child: { name: 'Bob' })
|
|
132
|
+
hash[:child].class # => SpecialHash
|
|
133
|
+
hash[:child][:name] # => 'Bob'
|
|
111
134
|
```
|
|
112
135
|
|
|
113
|
-
|
|
136
|
+
#### Coercing Collections
|
|
114
137
|
|
|
115
138
|
```ruby
|
|
139
|
+
require 'set'
|
|
140
|
+
|
|
141
|
+
class User < Hashie::Mash
|
|
142
|
+
end
|
|
143
|
+
|
|
116
144
|
class Tweet < Hash
|
|
117
145
|
include Hashie::Extensions::Coercion
|
|
146
|
+
include Hashie::Extensions::MergeInitializer
|
|
147
|
+
include Hashie::Extensions::MethodAccess
|
|
118
148
|
coerce_key :mentions, Array[User]
|
|
119
149
|
coerce_key :friends, Set[User]
|
|
120
150
|
end
|
|
121
151
|
|
|
122
152
|
user_hash = { name: "Bob" }
|
|
123
|
-
mentions_hash= [user_hash, user_hash]
|
|
153
|
+
mentions_hash = [user_hash, user_hash]
|
|
124
154
|
friends_hash = [user_hash]
|
|
125
155
|
tweet = Tweet.new(mentions: mentions_hash, friends: friends_hash)
|
|
126
|
-
#
|
|
127
|
-
#
|
|
156
|
+
# Automatically calls User.coerce(user_hash), or User.new(user_hash) if absent,
|
|
157
|
+
# on each element of the array.
|
|
128
158
|
|
|
129
159
|
tweet.mentions.map(&:class) # => [User, User]
|
|
130
160
|
tweet.friends.class # => Set
|
|
131
161
|
```
|
|
132
162
|
|
|
133
|
-
|
|
163
|
+
#### Coercing Hashes
|
|
134
164
|
|
|
135
165
|
```ruby
|
|
166
|
+
class User < Hashie::Mash
|
|
167
|
+
end
|
|
168
|
+
|
|
136
169
|
class Relation
|
|
137
170
|
def initialize(string)
|
|
138
171
|
@relation = string
|
|
@@ -141,20 +174,22 @@ end
|
|
|
141
174
|
|
|
142
175
|
class Tweet < Hash
|
|
143
176
|
include Hashie::Extensions::Coercion
|
|
177
|
+
include Hashie::Extensions::MergeInitializer
|
|
178
|
+
include Hashie::Extensions::MethodAccess
|
|
144
179
|
coerce_key :relations, Hash[User => Relation]
|
|
145
180
|
end
|
|
146
181
|
|
|
147
182
|
user_hash = { name: "Bob" }
|
|
148
|
-
relations_hash= { user_hash => "father",
|
|
183
|
+
relations_hash = { user_hash => "father", { name: "Alice" } => "friend" }
|
|
149
184
|
tweet = Tweet.new(relations: relations_hash)
|
|
150
185
|
tweet.relations.map { |k,v| [k.class, v.class] } # => [[User, Relation], [User, Relation]]
|
|
151
186
|
tweet.relations.class # => Hash
|
|
152
187
|
|
|
153
|
-
#
|
|
154
|
-
#
|
|
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`.
|
|
155
190
|
```
|
|
156
191
|
|
|
157
|
-
|
|
192
|
+
#### Coercing Core Types
|
|
158
193
|
|
|
159
194
|
Hashie handles coercion to the following by using standard conversion methods:
|
|
160
195
|
|
|
@@ -169,19 +204,18 @@ Hashie handles coercion to the following by using standard conversion methods:
|
|
|
169
204
|
|
|
170
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.
|
|
171
206
|
|
|
172
|
-
|
|
173
|
-
- Integer
|
|
174
|
-
- Numeric
|
|
207
|
+
`coerce_value` also supports the supertypes `Integer` and `Numeric`.
|
|
175
208
|
|
|
176
|
-
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.
|
|
177
210
|
|
|
178
|
-
|
|
211
|
+
#### Coercion Proc
|
|
179
212
|
|
|
180
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:
|
|
181
214
|
|
|
182
215
|
```ruby
|
|
183
216
|
class Tweet < Hash
|
|
184
217
|
include Hashie::Extensions::Coercion
|
|
218
|
+
include Hashie::Extensions::MergeInitializer
|
|
185
219
|
coerce_key :retweeted, ->(v) do
|
|
186
220
|
case v
|
|
187
221
|
when String
|
|
@@ -193,43 +227,199 @@ class Tweet < Hash
|
|
|
193
227
|
end
|
|
194
228
|
end
|
|
195
229
|
end
|
|
230
|
+
|
|
231
|
+
Tweet.new(retweeted: 'yes')[:retweeted] # => true
|
|
232
|
+
Tweet.new(retweeted: 'false')[:retweeted] # => false
|
|
196
233
|
```
|
|
197
234
|
|
|
198
|
-
|
|
235
|
+
##### Circular Coercion
|
|
199
236
|
|
|
200
237
|
Since `coerce_key` is a class-level method, you cannot have circular coercion without the use of a proc. For example:
|
|
201
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
|
+
|
|
202
260
|
```ruby
|
|
203
261
|
class CategoryHash < Hash
|
|
204
262
|
include Hashie::Extensions::Coercion
|
|
205
263
|
include Hashie::Extensions::MergeInitializer
|
|
206
264
|
|
|
207
|
-
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
|
|
208
270
|
end
|
|
209
271
|
|
|
210
272
|
class ProductHash < Hash
|
|
211
273
|
include Hashie::Extensions::Coercion
|
|
212
274
|
include Hashie::Extensions::MergeInitializer
|
|
213
275
|
|
|
214
|
-
coerce_key :categories, Array[
|
|
276
|
+
coerce_key :categories, Array[CategoryHash]
|
|
215
277
|
end
|
|
278
|
+
|
|
279
|
+
category = CategoryHash.new(products: [{ categories: [{}] }])
|
|
280
|
+
category[:products].first.class # => ProductHash
|
|
281
|
+
category[:products].first[:categories].first.class # => CategoryHash
|
|
216
282
|
```
|
|
217
283
|
|
|
218
|
-
|
|
284
|
+
#### Nil Values and Defaults
|
|
219
285
|
|
|
220
|
-
|
|
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:
|
|
221
287
|
|
|
222
288
|
```ruby
|
|
223
|
-
class
|
|
224
|
-
|
|
225
|
-
coerce_key :products, ->(value) do
|
|
226
|
-
return value.map { |v| ProductHash.new(v) } if value.respond_to?(:map)
|
|
289
|
+
class DescriptionParams < Hashie::Dash
|
|
290
|
+
include Hashie::Extensions::Coercion
|
|
227
291
|
|
|
228
|
-
|
|
229
|
-
|
|
292
|
+
property :description, default: ''
|
|
293
|
+
coerce_key :description, String
|
|
230
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 # => ''
|
|
231
305
|
```
|
|
232
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
|
|
315
|
+
```
|
|
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
|
+
|
|
233
423
|
### KeyConversion
|
|
234
424
|
|
|
235
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`.
|
|
@@ -237,16 +427,51 @@ The KeyConversion extension gives you the convenience methods of `symbolize_keys
|
|
|
237
427
|
Hashie also has a utility method for converting keys on a Hash without a mixin:
|
|
238
428
|
|
|
239
429
|
```ruby
|
|
240
|
-
|
|
241
|
-
Hashie.symbolize_keys
|
|
242
|
-
|
|
243
|
-
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' }
|
|
244
437
|
```
|
|
245
438
|
|
|
246
439
|
### MergeInitializer
|
|
247
440
|
|
|
248
441
|
The MergeInitializer extension simply makes it possible to initialize a Hash subclass with another Hash, giving you a quick short-hand.
|
|
249
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
|
+
|
|
250
475
|
### MethodAccess
|
|
251
476
|
|
|
252
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`.
|
|
@@ -262,6 +487,56 @@ h.abc # => 'def'
|
|
|
262
487
|
h.abc? # => true
|
|
263
488
|
```
|
|
264
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
|
+
|
|
265
540
|
### MethodAccessWithOverride
|
|
266
541
|
|
|
267
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`.
|
|
@@ -311,6 +586,7 @@ This extension can be mixed in to your Hash subclass to allow you to use Strings
|
|
|
311
586
|
|
|
312
587
|
In addition, IndifferentAccess will also inject itself into sub-hashes so they behave the same.
|
|
313
588
|
|
|
589
|
+
<!-- hashie:example indifferent-access -->
|
|
314
590
|
```ruby
|
|
315
591
|
class MyHash < Hash
|
|
316
592
|
include Hashie::Extensions::MergeInitializer
|
|
@@ -332,14 +608,15 @@ myhash['fishes']['food'] # => "flakes"
|
|
|
332
608
|
|
|
333
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:
|
|
334
610
|
|
|
611
|
+
<!-- hashie:example indifferent-access -->
|
|
335
612
|
```ruby
|
|
336
613
|
myhash = MyHash.new
|
|
337
614
|
myhash["foo"] = "bar"
|
|
338
615
|
myhash[:foo] #=> "bar"
|
|
339
616
|
|
|
340
617
|
normal_hash = myhash.to_hash
|
|
341
|
-
|
|
342
|
-
|
|
618
|
+
normal_hash["foo"] #=> "bar"
|
|
619
|
+
normal_hash[:foo] #=> nil
|
|
343
620
|
```
|
|
344
621
|
|
|
345
622
|
### IgnoreUndeclared
|
|
@@ -347,7 +624,7 @@ myhash[:foo] #=> nil
|
|
|
347
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.
|
|
348
625
|
|
|
349
626
|
```ruby
|
|
350
|
-
class Person < Trash
|
|
627
|
+
class Person < Hashie::Trash
|
|
351
628
|
include Hashie::Extensions::IgnoreUndeclared
|
|
352
629
|
property :first_name
|
|
353
630
|
property :last_name
|
|
@@ -363,13 +640,16 @@ p = Person.new(user_data) # 'email' is silently ignored
|
|
|
363
640
|
|
|
364
641
|
p.first_name # => 'Freddy'
|
|
365
642
|
p.last_name # => 'Nostrils'
|
|
366
|
-
|
|
643
|
+
begin
|
|
644
|
+
p.email
|
|
645
|
+
rescue NoMethodError
|
|
646
|
+
# Expected: NoMethodError
|
|
647
|
+
end
|
|
367
648
|
```
|
|
368
649
|
|
|
369
650
|
### DeepMerge
|
|
370
651
|
|
|
371
|
-
|
|
372
|
-
system into any Hash descendant:
|
|
652
|
+
Include `DeepMerge` to merge nested hashes recursively:
|
|
373
653
|
|
|
374
654
|
```ruby
|
|
375
655
|
class MyHash < Hash
|
|
@@ -393,11 +673,9 @@ end
|
|
|
393
673
|
h1 = MyHash[{ a: 100, b: 200, c: { c1: 100 } }]
|
|
394
674
|
h2 = MyHash[{ b: 250, c: { c1: 200 } }]
|
|
395
675
|
|
|
396
|
-
h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
|
|
397
|
-
# => { 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 } }
|
|
398
677
|
```
|
|
399
678
|
|
|
400
|
-
|
|
401
679
|
### DeepFetch
|
|
402
680
|
|
|
403
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.
|
|
@@ -416,7 +694,11 @@ user = {
|
|
|
416
694
|
user.extend Hashie::Extensions::DeepFetch
|
|
417
695
|
|
|
418
696
|
user.deep_fetch :name, :first # => 'Bob'
|
|
419
|
-
|
|
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
|
|
420
702
|
|
|
421
703
|
# using a default block
|
|
422
704
|
user.deep_fetch(:name, :middle) { |key| 'default' } # => 'default'
|
|
@@ -451,11 +733,39 @@ user.deep_find_all(:name) #=> [{ first: 'Bob', last: 'Boberts' }, 'Rubyists', 'O
|
|
|
451
733
|
user.deep_select(:name) #=> [{ first: 'Bob', last: 'Boberts' }, 'Rubyists', 'Open source enthusiasts']
|
|
452
734
|
```
|
|
453
735
|
|
|
454
|
-
###
|
|
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
|
+
]
|
|
455
752
|
|
|
456
|
-
|
|
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
|
|
758
|
+
|
|
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.
|
|
457
765
|
|
|
458
|
-
|
|
766
|
+
### DeepLocate
|
|
767
|
+
|
|
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.
|
|
459
769
|
|
|
460
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.
|
|
461
771
|
|
|
@@ -483,17 +793,14 @@ books = [
|
|
|
483
793
|
|
|
484
794
|
books.extend(Hashie::Extensions::DeepLocate)
|
|
485
795
|
|
|
486
|
-
|
|
487
|
-
# http://ruby-journal.com/becareful-with-space-in-lambda-hash-rocket-syntax-between-ruby-1-dot-9-and-2-dot-0/
|
|
488
|
-
|
|
489
|
-
books.deep_locate -> (key, value, object) { key == :title && value.include?("Ruby") }
|
|
490
|
-
# => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"Ruby for the rest of us", :pages=>576}]
|
|
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}]
|
|
491
797
|
|
|
492
|
-
books.deep_locate ->
|
|
493
|
-
# => [{: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}]
|
|
494
799
|
```
|
|
495
800
|
|
|
496
|
-
|
|
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
|
|
497
804
|
|
|
498
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.
|
|
499
806
|
|
|
@@ -502,17 +809,18 @@ class StrictKeyAccessHash < Hash
|
|
|
502
809
|
include Hashie::Extensions::StrictKeyAccess
|
|
503
810
|
end
|
|
504
811
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
|
511
819
|
```
|
|
512
820
|
|
|
513
821
|
## Mash
|
|
514
822
|
|
|
515
|
-
Mash
|
|
823
|
+
Mash adds method-style access to Hash keys and recursively wraps nested hashes:
|
|
516
824
|
|
|
517
825
|
```ruby
|
|
518
826
|
mash = Hashie::Mash.new
|
|
@@ -521,37 +829,34 @@ mash.name # => nil
|
|
|
521
829
|
mash.name = "My Mash"
|
|
522
830
|
mash.name # => "My Mash"
|
|
523
831
|
mash.name? # => true
|
|
524
|
-
mash.inspect # =>
|
|
832
|
+
mash.inspect # => '#<Hashie::Mash name="My Mash">'
|
|
525
833
|
|
|
526
834
|
mash = Hashie::Mash.new
|
|
527
835
|
# use bang methods for multi-level assignment
|
|
528
836
|
mash.author!.name = "Michael Bleigh"
|
|
529
|
-
mash.author # =>
|
|
837
|
+
mash.author.to_h # => { 'name' => 'Michael Bleigh' }
|
|
530
838
|
|
|
531
839
|
mash = Hashie::Mash.new
|
|
532
840
|
# use under-bang methods for multi-level testing
|
|
533
841
|
mash.author_.name? # => false
|
|
534
|
-
mash.inspect # =>
|
|
842
|
+
mash.inspect # => '#<Hashie::Mash>'
|
|
535
843
|
```
|
|
536
844
|
|
|
537
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.
|
|
538
846
|
|
|
539
|
-
|
|
847
|
+
### Method Conflicts
|
|
540
848
|
|
|
541
|
-
|
|
849
|
+
Mash does not override existing methods. Use brackets to read a key that conflicts with a method name:
|
|
542
850
|
|
|
543
851
|
```ruby
|
|
544
852
|
mash = Hashie::Mash.new
|
|
545
853
|
mash.name = "My Mash"
|
|
546
854
|
mash.zip = "Method Override?"
|
|
547
855
|
mash.zip # => [[["name", "My Mash"]], [["zip", "Method Override?"]]]
|
|
856
|
+
mash[:zip] # => 'Method Override?'
|
|
548
857
|
```
|
|
549
858
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
```ruby
|
|
553
|
-
Hashie.logger = Rails.logger
|
|
554
|
-
```
|
|
859
|
+
Hashie logs a warning when a key conflicts with an existing method. See [Logging](#logging) to configure the logger.
|
|
555
860
|
|
|
556
861
|
You can also disable the logging in subclasses of Mash:
|
|
557
862
|
|
|
@@ -559,14 +864,19 @@ You can also disable the logging in subclasses of Mash:
|
|
|
559
864
|
class Response < Hashie::Mash
|
|
560
865
|
disable_warnings
|
|
561
866
|
end
|
|
867
|
+
|
|
868
|
+
Response.new(zip: '90210').zip # => [[['zip', '90210']]]
|
|
869
|
+
Response.disable_warnings? # => true
|
|
562
870
|
```
|
|
563
871
|
|
|
564
|
-
|
|
872
|
+
To suppress warnings only for specific methods, pass their names to `disable_warnings`:
|
|
565
873
|
|
|
566
874
|
```ruby
|
|
567
875
|
class Response < Hashie::Mash
|
|
568
876
|
disable_warnings :zip, :zap
|
|
569
877
|
end
|
|
878
|
+
|
|
879
|
+
Response.disabled_warnings # => [:zip, :zap]
|
|
570
880
|
```
|
|
571
881
|
|
|
572
882
|
This behavior is cumulative. The examples above and below behave identically.
|
|
@@ -576,21 +886,29 @@ class Response < Hashie::Mash
|
|
|
576
886
|
disable_warnings :zip
|
|
577
887
|
disable_warnings :zap
|
|
578
888
|
end
|
|
889
|
+
|
|
890
|
+
Response.disabled_warnings # => [:zip, :zap]
|
|
579
891
|
```
|
|
580
892
|
|
|
581
|
-
|
|
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:
|
|
582
894
|
|
|
583
895
|
```ruby
|
|
896
|
+
require 'stringio'
|
|
897
|
+
|
|
584
898
|
class Message < Hashie::Mash
|
|
585
899
|
disable_warnings :zip, :zap
|
|
586
900
|
disable_warnings
|
|
587
901
|
end
|
|
588
902
|
|
|
589
|
-
|
|
903
|
+
log = StringIO.new
|
|
904
|
+
Hashie.logger = Logger.new(log)
|
|
590
905
|
Message.new(merge: 'true', compact: true)
|
|
906
|
+
log.string # => ''
|
|
591
907
|
```
|
|
592
908
|
|
|
593
909
|
```ruby
|
|
910
|
+
require 'stringio'
|
|
911
|
+
|
|
594
912
|
class Message < Hashie::Mash
|
|
595
913
|
disable_warnings
|
|
596
914
|
end
|
|
@@ -599,20 +917,31 @@ class Response < Message
|
|
|
599
917
|
disable_warnings :zip, :zap
|
|
600
918
|
end
|
|
601
919
|
|
|
602
|
-
|
|
920
|
+
log = StringIO.new
|
|
921
|
+
Hashie.logger = Logger.new(log)
|
|
603
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
|
|
604
926
|
```
|
|
605
927
|
|
|
606
928
|
If you would like to create an anonymous subclass of a Hashie::Mash with key conflict warnings disabled:
|
|
607
929
|
|
|
608
930
|
```ruby
|
|
609
|
-
|
|
610
|
-
|
|
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
|
|
611
940
|
```
|
|
612
941
|
|
|
613
|
-
|
|
942
|
+
### Nested Hashes
|
|
614
943
|
|
|
615
|
-
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:
|
|
616
945
|
|
|
617
946
|
```ruby
|
|
618
947
|
mash = Hashie::Mash.new(name: "Hashie", dependencies: { rake: "< 11", rspec: "~> 3.0" })
|
|
@@ -623,75 +952,127 @@ my_gem = MyGem.new(name: "Hashie", dependencies: { rake: "< 11", rspec: "~> 3.0"
|
|
|
623
952
|
my_gem.dependencies.class #=> MyGem
|
|
624
953
|
```
|
|
625
954
|
|
|
626
|
-
|
|
955
|
+
### Key Types
|
|
627
956
|
|
|
628
|
-
Mash
|
|
957
|
+
Mash uses string keys for indifferent string/symbol access. Other key types, such as integers, are preserved and accessed with brackets:
|
|
629
958
|
|
|
630
959
|
```ruby
|
|
631
|
-
Hashie::Mash.new('1' => 'one string', :'1' => 'one sym', 1 => 'one num')
|
|
632
|
-
# => {"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"}
|
|
633
961
|
```
|
|
634
962
|
|
|
635
|
-
The symbol
|
|
963
|
+
The symbol `:'1'` and string `'1'` share a key, so the later value wins. The integer `1` remains a separate key.
|
|
636
964
|
|
|
637
|
-
|
|
965
|
+
### Loading Files
|
|
638
966
|
|
|
639
|
-
Mash
|
|
967
|
+
Use `Mash.load` to read a YAML configuration file:
|
|
640
968
|
|
|
641
969
|
```yml
|
|
642
|
-
|
|
970
|
+
# settings/twitter.yml
|
|
643
971
|
development:
|
|
644
972
|
api_key: 'api_key'
|
|
645
973
|
production:
|
|
646
|
-
api_key: <%= ENV
|
|
974
|
+
api_key: <%= ENV.fetch('HASHIE_README_API_KEY') %> # set to 'abcd' below
|
|
647
975
|
```
|
|
648
976
|
|
|
977
|
+
<!-- hashie:example mash-load -->
|
|
649
978
|
```ruby
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
|
653
998
|
mash.development.api_key? # => true
|
|
999
|
+
mash.development.api_key = "foo"
|
|
1000
|
+
mash.development.api_key # => 'foo'
|
|
654
1001
|
```
|
|
655
1002
|
|
|
656
1003
|
You can also load with a `Pathname` object:
|
|
657
1004
|
|
|
1005
|
+
<!-- hashie:example mash-load -->
|
|
658
1006
|
```ruby
|
|
659
|
-
mash = Mash.load(Pathname 'settings/twitter.yml')
|
|
660
|
-
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 %>'
|
|
661
1019
|
```
|
|
662
1020
|
|
|
663
1021
|
You can access a Mash from another class:
|
|
664
1022
|
|
|
1023
|
+
<!-- hashie:example mash-load -->
|
|
665
1024
|
```ruby
|
|
666
|
-
|
|
1025
|
+
class Twitter
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
mash = Hashie::Mash.load('settings/twitter.yml')['production']
|
|
667
1029
|
Twitter.extend mash.to_module # NOTE: if you want another name than settings, call: to_module('my_settings')
|
|
668
1030
|
Twitter.settings.api_key # => 'abcd'
|
|
1031
|
+
ENV.delete('HASHIE_README_API_KEY')
|
|
669
1032
|
```
|
|
670
1033
|
|
|
671
|
-
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):
|
|
672
1035
|
|
|
673
|
-
```
|
|
674
|
-
|
|
675
|
-
id
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
2 |Laurent | Garnier
|
|
1036
|
+
```text
|
|
1037
|
+
# data/user.csv
|
|
1038
|
+
id,name,lastname
|
|
1039
|
+
1,John,Doe
|
|
1040
|
+
2,Laurent,Garnier
|
|
679
1041
|
```
|
|
680
1042
|
|
|
681
1043
|
```ruby
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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' }
|
|
685
1060
|
```
|
|
686
1061
|
|
|
687
|
-
|
|
1062
|
+
Both built-in YAML parsers use `YAML.safe_load`.
|
|
688
1063
|
|
|
689
1064
|
Specify `permitted_symbols`, `permitted_classes` and `aliases` options as needed.
|
|
690
1065
|
|
|
691
1066
|
```ruby
|
|
692
|
-
|
|
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'
|
|
693
1070
|
```
|
|
694
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
|
+
|
|
695
1076
|
### KeepOriginalKeys
|
|
696
1077
|
|
|
697
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.
|
|
@@ -733,7 +1114,7 @@ mash = PermissiveMash.new(a: 1)
|
|
|
733
1114
|
mash.respond_to? :b #=> true
|
|
734
1115
|
```
|
|
735
1116
|
|
|
736
|
-
|
|
1117
|
+
Use this only when the delegator needs to forward reads for keys that have not been set.
|
|
737
1118
|
|
|
738
1119
|
### SafeAssignment
|
|
739
1120
|
|
|
@@ -745,14 +1126,58 @@ class SafeMash < ::Hashie::Mash
|
|
|
745
1126
|
end
|
|
746
1127
|
|
|
747
1128
|
safe_mash = SafeMash.new
|
|
748
|
-
|
|
749
|
-
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
|
|
750
1139
|
```
|
|
751
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' }
|
|
1172
|
+
```
|
|
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
|
+
|
|
752
1176
|
### SymbolizeKeys
|
|
753
1177
|
|
|
754
|
-
|
|
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:
|
|
755
1179
|
|
|
1180
|
+
<!-- hashie:example symbolized-mash -->
|
|
756
1181
|
```ruby
|
|
757
1182
|
class SymbolizedMash < ::Hashie::Mash
|
|
758
1183
|
include Hashie::Extensions::Mash::SymbolizeKeys
|
|
@@ -764,27 +1189,27 @@ symbol_mash.test #=> 'value'
|
|
|
764
1189
|
symbol_mash.to_h #=> {test: 'value'}
|
|
765
1190
|
|
|
766
1191
|
def example(test:)
|
|
767
|
-
|
|
1192
|
+
test
|
|
768
1193
|
end
|
|
769
1194
|
|
|
770
|
-
example(symbol_mash) #=> value
|
|
1195
|
+
example(**symbol_mash) #=> 'value'
|
|
771
1196
|
```
|
|
772
1197
|
|
|
773
|
-
|
|
1198
|
+
Iteration also returns symbol keys:
|
|
774
1199
|
|
|
1200
|
+
<!-- hashie:example symbolized-mash -->
|
|
775
1201
|
```ruby
|
|
776
1202
|
symbol_mash = SymbolizedMash.new(id: 123, name: 'Rey')
|
|
1203
|
+
entries = []
|
|
777
1204
|
symbol_mash.each do |key, value|
|
|
778
|
-
|
|
779
|
-
# value is 123, then 'Rey'
|
|
1205
|
+
entries << [key, value]
|
|
780
1206
|
end
|
|
1207
|
+
entries # => [[:id, 123], [:name, 'Rey']]
|
|
781
1208
|
```
|
|
782
1209
|
|
|
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.
|
|
784
|
-
|
|
785
1210
|
### DefineAccessors
|
|
786
1211
|
|
|
787
|
-
|
|
1212
|
+
Include `Hashie::Extensions::Mash::DefineAccessors` to lazily define readers and writers when first used, reducing repeated dispatch through `method_missing`.
|
|
788
1213
|
|
|
789
1214
|
```ruby
|
|
790
1215
|
class MyHash < ::Hashie::Mash
|
|
@@ -805,15 +1230,76 @@ You can also extend the existing mash without defining a class:
|
|
|
805
1230
|
|
|
806
1231
|
```ruby
|
|
807
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
|
|
808
1288
|
```
|
|
809
1289
|
|
|
810
1290
|
## Dash
|
|
811
1291
|
|
|
812
|
-
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.
|
|
813
1293
|
|
|
814
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.
|
|
815
1295
|
|
|
816
1296
|
```ruby
|
|
1297
|
+
class Genome
|
|
1298
|
+
def self.sequence
|
|
1299
|
+
'GATTACA'
|
|
1300
|
+
end
|
|
1301
|
+
end
|
|
1302
|
+
|
|
817
1303
|
class Person < Hashie::Dash
|
|
818
1304
|
property :name, required: true
|
|
819
1305
|
property :age, required: true, message: 'must be set.'
|
|
@@ -828,18 +1314,38 @@ class Person < Hashie::Dash
|
|
|
828
1314
|
end
|
|
829
1315
|
end
|
|
830
1316
|
|
|
831
|
-
|
|
832
|
-
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
|
|
833
1327
|
|
|
834
|
-
p = Person.new(name: "Bob", age: 18)
|
|
1328
|
+
p = Person.new(name: "Bob", age: 18, email: 'bob@example.com', pants: true)
|
|
835
1329
|
p.name # => 'Bob'
|
|
836
|
-
|
|
1330
|
+
begin
|
|
1331
|
+
p.name = nil
|
|
1332
|
+
rescue ArgumentError
|
|
1333
|
+
# Expected: ArgumentError, /the property `name` is required for/
|
|
1334
|
+
end
|
|
837
1335
|
p.age # => 18
|
|
838
|
-
|
|
1336
|
+
begin
|
|
1337
|
+
p.age = nil
|
|
1338
|
+
rescue ArgumentError
|
|
1339
|
+
# Expected: ArgumentError, "the property `age` must be set."
|
|
1340
|
+
end
|
|
839
1341
|
p.email = 'abc@def.com'
|
|
840
1342
|
p.occupation # => 'Rubyist'
|
|
841
1343
|
p.email # => 'abc@def.com'
|
|
842
|
-
|
|
1344
|
+
begin
|
|
1345
|
+
p[:awesome]
|
|
1346
|
+
rescue NoMethodError
|
|
1347
|
+
# Expected: NoMethodError
|
|
1348
|
+
end
|
|
843
1349
|
p[:occupation] # => 'Rubyist'
|
|
844
1350
|
p.update_attributes!(name: 'Trudy', occupation: 'Evil')
|
|
845
1351
|
p.occupation # => 'Evil'
|
|
@@ -859,12 +1365,12 @@ class Tricky < Hashie::Dash
|
|
|
859
1365
|
end
|
|
860
1366
|
|
|
861
1367
|
p = Tricky.new(trick: 'one', 'trick' => 'two')
|
|
862
|
-
p.trick # => '
|
|
1368
|
+
p.trick # => 'two'
|
|
863
1369
|
p[:trick] # => 'one'
|
|
864
1370
|
p['trick'] # => 'two'
|
|
865
1371
|
```
|
|
866
1372
|
|
|
867
|
-
|
|
1373
|
+
Bracket access distinguishes symbol and string properties. When both declarations generate the same method name, the last declaration supplies the method accessor.
|
|
868
1374
|
|
|
869
1375
|
```ruby
|
|
870
1376
|
class Tricky < Hashie::Dash
|
|
@@ -872,7 +1378,7 @@ class Tricky < Hashie::Dash
|
|
|
872
1378
|
end
|
|
873
1379
|
|
|
874
1380
|
p = Tricky.new('trick' => 'two')
|
|
875
|
-
p.trick # =>
|
|
1381
|
+
p.trick # => 'two'
|
|
876
1382
|
```
|
|
877
1383
|
|
|
878
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!`.
|
|
@@ -896,10 +1402,98 @@ dash.update_attributes!(description: nil)
|
|
|
896
1402
|
dash.description #=> 'none'
|
|
897
1403
|
```
|
|
898
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
|
+
|
|
899
1492
|
### Potential Gotchas
|
|
900
1493
|
|
|
901
|
-
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:
|
|
902
1495
|
|
|
1496
|
+
<!-- hashie:example dash-double-splat -->
|
|
903
1497
|
```ruby
|
|
904
1498
|
class Foo < Hashie::Dash
|
|
905
1499
|
property :bar
|
|
@@ -907,17 +1501,17 @@ end
|
|
|
907
1501
|
|
|
908
1502
|
foo = Foo.new(bar: 'baz') #=> {:bar=>"baz"}
|
|
909
1503
|
qux = { **foo, quux: 'corge' } #=> {:bar=> "baz", :quux=>"corge"}
|
|
910
|
-
qux.is_a?(Foo) #=>
|
|
911
|
-
qux[:quux]
|
|
912
|
-
#=> raise NoMethodError, "The property 'quux' is not defined for Foo."
|
|
1504
|
+
qux.is_a?(Foo) #=> false
|
|
1505
|
+
qux[:quux] #=> 'corge'
|
|
913
1506
|
qux.key?(:quux) #=> true
|
|
914
1507
|
```
|
|
915
1508
|
|
|
916
|
-
|
|
1509
|
+
For code that also targets older Rubies, you can avoid preserving the subclass in two ways:
|
|
917
1510
|
|
|
918
1511
|
1. Call `#to_h` on the resulting object to convert it into a Hash.
|
|
919
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.
|
|
920
1513
|
|
|
1514
|
+
<!-- hashie:example dash-double-splat -->
|
|
921
1515
|
```ruby
|
|
922
1516
|
qux = { **foo, quux: 'corge' }.to_h #=> {:bar=> "baz", :quux=>"corge"}
|
|
923
1517
|
qux.is_a?(Hash) #=> true
|
|
@@ -930,12 +1524,7 @@ qux[:quux] #=> "corge"
|
|
|
930
1524
|
|
|
931
1525
|
### PropertyTranslation
|
|
932
1526
|
|
|
933
|
-
|
|
934
|
-
the ability to remap keys from a source hash.
|
|
935
|
-
|
|
936
|
-
Property translation is useful when you need to read data from another
|
|
937
|
-
application -- such as a Java API -- where the keys are named differently from
|
|
938
|
-
Ruby conventions.
|
|
1527
|
+
Include `Hashie::Extensions::Dash::PropertyTranslation` to map incoming keys to declared properties, for example when an API uses different naming conventions:
|
|
939
1528
|
|
|
940
1529
|
```ruby
|
|
941
1530
|
class PersonHash < Hashie::Dash
|
|
@@ -949,13 +1538,14 @@ end
|
|
|
949
1538
|
|
|
950
1539
|
person = PersonHash.new(firstName: 'Michael', l_name: 'Bleigh')
|
|
951
1540
|
person[:first_name] #=> 'Michael'
|
|
952
|
-
person[:last_name] #=> 'Bleigh
|
|
1541
|
+
person[:last_name] #=> 'Bleigh'
|
|
953
1542
|
```
|
|
954
1543
|
|
|
955
|
-
|
|
956
|
-
when you want to ensure the type of data you're wrapping.
|
|
1544
|
+
Use a lambda to transform an incoming value:
|
|
957
1545
|
|
|
958
1546
|
```ruby
|
|
1547
|
+
require 'time'
|
|
1548
|
+
|
|
959
1549
|
class DataModelHash < Hashie::Dash
|
|
960
1550
|
include Hashie::Extensions::Dash::PropertyTranslation
|
|
961
1551
|
|
|
@@ -964,23 +1554,19 @@ class DataModelHash < Hashie::Dash
|
|
|
964
1554
|
end
|
|
965
1555
|
|
|
966
1556
|
model = DataModelHash.new(id: '123', created: '2014-04-25 22:35:28')
|
|
967
|
-
model.id.class #=> Integer
|
|
1557
|
+
model.id.class #=> Integer
|
|
968
1558
|
model.created_at.class #=> Time
|
|
969
1559
|
```
|
|
970
1560
|
|
|
971
|
-
### Mash and Rails 4 Strong Parameters
|
|
972
|
-
|
|
973
|
-
To enable compatibility with Rails 4 use the [hashie-forbidden_attributes](https://github.com/Maxim-Filimonov/hashie-forbidden_attributes) gem.
|
|
974
|
-
|
|
975
1561
|
### Coercion
|
|
976
1562
|
|
|
977
|
-
|
|
978
|
-
you may probably want to use `Hashie::Extensions::Dash::Coercion` instead.
|
|
979
|
-
This extension automatically includes `Hashie::Extensions::Coercion`
|
|
980
|
-
and also adds a convenient `:coerce` option to `property` so you can define coercion in one line
|
|
981
|
-
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:
|
|
982
1564
|
|
|
983
1565
|
```ruby
|
|
1566
|
+
class PostHash < Hashie::Dash
|
|
1567
|
+
property :title
|
|
1568
|
+
end
|
|
1569
|
+
|
|
984
1570
|
class UserHash < Hashie::Dash
|
|
985
1571
|
include Hashie::Extensions::Coercion
|
|
986
1572
|
|
|
@@ -989,23 +1575,30 @@ class UserHash < Hashie::Dash
|
|
|
989
1575
|
|
|
990
1576
|
coerce_key :posts, Array[PostHash]
|
|
991
1577
|
end
|
|
1578
|
+
|
|
1579
|
+
UserHash.new(posts: [{ title: 'Hello' }]).posts.first.title # => 'Hello'
|
|
992
1580
|
```
|
|
993
1581
|
|
|
994
1582
|
This is the same as:
|
|
995
1583
|
|
|
996
1584
|
```ruby
|
|
1585
|
+
class PostHash < Hashie::Dash
|
|
1586
|
+
property :title
|
|
1587
|
+
end
|
|
1588
|
+
|
|
997
1589
|
class UserHash < Hashie::Dash
|
|
998
1590
|
include Hashie::Extensions::Dash::Coercion
|
|
999
1591
|
|
|
1000
1592
|
property :id
|
|
1001
1593
|
property :posts, coerce: Array[PostHash]
|
|
1002
1594
|
end
|
|
1595
|
+
|
|
1596
|
+
UserHash.new(posts: [{ title: 'Hello' }]).posts.first.title # => 'Hello'
|
|
1003
1597
|
```
|
|
1004
1598
|
|
|
1005
1599
|
### PredefinedValues
|
|
1006
1600
|
|
|
1007
|
-
The `Hashie::Extensions::Dash::PredefinedValues` mixin extends a Dash with
|
|
1008
|
-
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.
|
|
1009
1602
|
|
|
1010
1603
|
```ruby
|
|
1011
1604
|
class UserHash < Hashie::Dash
|
|
@@ -1014,29 +1607,39 @@ class UserHash < Hashie::Dash
|
|
|
1014
1607
|
property :gender, values: %i[male female prefer_not_to_say]
|
|
1015
1608
|
property :age, values: (0..150)
|
|
1016
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
|
|
1017
1617
|
```
|
|
1018
1618
|
|
|
1019
1619
|
## Trash
|
|
1020
1620
|
|
|
1021
|
-
A Trash is a Dash that allows you to translate keys on initialization. It mixes
|
|
1022
|
-
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:
|
|
1023
1622
|
|
|
1623
|
+
<!-- hashie:example trash -->
|
|
1024
1624
|
```ruby
|
|
1025
1625
|
class Person < Hashie::Trash
|
|
1026
1626
|
property :first_name, from: :firstName
|
|
1027
1627
|
end
|
|
1028
1628
|
```
|
|
1029
1629
|
|
|
1030
|
-
This will automatically translate the
|
|
1031
|
-
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:
|
|
1032
1631
|
|
|
1632
|
+
<!-- hashie:example trash -->
|
|
1033
1633
|
```ruby
|
|
1034
|
-
Person.new(firstName: 'Bob')
|
|
1634
|
+
Person.new(firstName: 'Bob').first_name # => 'Bob'
|
|
1035
1635
|
```
|
|
1036
1636
|
|
|
1037
1637
|
Trash also supports translations using lambda, this could be useful when dealing with external API's. You can use it in this way:
|
|
1038
1638
|
|
|
1639
|
+
<!-- hashie:example trash-result -->
|
|
1039
1640
|
```ruby
|
|
1641
|
+
require 'time'
|
|
1642
|
+
|
|
1040
1643
|
class Result < Hashie::Trash
|
|
1041
1644
|
property :id, transform_with: lambda { |v| v.to_i }
|
|
1042
1645
|
property :created_at, from: :creation_date, with: lambda { |v| Time.parse(v) }
|
|
@@ -1045,12 +1648,29 @@ end
|
|
|
1045
1648
|
|
|
1046
1649
|
this will produce the following
|
|
1047
1650
|
|
|
1651
|
+
<!-- hashie:example trash-result -->
|
|
1048
1652
|
```ruby
|
|
1049
1653
|
result = Result.new(id: '123', creation_date: '2012-03-30 17:23:28')
|
|
1050
|
-
result.id.class # => Integer
|
|
1654
|
+
result.id.class # => Integer
|
|
1051
1655
|
result.created_at.class # => Time
|
|
1052
1656
|
```
|
|
1053
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
|
+
|
|
1054
1674
|
## Clash
|
|
1055
1675
|
|
|
1056
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.
|
|
@@ -1074,6 +1694,8 @@ c.where(abc: 'def').where(hgi: 123)
|
|
|
1074
1694
|
c # => { where: { abc: 'def', hgi: 123 } }
|
|
1075
1695
|
```
|
|
1076
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
|
+
|
|
1077
1699
|
## Rash
|
|
1078
1700
|
|
|
1079
1701
|
Rash is a Hash whose keys can be Regexps or Ranges, which will map many input keys to a value.
|
|
@@ -1085,7 +1707,7 @@ If the Rash's value is a `proc`, the `proc` will be automatically called with th
|
|
|
1085
1707
|
```ruby
|
|
1086
1708
|
|
|
1087
1709
|
# Mapping names to appropriate greetings
|
|
1088
|
-
greeting = Hashie::Rash.new( /^Mr
|
|
1710
|
+
greeting = Hashie::Rash.new( /^Mr\./ => "Hello sir!", /^Mrs\./ => "Evening, madame." )
|
|
1089
1711
|
greeting["Mr. Steve Austin"] # => "Hello sir!"
|
|
1090
1712
|
greeting["Mrs. Steve Austin"] # => "Evening, madame."
|
|
1091
1713
|
|
|
@@ -1104,6 +1726,19 @@ mapper["Get off my lawn!"] # => "Forget your lawn, old man!"
|
|
|
1104
1726
|
|
|
1105
1727
|
If this value is too low or too high for your needs, you can tune it by setting: `rash.optimize_every = n`.
|
|
1106
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
|
+
|
|
1107
1742
|
## Mascot
|
|
1108
1743
|
[](https://en.wiktionary.org/wiki/eierlegende_Wollmilchsau) Meet Hashie's "official" mascot, the [eierlegende Wollmilchsau](https://en.wiktionary.org/wiki/eierlegende_Wollmilchsau)!
|
|
1109
1744
|
|