hashie 2.1.2 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +524 -59
  3. data/CONTRIBUTING.md +24 -7
  4. data/README.md +781 -90
  5. data/Rakefile +19 -2
  6. data/UPGRADING.md +245 -0
  7. data/hashie.gemspec +21 -13
  8. data/lib/hashie.rb +60 -21
  9. data/lib/hashie/array.rb +21 -0
  10. data/lib/hashie/clash.rb +24 -12
  11. data/lib/hashie/dash.rb +96 -33
  12. data/lib/hashie/extensions/active_support/core_ext/hash.rb +14 -0
  13. data/lib/hashie/extensions/array/pretty_inspect.rb +19 -0
  14. data/lib/hashie/extensions/coercion.rb +124 -18
  15. data/lib/hashie/extensions/dash/coercion.rb +25 -0
  16. data/lib/hashie/extensions/dash/indifferent_access.rb +56 -0
  17. data/lib/hashie/extensions/dash/property_translation.rb +191 -0
  18. data/lib/hashie/extensions/deep_fetch.rb +7 -5
  19. data/lib/hashie/extensions/deep_find.rb +69 -0
  20. data/lib/hashie/extensions/deep_locate.rb +113 -0
  21. data/lib/hashie/extensions/deep_merge.rb +35 -12
  22. data/lib/hashie/extensions/ignore_undeclared.rb +11 -5
  23. data/lib/hashie/extensions/indifferent_access.rb +28 -16
  24. data/lib/hashie/extensions/key_conflict_warning.rb +55 -0
  25. data/lib/hashie/extensions/key_conversion.rb +0 -82
  26. data/lib/hashie/extensions/mash/define_accessors.rb +90 -0
  27. data/lib/hashie/extensions/mash/keep_original_keys.rb +53 -0
  28. data/lib/hashie/extensions/mash/permissive_respond_to.rb +61 -0
  29. data/lib/hashie/extensions/mash/safe_assignment.rb +18 -0
  30. data/lib/hashie/extensions/mash/symbolize_keys.rb +38 -0
  31. data/lib/hashie/extensions/method_access.rb +154 -11
  32. data/lib/hashie/extensions/parsers/yaml_erb_parser.rb +48 -0
  33. data/lib/hashie/extensions/pretty_inspect.rb +19 -0
  34. data/lib/hashie/extensions/ruby_version.rb +60 -0
  35. data/lib/hashie/extensions/ruby_version_check.rb +21 -0
  36. data/lib/hashie/extensions/strict_key_access.rb +77 -0
  37. data/lib/hashie/extensions/stringify_keys.rb +71 -0
  38. data/lib/hashie/extensions/symbolize_keys.rb +71 -0
  39. data/lib/hashie/hash.rb +27 -8
  40. data/lib/hashie/logger.rb +18 -0
  41. data/lib/hashie/mash.rb +235 -57
  42. data/lib/hashie/railtie.rb +21 -0
  43. data/lib/hashie/rash.rb +40 -16
  44. data/lib/hashie/trash.rb +2 -88
  45. data/lib/hashie/utils.rb +44 -0
  46. data/lib/hashie/version.rb +1 -1
  47. metadata +42 -81
  48. data/.gitignore +0 -9
  49. data/.rspec +0 -2
  50. data/.rubocop.yml +0 -36
  51. data/.travis.yml +0 -15
  52. data/Gemfile +0 -11
  53. data/Guardfile +0 -5
  54. data/lib/hashie/hash_extensions.rb +0 -47
  55. data/spec/hashie/clash_spec.rb +0 -48
  56. data/spec/hashie/dash_spec.rb +0 -338
  57. data/spec/hashie/extensions/coercion_spec.rb +0 -156
  58. data/spec/hashie/extensions/deep_fetch_spec.rb +0 -70
  59. data/spec/hashie/extensions/deep_merge_spec.rb +0 -22
  60. data/spec/hashie/extensions/ignore_undeclared_spec.rb +0 -23
  61. data/spec/hashie/extensions/indifferent_access_spec.rb +0 -152
  62. data/spec/hashie/extensions/key_conversion_spec.rb +0 -103
  63. data/spec/hashie/extensions/merge_initializer_spec.rb +0 -23
  64. data/spec/hashie/extensions/method_access_spec.rb +0 -121
  65. data/spec/hashie/hash_spec.rb +0 -66
  66. data/spec/hashie/mash_spec.rb +0 -467
  67. data/spec/hashie/rash_spec.rb +0 -44
  68. data/spec/hashie/trash_spec.rb +0 -193
  69. data/spec/hashie/version_spec.rb +0 -7
  70. data/spec/spec.opts +0 -3
  71. data/spec/spec_helper.rb +0 -8
@@ -1,16 +1,16 @@
1
1
  Contributing to Hashie
2
2
  ======================
3
3
 
4
- Hashie is work of [many contributors](https://github.com/intridea/hashie/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/intridea/hashie/pulls), [propose features and discuss issues](https://github.com/intridea/hashie/issues).
4
+ Hashie is work of [many contributors](https://github.com/hashie/hashie/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/hashie/hashie/pulls), [propose features and discuss issues](https://github.com/hashie/hashie/issues).
5
5
 
6
6
  #### Fork the Project
7
7
 
8
- Fork the [project on Github](https://github.com/intridea/hashie) and check out your copy.
8
+ Fork the [project on Github](https://github.com/hashie/hashie) and check out your copy.
9
9
 
10
10
  ```
11
11
  git clone https://github.com/contributor/hashie.git
12
12
  cd hashie
13
- git remote add upstream https://github.com/intridea/hashie.git
13
+ git remote add upstream https://github.com/hashie/hashie.git
14
14
  ```
15
15
 
16
16
  #### Create a Topic Branch
@@ -23,12 +23,19 @@ git pull upstream master
23
23
  git checkout -b my-feature-branch
24
24
  ```
25
25
 
26
- #### Bundle Install and Test
26
+ #### Install dependencies
27
+
28
+ You can use the setup script to install dependencies for the gem and its integration tests.
29
+
30
+ ```
31
+ bin/setup
32
+ ```
33
+
34
+ #### Test
27
35
 
28
36
  Ensure that you can build the project and run tests.
29
37
 
30
38
  ```
31
- bundle install
32
39
  bundle exec rake
33
40
  ```
34
41
 
@@ -52,7 +59,17 @@ Document any external behavior in the [README](README.md).
52
59
 
53
60
  #### Update Changelog
54
61
 
55
- Add a line to [CHANGELOG](CHANGELOG.md) under *Next Release*. Make it look like every other line, including your name and link to your Github account.
62
+ Add a line to [CHANGELOG](CHANGELOG.md) under *Unreleased*. Make it look like every other line, including your name and link to your Github account.
63
+
64
+ There are several categorizations of changes that you can choose from. Add your line to the appropriate section, following these conventions:
65
+
66
+ * **Added** - When you add a new behavior to any class or module (or add a new extension) that does not break backwards compatibility, you should mark it as "added". This is generally a fully new behavior that does not touch any pre-existing public API. Changes here require a MINOR version bump, following the Semantic Versioning specification.
67
+ * **Changed** - You should mark any change to the behavior of a public API on any class or module as "changed". Changes here require a MAJOR version bump, following the Semantic Versioning specification.
68
+ * **Deprecated** - Any time you deprecate part of the public API on any class or module you should mark the change as "deprecated". Deprecated behavior will be removed in the next MAJOR version bump, but should be left in until then. Changes here require a MINOR version bump, following the Semantic Versioning specification.
69
+ * **Removed** - You should mark any behavior that you removed from a public API on any class or module as "removed". Changes here require a MAJOR version bump, following the Semantic Versioning specification.
70
+ * **Fixed** - Any time you fix a bug you should mark as "fixed". Changes here require a PATCH version bump.
71
+ * **Security** - You should mark any security issue that you fix as "security". Changes here require a PATCH version bump.
72
+ * **Miscellaneous** - Mark any other changes you make (i.e. documentation updates, test harness changes, etc.) as "miscellaneous". Changes here require a PATCH version bump.
56
73
 
57
74
  #### Commit Changes
58
75
 
@@ -95,7 +112,7 @@ git push origin my-feature-branch -f
95
112
  Update the [CHANGELOG](CHANGELOG.md) with the pull request number. A typical entry looks as follows.
96
113
 
97
114
  ```
98
- * [#123](https://github.com/intridea/hashie/pull/123): Reticulated splines - [@contributor](https://github.com/contributor).
115
+ * [#123](https://github.com/hashie/hashie/pull/123): Reticulated splines - [@contributor](https://github.com/contributor).
99
116
  ```
100
117
 
101
118
  Amend your previous commit and force push the changes.
data/README.md CHANGED
@@ -1,7 +1,12 @@
1
- # Hashie [![Build Status](https://secure.travis-ci.org/intridea/hashie.png)](http://travis-ci.org/intridea/hashie) [![Dependency Status](https://gemnasium.com/intridea/hashie.png)](https://gemnasium.com/intridea/hashie)
1
+ # Hashie
2
2
 
3
- Hashie is a growing collection of tools that extend Hashes and make
4
- them more useful.
3
+ [![Join the chat at https://gitter.im/hashie/hashie](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/hashie/hashie?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4
+ [![Gem Version](http://img.shields.io/gem/v/hashie.svg)](http://badge.fury.io/rb/hashie)
5
+ [![Build Status](http://img.shields.io/travis/hashie/hashie.svg)](https://travis-ci.org/hashie/hashie)
6
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/7a0b42c8a22c945571fd/test_coverage)](https://codeclimate.com/github/hashie/hashie/test_coverage)
7
+ [![Maintainability](https://api.codeclimate.com/v1/badges/7a0b42c8a22c945571fd/maintainability)](https://codeclimate.com/github/hashie/hashie/maintainability)
8
+
9
+ Hashie is a growing collection of tools that extend Hashes and make them more useful.
5
10
 
6
11
  ## Installation
7
12
 
@@ -11,26 +16,33 @@ Hashie is available as a RubyGem:
11
16
  $ gem install hashie
12
17
  ```
13
18
 
19
+ ## Stable Release
20
+
21
+ You're reading the documentation for the stable release of Hashie, 4.1.0.(https://github.com/hashie/hashie/blob/v4.1.0/README.md).
22
+
14
23
  ## Hash Extensions
15
24
 
16
- The library is broken up into a number of atomically includeable Hash
17
- extension modules as described below. This provides maximum flexibility
18
- for users to mix and match functionality while maintaining feature parity
19
- with earlier versions of Hashie.
25
+ The library is broken up into a number of atomically includable Hash extension modules as described below. This provides maximum flexibility for users to mix and match functionality while maintaining feature parity with earlier versions of Hashie.
20
26
 
21
- Any of the extensions listed below can be mixed into a class by
22
- `include`-ing `Hashie::Extensions::ExtensionName`.
27
+ Any of the extensions listed below can be mixed into a class by `include`-ing `Hashie::Extensions::ExtensionName`.
28
+
29
+ ## Logging
30
+
31
+ 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:
32
+
33
+ ```ruby
34
+ # Set the logger to the Rails logger
35
+ Hashie.logger = Rails.logger
36
+ ```
23
37
 
24
38
  ### Coercion
25
39
 
26
- Coercions allow you to set up "coercion rules" based either on the key
27
- or the value type to massage data as it's being inserted into the Hash.
28
- Key coercions might be used, for example, in lightweight data modeling
29
- applications such as an API client:
40
+ Coercions allow you to set up "coercion rules" based either on the key or the value type to massage data as it's being inserted into the Hash. Key coercions might be used, for example, in lightweight data modeling applications such as an API client:
30
41
 
31
42
  ```ruby
32
43
  class Tweet < Hash
33
44
  include Hashie::Extensions::Coercion
45
+ include Hashie::Extensions::MergeInitializer
34
46
  coerce_key :user, User
35
47
  end
36
48
 
@@ -40,9 +52,7 @@ Tweet.new(user: user_hash)
40
52
  # User.new(user_hash) if that isn't present.
41
53
  ```
42
54
 
43
- Value coercions, on the other hand, will coerce values based on the type
44
- of the value being inserted. This is useful if you are trying to build a
45
- Hash-like class that is self-propagating.
55
+ 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.
46
56
 
47
57
  ```ruby
48
58
  class SpecialHash < Hash
@@ -58,24 +68,146 @@ class SpecialHash < Hash
58
68
  end
59
69
  ```
60
70
 
71
+ ### Coercing Collections
72
+
73
+ ```ruby
74
+ class Tweet < Hash
75
+ include Hashie::Extensions::Coercion
76
+ coerce_key :mentions, Array[User]
77
+ coerce_key :friends, Set[User]
78
+ end
79
+
80
+ user_hash = { name: "Bob" }
81
+ mentions_hash= [user_hash, user_hash]
82
+ friends_hash = [user_hash]
83
+ tweet = Tweet.new(mentions: mentions_hash, friends: friends_hash)
84
+ # => automatically calls User.coerce(user_hash) or
85
+ # User.new(user_hash) if that isn't present on each element of the array
86
+
87
+ tweet.mentions.map(&:class) # => [User, User]
88
+ tweet.friends.class # => Set
89
+ ```
90
+
91
+ ### Coercing Hashes
92
+
93
+ ```ruby
94
+ class Relation
95
+ def initialize(string)
96
+ @relation = string
97
+ end
98
+ end
99
+
100
+ class Tweet < Hash
101
+ include Hashie::Extensions::Coercion
102
+ coerce_key :relations, Hash[User => Relation]
103
+ end
104
+
105
+ user_hash = { name: "Bob" }
106
+ relations_hash= { user_hash => "father", user_hash => "friend" }
107
+ tweet = Tweet.new(relations: relations_hash)
108
+ tweet.relations.map { |k,v| [k.class, v.class] } # => [[User, Relation], [User, Relation]]
109
+ tweet.relations.class # => Hash
110
+
111
+ # => automatically calls User.coerce(user_hash) on each key
112
+ # and Relation.new on each value since Relation doesn't define the `coerce` class method
113
+ ```
114
+
115
+ ### Coercing Core Types
116
+
117
+ Hashie handles coercion to the following by using standard conversion methods:
118
+
119
+ | type | method |
120
+ |----------|----------|
121
+ | Integer | `#to_i` |
122
+ | Float | `#to_f` |
123
+ | Complex | `#to_c` |
124
+ | Rational | `#to_r` |
125
+ | String | `#to_s` |
126
+ | Symbol | `#to_sym`|
127
+
128
+ **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.
129
+
130
+ You can also use coerce from the following supertypes with `coerce_value`:
131
+ - Integer
132
+ - Numeric
133
+
134
+ Hashie does not have built-in support for coercing boolean values, since Ruby does not have a built-in boolean type or standard method for coercing to a boolean. You can coerce to booleans using a custom proc.
135
+
136
+ ### Coercion Proc
137
+
138
+ 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:
139
+
140
+ ```ruby
141
+ class Tweet < Hash
142
+ include Hashie::Extensions::Coercion
143
+ coerce_key :retweeted, ->(v) do
144
+ case v
145
+ when String
146
+ !!(v =~ /\A(true|t|yes|y|1)\z/i)
147
+ when Numeric
148
+ !v.to_i.zero?
149
+ else
150
+ v == true
151
+ end
152
+ end
153
+ end
154
+ ```
155
+
156
+ #### A note on circular coercion
157
+
158
+ Since `coerce_key` is a class-level method, you cannot have circular coercion without the use of a proc. For example:
159
+
160
+ ```ruby
161
+ class CategoryHash < Hash
162
+ include Hashie::Extensions::Coercion
163
+ include Hashie::Extensions::MergeInitializer
164
+
165
+ coerce_key :products, Array[ProductHash]
166
+ end
167
+
168
+ class ProductHash < Hash
169
+ include Hashie::Extensions::Coercion
170
+ include Hashie::Extensions::MergeInitializer
171
+
172
+ coerce_key :categories, Array[CategoriesHash]
173
+ end
174
+ ```
175
+
176
+ This will fail with a `NameError` for `CategoryHash::ProductHash` because `ProductHash` is not defined at the point that `coerce_key` is happening for `CategoryHash`.
177
+
178
+ To work around this, you can use a coercion proc. For example, you could do:
179
+
180
+ ```ruby
181
+ class CategoryHash < Hash
182
+ # ...
183
+ coerce_key :products, ->(value) do
184
+ return value.map { |v| ProductHash.new(v) } if value.respond_to?(:map)
185
+
186
+ ProductHash.new(value)
187
+ end
188
+ end
189
+ ```
190
+
61
191
  ### KeyConversion
62
192
 
63
- The KeyConversion extension gives you the convenience methods of
64
- `symbolize_keys` and `stringify_keys` along with their bang
65
- counterparts. You can also include just stringify or just symbolize with
66
- `Hashie::Extensions::StringifyKeys` or `Hashie::Extensions::SymbolizeKeys`.
193
+ The KeyConversion extension gives you the convenience methods of `symbolize_keys` and `stringify_keys` along with their bang counterparts. You can also include just stringify or just symbolize with `Hashie::Extensions::StringifyKeys` or [`Hashie::Extensions::SymbolizeKeys`](#mash-extension-symbolizekeys).
194
+
195
+ Hashie also has a utility method for converting keys on a Hash without a mixin:
196
+
197
+ ```ruby
198
+ Hashie.symbolize_keys! hash # => Symbolizes keys of hash.
199
+ Hashie.symbolize_keys hash # => Returns a copy of hash with keys symbolized.
200
+ Hashie.stringify_keys! hash # => Stringifies keys of hash.
201
+ Hashie.stringify_keys hash # => Returns a copy of hash with keys stringified.
202
+ ```
67
203
 
68
204
  ### MergeInitializer
69
205
 
70
- The MergeInitializer extension simply makes it possible to initialize a
71
- Hash subclass with another Hash, giving you a quick short-hand.
206
+ The MergeInitializer extension simply makes it possible to initialize a Hash subclass with another Hash, giving you a quick short-hand.
72
207
 
73
208
  ### MethodAccess
74
209
 
75
- The MethodAccess extension allows you to quickly build method-based
76
- reading, writing, and querying into your Hash descendant. It can also be
77
- included as individual modules, i.e. `Hashie::Extensions::MethodReader`,
78
- `Hashie::Extensions::MethodWriter` and `Hashie::Extensions::MethodQuery`
210
+ 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`.
79
211
 
80
212
  ```ruby
81
213
  class MyHash < Hash
@@ -88,24 +220,79 @@ h.abc # => 'def'
88
220
  h.abc? # => true
89
221
  ```
90
222
 
223
+ ### MethodAccessWithOverride
224
+
225
+ 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`.
226
+
227
+ ```ruby
228
+ class MyHash < Hash
229
+ include Hashie::Extensions::MethodAccess
230
+ end
231
+
232
+ class MyOverridingHash < Hash
233
+ include Hashie::Extensions::MethodAccessWithOverride
234
+ end
235
+
236
+ non_overriding = MyHash.new
237
+ non_overriding.zip = 'a-dee-doo-dah'
238
+ non_overriding.zip #=> [[['zip', 'a-dee-doo-dah']]]
239
+
240
+ overriding = MyOverridingHash.new
241
+ overriding.zip = 'a-dee-doo-dah'
242
+ overriding.zip #=> 'a-dee-doo-dah'
243
+ overriding.__zip #=> [[['zip', 'a-dee-doo-dah']]]
244
+ ```
245
+
246
+ ### MethodOverridingInitializer
247
+
248
+ The MethodOverridingInitializer extension will override hash methods if you pass in a normal hash to the constructor. It aliases any overridden method with two leading underscores. To include only this initializing functionality, you can include the single module `Hashie::Extensions::MethodOverridingInitializer`.
249
+
250
+ ```ruby
251
+ class MyHash < Hash
252
+ end
253
+
254
+ class MyOverridingHash < Hash
255
+ include Hashie::Extensions::MethodOverridingInitializer
256
+ end
257
+
258
+ non_overriding = MyHash.new(zip: 'a-dee-doo-dah')
259
+ non_overriding.zip #=> []
260
+
261
+ overriding = MyOverridingHash.new(zip: 'a-dee-doo-dah')
262
+ overriding.zip #=> 'a-dee-doo-dah'
263
+ overriding.__zip #=> [[['zip', 'a-dee-doo-dah']]]
264
+ ```
265
+
91
266
  ### IndifferentAccess
92
267
 
93
- This extension can be mixed in to instantly give you indifferent access
94
- to your Hash subclass. This works just like the params hash in Rails and
95
- other frameworks where whether you provide symbols or strings to access
96
- keys, you will get the same results.
268
+ This extension can be mixed in to your Hash subclass to allow you to use Strings or Symbols interchangeably as keys; similar to the `params` hash in Rails.
269
+
270
+ In addition, IndifferentAccess will also inject itself into sub-hashes so they behave the same.
271
+
272
+ Example:
273
+
274
+ ```ruby
275
+ class MyHash < Hash
276
+ include Hashie::Extensions::MergeInitializer
277
+ include Hashie::Extensions::IndifferentAccess
278
+ end
97
279
 
98
- A unique feature of Hashie's IndifferentAccess mixin is that it will
99
- inject itself recursively into subhashes *without* reinitializing the
100
- hash in question. This means you can safely merge together indifferent
101
- and non-indifferent hashes arbitrarily deeply without worrying about
102
- whether you'll be able to `hash[:other][:another]` properly.
280
+ myhash = MyHash.new(:cat => 'meow', 'dog' => 'woof')
281
+ myhash['cat'] # => "meow"
282
+ myhash[:cat] # => "meow"
283
+ myhash[:dog] # => "woof"
284
+ myhash['dog'] # => "woof"
285
+
286
+ # Auto-Injecting into sub-hashes.
287
+ myhash['fishes'] = {}
288
+ myhash['fishes'].class # => Hash
289
+ myhash['fishes'][:food] = 'flakes'
290
+ myhash['fishes']['food'] # => "flakes"
291
+ ```
103
292
 
104
293
  ### IgnoreUndeclared
105
294
 
106
- This extension can be mixed in to silently ignore undeclared properties on
107
- initialization instead of raising an error. This is useful when using a
108
- Trash to capture a subset of a larger hash.
295
+ 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.
109
296
 
110
297
  ```ruby
111
298
  class Person < Trash
@@ -129,33 +316,41 @@ p.email # => NoMethodError
129
316
 
130
317
  ### DeepMerge
131
318
 
132
- This extension allow you to easily include a recursive merging
133
- system to any Hash descendant:
319
+ This extension allows you to easily include a recursive merging
320
+ system into any Hash descendant:
134
321
 
135
322
  ```ruby
136
323
  class MyHash < Hash
137
324
  include Hashie::Extensions::DeepMerge
138
325
  end
139
326
 
140
- h1 = MyHash.new
141
- h2 = MyHash.new
327
+ h1 = MyHash[{ x: { y: [4,5,6] }, z: [7,8,9] }]
328
+ h2 = MyHash[{ x: { y: [7,8,9] }, z: "xyz" }]
329
+
330
+ h1.deep_merge(h2) # => { x: { y: [7, 8, 9] }, z: "xyz" }
331
+ h2.deep_merge(h1) # => { x: { y: [4, 5, 6] }, z: [7, 8, 9] }
332
+ ```
333
+
334
+ Like with Hash#merge in the standard library, a block can be provided to merge values:
335
+
336
+ ```ruby
337
+ class MyHash < Hash
338
+ include Hashie::Extensions::DeepMerge
339
+ end
142
340
 
143
- h1 = { x: { y: [4,5,6] }, z: [7,8,9] }
144
- h2 = { x: { y: [7,8,9] }, z: "xyz" }
341
+ h1 = MyHash[{ a: 100, b: 200, c: { c1: 100 } }]
342
+ h2 = MyHash[{ b: 250, c: { c1: 200 } }]
145
343
 
146
- h1.deep_merge(h2) #=> { x: { y: [7, 8, 9] }, z: "xyz" }
147
- h2.deep_merge(h1) #=> { x: { y: [4, 5, 6] }, z: [7, 8, 9] }
344
+ h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
345
+ # => { a: 100, b: 450, c: { c1: 300 } }
148
346
  ```
149
347
 
348
+
150
349
  ### DeepFetch
151
350
 
152
- This extension can be mixed in to provide for safe and concise retrieval of
153
- deeply nested hash values. In the event that the requested key does not exist
154
- a block can be provided and its value will be returned.
351
+ 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.
155
352
 
156
- Though this is a hash extension, it conveniently allows for arrays to be
157
- present in the nested structure. This feature makes the extension particularly
158
- useful for working with JSON API responses.
353
+ Though this is a hash extension, it conveniently allows for arrays to be present in the nested structure. This feature makes the extension particularly useful for working with JSON API responses.
159
354
 
160
355
  ```ruby
161
356
  user = {
@@ -165,24 +360,109 @@ user = {
165
360
  { name: 'Open source enthusiasts' }
166
361
  ]
167
362
  }
363
+
168
364
  user.extend Hashie::Extensions::DeepFetch
169
365
 
170
- user.deep_fetch :name, :first #=> 'Bob'
171
- user.deep_fetch :name, :middle #=> 'KeyError: Could not fetch middle'
366
+ user.deep_fetch :name, :first # => 'Bob'
367
+ user.deep_fetch :name, :middle # => 'KeyError: Could not fetch middle'
172
368
 
173
369
  # using a default block
174
- user.deep_fetch :name, :middle { |key| 'default' } #=> 'default'
370
+ user.deep_fetch(:name, :middle) { |key| 'default' } # => 'default'
175
371
 
176
372
  # a nested array
177
- user.deep_fetch :groups, 1, :name #=> 'Open source enthusiasts'
373
+ user.deep_fetch :groups, 1, :name # => 'Open source enthusiasts'
374
+ ```
375
+
376
+ ### DeepFind
377
+
378
+ This extension can be mixed in to provide for concise searching for keys within a deeply nested hash.
379
+
380
+ It can also search through any Enumerable contained within the hash for objects with the specified key.
381
+
382
+ Note: The searches are depth-first, so it is not guaranteed that a shallowly nested value will be found before a deeply nested value.
383
+
384
+ ```ruby
385
+ user = {
386
+ name: { first: 'Bob', last: 'Boberts' },
387
+ groups: [
388
+ { name: 'Rubyists' },
389
+ { name: 'Open source enthusiasts' }
390
+ ]
391
+ }
392
+
393
+ user.extend Hashie::Extensions::DeepFind
394
+
395
+ user.deep_find(:name) #=> { first: 'Bob', last: 'Boberts' }
396
+ user.deep_detect(:name) #=> { first: 'Bob', last: 'Boberts' }
397
+
398
+ user.deep_find_all(:name) #=> [{ first: 'Bob', last: 'Boberts' }, 'Rubyists', 'Open source enthusiasts']
399
+ user.deep_select(:name) #=> [{ first: 'Bob', last: 'Boberts' }, 'Rubyists', 'Open source enthusiasts']
400
+ ```
401
+
402
+ ### DeepLocate
403
+
404
+ This extension can be mixed in to provide a depth first search based search for enumerables matching a given comparator callable.
405
+
406
+ It returns all enumerables which contain at least one element, for which the given comparator returns ```true```.
407
+
408
+ 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.
409
+
410
+ ```ruby
411
+
412
+ books = [
413
+ {
414
+ title: "Ruby for beginners",
415
+ pages: 120
416
+ },
417
+ {
418
+ title: "CSS for intermediates",
419
+ pages: 80
420
+ },
421
+ {
422
+ title: "Collection of ruby books",
423
+ books: [
424
+ {
425
+ title: "Ruby for the rest of us",
426
+ pages: 576
427
+ }
428
+ ]
429
+ }
430
+ ]
431
+
432
+ books.extend(Hashie::Extensions::DeepLocate)
433
+
434
+ # for ruby 1.9 leave *no* space between the lambda rocket and the braces
435
+ # http://ruby-journal.com/becareful-with-space-in-lambda-hash-rocket-syntax-between-ruby-1-dot-9-and-2-dot-0/
436
+
437
+ books.deep_locate -> (key, value, object) { key == :title && value.include?("Ruby") }
438
+ # => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"Ruby for the rest of us", :pages=>576}]
439
+
440
+ books.deep_locate -> (key, value, object) { key == :pages && value <= 120 }
441
+ # => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"CSS for intermediates", :pages=>80}]
442
+ ```
443
+
444
+ ## StrictKeyAccess
445
+
446
+ This extension can be mixed in to allow a Hash to raise an error when attempting to extract a value using a non-existent key.
447
+
448
+ ### Example:
449
+
450
+ ```ruby
451
+ class StrictKeyAccessHash < Hash
452
+ include Hashie::Extensions::StrictKeyAccess
453
+ end
454
+
455
+ >> hash = StrictKeyAccessHash[foo: "bar"]
456
+ => {:foo=>"bar"}
457
+ >> hash[:foo]
458
+ => "bar"
459
+ >> hash[:cow]
460
+ KeyError: key not found: :cow
178
461
  ```
179
462
 
180
463
  ## Mash
181
464
 
182
- Mash is an extended Hash that gives simple pseudo-object functionality
183
- that can be built from hashes and easily extended. It is designed to
184
- be used in RESTful API libraries to provide easy object-like access
185
- to JSON and XML parsed hashes.
465
+ Mash is an extended Hash that gives simple pseudo-object functionality that can be built from hashes and easily extended. It is intended to give the user easier access to the objects within the Mash through a property-like syntax, while still retaining all Hash functionality.
186
466
 
187
467
  ### Example:
188
468
 
@@ -195,53 +475,470 @@ mash.name # => "My Mash"
195
475
  mash.name? # => true
196
476
  mash.inspect # => <Hashie::Mash name="My Mash">
197
477
 
198
- mash = Mash.new
478
+ mash = Hashie::Mash.new
199
479
  # use bang methods for multi-level assignment
200
480
  mash.author!.name = "Michael Bleigh"
201
481
  mash.author # => <Hashie::Mash name="Michael Bleigh">
202
482
 
203
- mash = Mash.new
483
+ mash = Hashie::Mash.new
204
484
  # use under-bang methods for multi-level testing
205
485
  mash.author_.name? # => false
206
486
  mash.inspect # => <Hashie::Mash>
207
487
  ```
208
488
 
209
- **Note:** The `?` method will return false if a key has been set
210
- to false or nil. In order to check if a key has been set at all, use the
211
- `mash.key?('some_key')` method instead.
489
+ **Note:** The `?` method will return false if a key has been set to false or nil. In order to check if a key has been set at all, use the `mash.key?('some_key')` method instead.
490
+
491
+ ### How does Mash handle conflicts with pre-existing methods?
492
+
493
+ Please note that a Mash will not override methods through the use of the property-like syntax. This can lead to confusion if you expect to be able to access a Mash value through the property-like syntax for a key that conflicts with a method name. However, it protects users of your library from the unexpected behavior of those methods being overridden behind the scenes.
494
+
495
+ #### Example:
496
+
497
+ ```ruby
498
+ mash = Hashie::Mash.new
499
+ mash.name = "My Mash"
500
+ mash.zip = "Method Override?"
501
+ mash.zip # => [[["name", "My Mash"]], [["zip", "Method Override?"]]]
502
+ ```
503
+
504
+ Since Mash gives you the ability to set arbitrary keys that then act as methods, Hashie logs when there is a conflict between a key and a pre-existing method. You can set the logger that this logs message to via the global Hashie logger:
505
+
506
+ ```ruby
507
+ Hashie.logger = Rails.logger
508
+ ```
509
+
510
+ You can also disable the logging in subclasses of Mash:
511
+
512
+ ```ruby
513
+ class Response < Hashie::Mash
514
+ disable_warnings
515
+ end
516
+ ```
517
+
518
+ The default is to disable logging for all methods that conflict. If you would like to only disable the logging for specific methods, you can include an array of method keys:
519
+
520
+ ```ruby
521
+ class Response < Hashie::Mash
522
+ disable_warnings :zip, :zap
523
+ end
524
+ ```
525
+
526
+ This behavior is cumulative. The examples above and below behave identically.
527
+
528
+ ```ruby
529
+ class Response < Hashie::Mash
530
+ disable_warnings :zip
531
+ disable_warnings :zap
532
+ end
533
+ ```
534
+
535
+ Disable warnings will honor the last `disable_warnings` call. Calling without parameters will override the ignored methods list, and calling with parameters will create a new ignored methods list. This includes child classes that inherit from a class that disables warnings.
536
+
537
+ ```ruby
538
+ class Message < Hashie::Mash
539
+ disable_warnings :zip, :zap
540
+ disable_warnings
541
+ end
542
+
543
+ # No errors will be logged
544
+ Message.new(merge: 'true', compact: true)
545
+ ```
546
+
547
+ ```ruby
548
+ class Message < Hashie::Mash
549
+ disable_warnings
550
+ end
551
+
552
+ class Response < Message
553
+ disable_warnings :zip, :zap
554
+ end
555
+
556
+ # 2 errors will be logged
557
+ Response.new(merge: 'true', compact: true, zip: '90210', zap: 'electric')
558
+ ```
559
+
560
+ If you would like to create an anonymous subclass of a Hashie::Mash with key conflict warnings disabled:
561
+
562
+ ```ruby
563
+ Hashie::Mash.quiet.new(zip: '90210', compact: true) # no errors logged
564
+ Hashie::Mash.quiet(:zip).new(zip: '90210', compact: true) # error logged for compact
565
+ ```
566
+
567
+ ### How does the wrapping of Mash sub-Hashes work?
568
+
569
+ Mash duplicates any sub-Hashes that you add to it and wraps them in a Mash. This allows for infinite chaining of nested Hashes within a Mash without modifying the object(s) that are passed into the Mash. When you subclass Mash, the subclass wraps any sub-Hashes in its own class. This preserves any extensions that you mixed into the Mash subclass and allows them to work within the sub-Hashes, in addition to the main containing Mash.
570
+
571
+ #### Example:
572
+
573
+ ```ruby
574
+ mash = Hashie::Mash.new(name: "Hashie", dependencies: { rake: "< 11", rspec: "~> 3.0" })
575
+ mash.dependencies.class #=> Hashie::Mash
576
+
577
+ class MyGem < Hashie::Mash; end
578
+ my_gem = MyGem.new(name: "Hashie", dependencies: { rake: "< 11", rspec: "~> 3.0" })
579
+ my_gem.dependencies.class #=> MyGem
580
+ ```
581
+
582
+ ### What else can Mash do?
583
+
584
+ Mash allows you also to transform any files into a Mash objects.
585
+
586
+ #### Example:
587
+
588
+ ```yml
589
+ #/etc/config/settings/twitter.yml
590
+ development:
591
+ api_key: 'api_key'
592
+ production:
593
+ api_key: <%= ENV['API_KEY'] %> #let's say that ENV['API_KEY'] is set to 'abcd'
594
+ ```
595
+
596
+ ```ruby
597
+ mash = Mash.load('settings/twitter.yml')
598
+ mash.development.api_key # => 'localhost'
599
+ mash.development.api_key = "foo" # => <# RuntimeError can't modify frozen ...>
600
+ mash.development.api_key? # => true
601
+ ```
602
+
603
+ You can also load with a `Pathname` object:
604
+
605
+ ```ruby
606
+ mash = Mash.load(Pathname 'settings/twitter.yml')
607
+ mash.development.api_key # => 'localhost'
608
+ ```
609
+
610
+ You can access a Mash from another class:
611
+
612
+ ```ruby
613
+ mash = Mash.load('settings/twitter.yml')[ENV['RACK_ENV']]
614
+ Twitter.extend mash.to_module # NOTE: if you want another name than settings, call: to_module('my_settings')
615
+ Twitter.settings.api_key # => 'abcd'
616
+ ```
617
+
618
+ You can use another parser (by default: [YamlErbParser](lib/hashie/extensions/parsers/yaml_erb_parser.rb)):
619
+
620
+ ```
621
+ #/etc/data/user.csv
622
+ id | name | lastname
623
+ ---|------------- | -------------
624
+ 1 |John | Doe
625
+ 2 |Laurent | Garnier
626
+ ```
627
+
628
+ ```ruby
629
+ mash = Mash.load('data/user.csv', parser: MyCustomCsvParser)
630
+ # => { 1 => { name: 'John', lastname: 'Doe'}, 2 => { name: 'Laurent', lastname: 'Garnier' } }
631
+ mash[1] #=> { name: 'John', lastname: 'Doe' }
632
+ ```
633
+
634
+ The `Mash#load` method calls `YAML.safe_load(path, [], [], true)`.
635
+
636
+ Specify `permitted_symbols`, `permitted_classes` and `aliases` options as needed.
637
+
638
+ ```ruby
639
+ Mash.load('data/user.csv', permitted_classes: [Symbol], permitted_symbols: [], aliases: false)
640
+ ```
641
+
642
+ ### Mash Extension: KeepOriginalKeys
643
+
644
+ This extension can be mixed into a Mash to keep the form of any keys passed directly into the Mash. By default, Mash converts keys to strings to give indifferent access. This extension still allows indifferent access, but keeps the form of the keys to eliminate confusion when you're not expecting the keys to change.
645
+
646
+ ```ruby
647
+ class KeepingMash < ::Hashie::Mash
648
+ include Hashie::Extensions::Mash::KeepOriginalKeys
649
+ end
650
+
651
+ mash = KeepingMash.new(:symbol_key => :symbol, 'string_key' => 'string')
652
+ mash.to_hash == { :symbol_key => :symbol, 'string_key' => 'string' } #=> true
653
+ mash.symbol_key #=> :symbol
654
+ mash[:symbol_key] #=> :symbol
655
+ mash['symbol_key'] #=> :symbol
656
+ mash.string_key #=> 'string'
657
+ mash['string_key'] #=> 'string'
658
+ mash[:string_key] #=> 'string'
659
+ ```
660
+
661
+ ### Mash Extension: PermissiveRespondTo
662
+
663
+ By default, Mash only states that it responds to built-in methods, affixed methods (e.g. setters, underbangs, etc.), and keys that it currently contains. That means it won't state that it responds to a getter for an unset key, as in the following example:
664
+
665
+ ```ruby
666
+ mash = Hashie::Mash.new(a: 1)
667
+ mash.respond_to? :b #=> false
668
+ ```
669
+
670
+ This means that by default Mash is not a perfect match for use with a SimpleDelegator since the delegator will not forward messages for unset keys to the Mash even though it can handle them.
671
+
672
+ In order to have a SimpleDelegator-compatible Mash, you can use the `PermissiveRespondTo` extension to make Mash respond to anything.
673
+
674
+ ```ruby
675
+ class PermissiveMash < Hashie::Mash
676
+ include Hashie::Extensions::Mash::PermissiveRespondTo
677
+ end
678
+
679
+ mash = PermissiveMash.new(a: 1)
680
+ mash.respond_to? :b #=> true
681
+ ```
682
+
683
+ This comes at the cost of approximately 20% performance for initialization and setters and 19KB of permanent memory growth for each such class that you create.
684
+
685
+ ### Mash Extension: SafeAssignment
686
+
687
+ This extension can be mixed into a Mash to guard the attempted overwriting of methods by property setters. When mixed in, the Mash will raise an `ArgumentError` if you attempt to write a property with the same name as an existing method.
688
+
689
+ #### Example:
690
+
691
+ ```ruby
692
+ class SafeMash < ::Hashie::Mash
693
+ include Hashie::Extensions::Mash::SafeAssignment
694
+ end
695
+
696
+ safe_mash = SafeMash.new
697
+ safe_mash.zip = 'Test' # => ArgumentError
698
+ safe_mash[:zip] = 'test' # => still ArgumentError
699
+ ```
700
+
701
+ ### Mash Extension: SymbolizeKeys
702
+
703
+ This extension can be mixed into a Mash to change the default behavior of converting keys to strings. After mixing this extension into a Mash, the Mash will convert all keys to symbols. It can be useful to use with keywords argument, which required symbol keys.
704
+
705
+ ```ruby
706
+ class SymbolizedMash < ::Hashie::Mash
707
+ include Hashie::Extensions::Mash::SymbolizeKeys
708
+ end
709
+
710
+ symbol_mash = SymbolizedMash.new
711
+ symbol_mash['test'] = 'value'
712
+ symbol_mash.test #=> 'value'
713
+ symbol_mash.to_h #=> {test: 'value'}
714
+
715
+ def example(test:)
716
+ puts test
717
+ end
718
+
719
+ example(symbol_mash) #=> value
720
+ ```
721
+
722
+ There is a major benefit and coupled with a major trade-off to this decision (at least on older Rubies). As a benefit, by using symbols as keys, you will be able to use the implicit conversion of a Mash via the `#to_hash` method to destructure (or splat) the contents of a Mash out to a block. This can be handy for doing iterations through the Mash's keys and values, as follows:
723
+
724
+ ```ruby
725
+ symbol_mash = SymbolizedMash.new(id: 123, name: 'Rey')
726
+ symbol_mash.each do |key, value|
727
+ # key is :id, then :name
728
+ # value is 123, then 'Rey'
729
+ end
730
+ ```
731
+
732
+ However, on Rubies less than 2.0, this means that every key you send to the Mash will generate a symbol. Since symbols are not garbage-collected on older versions of Ruby, this can cause a slow memory leak when using a symbolized Mash with data generated from user input.
733
+
734
+ ### Mash Extension: DefineAccessors
735
+
736
+ This extension can be mixed into a Mash so it makes it behave like `OpenStruct`. It reduces the overhead of `method_missing?` magic by lazily defining field accessors when they're requested.
737
+
738
+ ```ruby
739
+ class MyHash < ::Hashie::Mash
740
+ include Hashie::Extensions::Mash::DefineAccessors
741
+ end
742
+
743
+ mash = MyHash.new
744
+ MyHash.method_defined?(:foo=) #=> false
745
+ mash.foo = 123
746
+ MyHash.method_defined?(:foo=) #=> true
747
+
748
+ MyHash.method_defined?(:foo) #=> false
749
+ mash.foo #=> 123
750
+ MyHash.method_defined?(:foo) #=> true
751
+ ```
752
+
753
+ You can also extend the existing mash without defining a class:
754
+
755
+ ```ruby
756
+ mash = ::Hashie::Mash.new.with_accessors!
757
+ ```
212
758
 
213
759
  ## Dash
214
760
 
215
- Dash is an extended Hash that has a discrete set of defined properties
216
- and only those properties may be set on the hash. Additionally, you
217
- can set defaults for each property. You can also flag a property as
218
- required. Required properties will raise an exception if unset.
761
+ Dash is an extended Hash that has a discrete set of defined properties and only those properties may be set on the hash. Additionally, you can set defaults for each property. You can also flag a property as required. Required properties will raise an exception if unset. Another option is message for required properties, which allow you to add custom messages for required property.
762
+
763
+ 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.
219
764
 
220
765
  ### Example:
221
766
 
222
767
  ```ruby
223
768
  class Person < Hashie::Dash
224
769
  property :name, required: true
770
+ property :age, required: true, message: 'must be set.'
225
771
  property :email
772
+ property :phone, required: -> { email.nil? }, message: 'is required if email is not set.'
773
+ property :pants, required: :weekday?, message: 'are only required on weekdays.'
226
774
  property :occupation, default: 'Rubyist'
775
+
776
+ def weekday?
777
+ [ Time.now.saturday?, Time.now.sunday? ].none?
778
+ end
227
779
  end
228
780
 
229
781
  p = Person.new # => ArgumentError: The property 'name' is required for this Dash.
782
+ p = Person.new(name: 'Bob') # => ArgumentError: The property 'age' must be set.
230
783
 
231
- p = Person.new(name: "Bob")
232
- p.name # => 'Bob'
233
- p.name = nil # => ArgumentError: The property 'name' is required for this Dash.
784
+ p = Person.new(name: "Bob", age: 18)
785
+ p.name # => 'Bob'
786
+ p.name = nil # => ArgumentError: The property 'name' is required for this Dash.
787
+ p.age # => 18
788
+ p.age = nil # => ArgumentError: The property 'age' must be set.
234
789
  p.email = 'abc@def.com'
235
790
  p.occupation # => 'Rubyist'
236
791
  p.email # => 'abc@def.com'
237
792
  p[:awesome] # => NoMethodError
238
793
  p[:occupation] # => 'Rubyist'
794
+ p.update_attributes!(name: 'Trudy', occupation: 'Evil')
795
+ p.occupation # => 'Evil'
796
+ p.name # => 'Trudy'
797
+ p.update_attributes!(occupation: nil)
798
+ p.occupation # => 'Rubyist'
799
+ ```
800
+
801
+ Properties defined as symbols are not the same thing as properties defined as strings.
802
+
803
+ ### Example:
804
+
805
+ ```ruby
806
+ class Tricky < Hashie::Dash
807
+ property :trick
808
+ property 'trick'
809
+ end
810
+
811
+ p = Tricky.new(trick: 'one', 'trick' => 'two')
812
+ p.trick # => 'one', always symbol version
813
+ p[:trick] # => 'one'
814
+ p['trick'] # => 'two'
815
+ ```
816
+
817
+ Note that accessing a property as a method always uses the symbol version.
818
+
819
+ ```ruby
820
+ class Tricky < Hashie::Dash
821
+ property 'trick'
822
+ end
823
+
824
+ p = Tricky.new('trick' => 'two')
825
+ p.trick # => NoMethodError
826
+ ```
827
+
828
+ ### Potential gotchas
829
+
830
+ Because Dashes are subclasses of the built-in Ruby Hash class, the double-splat operator takes the Dash as-is without any conversion. This can lead to strange behavior when you use the double-splat operator on a Dash as the first part of a keyword list or built Hash. For example:
831
+
832
+ ```ruby
833
+ class Foo < Hashie::Dash
834
+ property :bar
835
+ end
836
+
837
+ foo = Foo.new(bar: 'baz') #=> {:bar=>"baz"}
838
+ qux = { **foo, quux: 'corge' } #=> {:bar=> "baz", :quux=>"corge"}
839
+ qux.is_a?(Foo) #=> true
840
+ qux[:quux]
841
+ #=> raise NoMethodError, "The property 'quux' is not defined for Foo."
842
+ qux.key?(:quux) #=> true
843
+ ```
844
+
845
+ You can work around this problem in two ways:
846
+
847
+ 1. Call `#to_h` on the resulting object to convert it into a Hash.
848
+ 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.
849
+
850
+ ```ruby
851
+ qux = { **foo, quux: 'corge' }.to_h #=> {:bar=> "baz", :quux=>"corge"}
852
+ qux.is_a?(Hash) #=> true
853
+ qux[:quux] #=> "corge"
854
+
855
+ qux = { quux: 'corge', **foo } #=> {:quux=>"corge", :bar=> "baz"}
856
+ qux.is_a?(Hash) #=> true
857
+ qux[:quux] #=> "corge"
858
+ ```
859
+
860
+ ### Dash Extension: PropertyTranslation
861
+
862
+ The `Hashie::Extensions::Dash::PropertyTranslation` mixin extends a Dash with
863
+ the ability to remap keys from a source hash.
864
+
865
+ ### Example from inconsistent APIs
866
+
867
+ Property translation is useful when you need to read data from another
868
+ application -- such as a Java API -- where the keys are named differently from
869
+ Ruby conventions.
870
+
871
+ ```ruby
872
+ class PersonHash < Hashie::Dash
873
+ include Hashie::Extensions::Dash::PropertyTranslation
874
+
875
+ property :first_name, from: :firstName
876
+ property :last_name, from: :lastName
877
+ property :first_name, from: :f_name
878
+ property :last_name, from: :l_name
879
+ end
880
+
881
+ person = PersonHash.new(firstName: 'Michael', l_name: 'Bleigh')
882
+ person[:first_name] #=> 'Michael'
883
+ person[:last_name] #=> 'Bleigh
884
+ ```
885
+
886
+ ### Example using translation lambdas
887
+
888
+ You can also use a lambda to translate the value. This is particularly useful
889
+ when you want to ensure the type of data you're wrapping.
890
+
891
+ ```ruby
892
+ class DataModelHash < Hashie::Dash
893
+ include Hashie::Extensions::Dash::PropertyTranslation
894
+
895
+ property :id, transform_with: ->(value) { value.to_i }
896
+ property :created_at, from: :created, with: ->(value) { Time.parse(value) }
897
+ end
898
+
899
+ model = DataModelHash.new(id: '123', created: '2014-04-25 22:35:28')
900
+ model.id.class #=> Integer (Fixnum if you are using Ruby 2.3 or lower)
901
+ model.created_at.class #=> Time
902
+ ```
903
+
904
+ ### Mash and Rails 4 Strong Parameters
905
+
906
+ To enable compatibility with Rails 4 use the [hashie-forbidden_attributes](https://github.com/Maxim-Filimonov/hashie-forbidden_attributes) gem.
907
+
908
+ ### Dash Extension: Coercion.
909
+
910
+ If you want to use `Hashie::Extensions::Coercion` together with `Dash` then
911
+ you may probably want to use `Hashie::Extensions::Dash::Coercion` instead.
912
+ This extension automatically includes `Hashie::Extensions::Coercion`
913
+ and also adds a convenient `:coerce` option to `property` so you can define coercion in one line
914
+ instead of using `property` and `coerce_key` separate:
915
+
916
+ ```ruby
917
+ class UserHash < Hashie::Dash
918
+ include Hashie::Extensions::Coercion
919
+
920
+ property :id
921
+ property :posts
922
+
923
+ coerce_key :posts, Array[PostHash]
924
+ end
925
+ ```
926
+
927
+ This is the same as:
928
+
929
+ ```ruby
930
+ class UserHash < Hashie::Dash
931
+ include Hashie::Extensions::Dash::Coercion
932
+
933
+ property :id
934
+ property :posts, coerce: Array[PostHash]
935
+ end
239
936
  ```
240
937
 
241
938
  ## Trash
242
939
 
243
- A Trash is a Dash that allows you to translate keys on initialization.
244
- It is used like so:
940
+ A Trash is a Dash that allows you to translate keys on initialization. It mixes
941
+ in the PropertyTranslation mixin by default and is used like so:
245
942
 
246
943
  ```ruby
247
944
  class Person < Hashie::Trash
@@ -256,8 +953,7 @@ when it is initialized using a hash such as through:
256
953
  Person.new(firstName: 'Bob')
257
954
  ```
258
955
 
259
- Trash also supports translations using lambda, this could be useful when dealing with
260
- external API's. You can use it in this way:
956
+ Trash also supports translations using lambda, this could be useful when dealing with external API's. You can use it in this way:
261
957
 
262
958
  ```ruby
263
959
  class Result < Hashie::Trash
@@ -270,19 +966,15 @@ this will produce the following
270
966
 
271
967
  ```ruby
272
968
  result = Result.new(id: '123', creation_date: '2012-03-30 17:23:28')
273
- result.id.class # => Fixnum
969
+ result.id.class # => Integer (Fixnum if you are using Ruby 2.3 or lower)
274
970
  result.created_at.class # => Time
275
971
  ```
276
972
 
277
973
  ## Clash
278
974
 
279
- Clash is a Chainable Lazy Hash that allows you to easily construct
280
- complex hashes using method notation chaining. This will allow you
281
- to use a more action-oriented approach to building options hashes.
975
+ 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.
282
976
 
283
- Essentially, a Clash is a generalized way to provide much of the same
284
- kind of "chainability" that libraries like Arel or Rails 2.x's named_scopes
285
- provide.
977
+ Essentially, a Clash is a generalized way to provide much of the same kind of "chainability" that libraries like Arel or Rails 2.x's named_scopes provide.
286
978
 
287
979
  ### Example:
288
980
 
@@ -317,22 +1009,21 @@ If the Rash's value is a `proc`, the `proc` will be automatically called with th
317
1009
 
318
1010
  # Mapping names to appropriate greetings
319
1011
  greeting = Hashie::Rash.new( /^Mr./ => "Hello sir!", /^Mrs./ => "Evening, madame." )
320
- greeting["Mr. Steve Austin"] #=> "Hello sir!"
321
- greeting["Mrs. Steve Austin"] #=> "Evening, madame."
1012
+ greeting["Mr. Steve Austin"] # => "Hello sir!"
1013
+ greeting["Mrs. Steve Austin"] # => "Evening, madame."
322
1014
 
323
1015
  # Mapping statements to saucy retorts
324
1016
  mapper = Hashie::Rash.new(
325
1017
  /I like (.+)/ => proc { |m| "Who DOESN'T like #{m[1]}?!" },
326
1018
  /Get off my (.+)!/ => proc { |m| "Forget your #{m[1]}, old man!" }
327
1019
  )
328
- mapper["I like traffic lights"] #=> "Who DOESN'T like traffic lights?!"
329
- mapper["Get off my lawn!"] #=> "Forget your lawn, old man!"
1020
+ mapper["I like traffic lights"] # => "Who DOESN'T like traffic lights?!"
1021
+ mapper["Get off my lawn!"] # => "Forget your lawn, old man!"
330
1022
  ```
331
1023
 
332
1024
  ### Auto-optimized
333
1025
 
334
- **Note:** The Rash is automatically optimized every 500 accesses
335
- (which means that it sorts the list of Regexps, putting the most frequently matched ones at the beginning).
1026
+ **Note:** The Rash is automatically optimized every 500 accesses (which means that it sorts the list of Regexps, putting the most frequently matched ones at the beginning).
336
1027
 
337
1028
  If this value is too low or too high for your needs, you can tune it by setting: `rash.optimize_every = n`.
338
1029
 
@@ -342,6 +1033,6 @@ See [CONTRIBUTING.md](CONTRIBUTING.md)
342
1033
 
343
1034
  ## Copyright
344
1035
 
345
- Copyright (c) 2009-2014 Intridea, Inc. (http://intridea.com/) and [contributors](https://github.com/intridea/hashie/graphs/contributors).
1036
+ Copyright (c) 2009-2014 Intridea, Inc. (http://intridea.com/) and [contributors](https://github.com/hashie/hashie/graphs/contributors).
346
1037
 
347
1038
  MIT License. See [LICENSE](LICENSE) for details.