hashie 3.3.2 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ac95f1ad6bc1d4b34deb7193b35586f0023e9b9e
4
- data.tar.gz: 3fde5748adb0c1a40db2409e6bc11b559fa9f739
3
+ metadata.gz: ea2ab0de9a0b73bb6f212ed67075c27c287bd4dc
4
+ data.tar.gz: 25fd352d7cf79e9b45aca7f00c1ee399d50b42a8
5
5
  SHA512:
6
- metadata.gz: 2e50bba8ebb421f26da2efdb38d7d64692d2cccfc37e8daa40c118f31e3830f0419577257e85a920f96eec3992a185fcc3da8d072f5b52efe298fa483a0dbe82
7
- data.tar.gz: 60f9d4137f83832a2d68a791d6d1a73690018c5180406b9b35b118eb84387b7292e517218cc4ddd933238a98db88a55dd6540b36ca7eab5cd692678da1def0fb
6
+ metadata.gz: e2c88e593d475002121b7f5129b03f3675d23f09d06d2b828595f4e7c2e67b08a1d8ebcaf37e15539744e53f83ff932e25e45239c0375e82098608f57cbbe663
7
+ data.tar.gz: 5531b32d1b26fee4d63a41db2b96ceabc53821e783af229131fc78f3746362577c690997c3789b7f682ca42cde6c0280efe22f48145f2c63f9969e81ca48ab2f
@@ -1,3 +1,23 @@
1
+ ## Next Release
2
+
3
+ * Your contribution here
4
+
5
+ ## 3.4.0 (02/02/2014)
6
+
7
+ * [#271](https://github.com/intridea/hashie/pull/271): Added ability to define defaults based on current hash - [@gregory](https://github.com/gregory).
8
+ * [#247](https://github.com/intridea/hashie/pull/247): Fixed #stringify_keys and #symbolize_keys collision with ActiveSupport - [@bartoszkopinski](https://github.com/bartoszkopinski).
9
+ * [#249](https://github.com/intridea/hashie/pull/249): SafeAssignment will now also protect hash-style assignments - [@jrochkind](https://github.com/jrochkind).
10
+ * [#251](https://github.com/intridea/hashie/pull/251): Added block support to indifferent access #fetch - [@jgraichen](https://github.com/jgraichen).
11
+ * [#252](https://github.com/intridia/hashie/pull/252): Added support for conditionally required Hashie::Dash attributes - [@ccashwell](https://github.com/ccashwell).
12
+ * [#256](https://github.com/intridia/hashie/pull/256): Inherit key coercions - [@Erol](https://github.com/Erol).
13
+ * [#259](https://github.com/intridia/hashie/pull/259): Fixed handling of default proc values in Mash - [@Erol](https://github.com/Erol).
14
+ * [#260](https://github.com/intridia/hashie/pull/260): Added block support to Extensions::DeepMerge - [@galathius](https://github.com/galathius).
15
+ * [#254](https://github.com/intridea/hashie/pull/254): Added public utility methods for stringify and symbolize keys - [@maxlinc](https://github.com/maxlinc).
16
+ * [#261](https://github.com/intridea/hashie/pull/261): Fixed bug where Dash.property modifies argument object - [@d_tw](https://github.com/d_tw).
17
+ * [#264](https://github.com/intridea/hashie/pull/264): Methods such as abc? return true/false with Hashie::Extensions::MethodReader - [@Zloy](https://github.com/Zloy).
18
+ * [#269](https://github.com/intridea/hashie/pull/269): Add #extractable_options? so ActiveSupport Array#extract_options! can extract it - [@ridiculous](https://github.com/ridiculous).
19
+ * Your contribution here.
20
+
1
21
  ## 3.3.2 (11/26/2014)
2
22
 
3
23
  * [#233](https://github.com/intridea/hashie/pull/233): Custom error messages for required properties in Hashie::Dash subclasses - [@joss](https://github.com/joss).
@@ -14,7 +34,7 @@
14
34
  * [#201](https://github.com/intridea/hashie/pull/201): Hashie::Trash transforms can be inherited - [@fobocaster](https://github.com/fobocaster).
15
35
  * [#189](https://github.com/intridea/hashie/pull/189): Added Rash#fetch - [@medcat](https://github.com/medcat).
16
36
  * [#200](https://github.com/intridea/hashie/pull/200): Improved coercion: primitives and error handling - [@maxlinc](https://github.com/maxlinc).
17
- * [#204](https://github.com/intridea/hashie/pull/204): Added Hashie::Extensions::MethodOverridingWriter and Hashie::Extensions::MethodAccessWithOverride - [@michaelherold](https://github.com/michaelherold).
37
+ * [#204](https://github.com/intridea/hashie/pull/204): Added Hashie::Extensions::MethodOverridingWriter and MethodAccessWithOverride - [@michaelherold](https://github.com/michaelherold).
18
38
  * [#205](http://github.com/intridea/hashie/pull/205): Added Hashie::Extensions::Mash::SafeAssignment - [@michaelherold](https://github.com/michaelherold).
19
39
  * [#206](http://github.com/intridea/hashie/pull/206): Fixed stack overflow from repetitively including coercion in subclasses - [@michaelherold](https://github.com/michaelherold).
20
40
  * [#207](http://github.com/intridea/hashie/pull/207): Fixed inheritance of transformations in Trash - [@fobocaster](https://github.com/fobocaster).
data/README.md CHANGED
@@ -1,4 +1,10 @@
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) [![Code Climate](https://codeclimate.com/github/intridea/hashie.png)](https://codeclimate.com/github/intridea/hashie)
1
+ # Hashie
2
+
3
+ [![Gem Version](http://img.shields.io/gem/v/hashie.svg)](http://badge.fury.io/rb/hashie)
4
+ [![Build Status](http://img.shields.io/travis/intridea/hashie.svg)](https://travis-ci.org/intridea/hashie)
5
+ [![Dependency Status](https://gemnasium.com/intridea/hashie.svg)](https://gemnasium.com/intridea/hashie)
6
+ [![Code Climate](https://codeclimate.com/github/intridea/hashie.svg)](https://codeclimate.com/github/intridea/hashie)
7
+ [![Coverage Status](https://codeclimate.com/github/intridea/hashie/badges/coverage.svg)](https://codeclimate.com/github/intridea/hashie)
2
8
 
3
9
  Hashie is a growing collection of tools that extend Hashes and make them more useful.
4
10
 
@@ -141,6 +147,15 @@ end
141
147
 
142
148
  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`.
143
149
 
150
+ Hashie also has a utility method for converting keys on a Hash without a mixin:
151
+
152
+ ```ruby
153
+ Hashie.symbolize_keys! hash # => Symbolizes keys of hash.
154
+ Hashie.symbolize_keys hash # => Returns a copy of hash with keys symbolized.
155
+ Hashie.stringify_keys hash # => Stringifies keys of hash.
156
+ Hashie.stringify_keys hash # => Returns a copy of hash with keys stringified.
157
+ ```
158
+
144
159
  ### MergeInitializer
145
160
 
146
161
  The MergeInitializer extension simply makes it possible to initialize a Hash subclass with another Hash, giving you a quick short-hand.
@@ -177,7 +192,7 @@ non_overriding = MyHash.new
177
192
  non_overriding.zip = 'a-dee-doo-dah'
178
193
  non_overriding.zip #=> [[['zip', 'a-dee-doo-dah']]]
179
194
 
180
- overriding = MyHash.new
195
+ overriding = MyOverridingHash.new
181
196
  overriding.zip = 'a-dee-doo-dah'
182
197
  overriding.zip #=> 'a-dee-doo-dah'
183
198
  overriding.__zip #=> [[['zip', 'a-dee-doo-dah']]]
@@ -230,6 +245,21 @@ h1.deep_merge(h2) # => { x: { y: [7, 8, 9] }, z: "xyz" }
230
245
  h2.deep_merge(h1) # => { x: { y: [4, 5, 6] }, z: [7, 8, 9] }
231
246
  ```
232
247
 
248
+ Like with Hash#merge in the standard library, a block can be provided to merge values:
249
+
250
+ ```ruby
251
+ class MyHash < Hash
252
+ include Hashie::Extensions::DeepMerge
253
+ end
254
+
255
+ h1 = MyHash[{ a: 100, b: 200, c: { c1: 100 } }]
256
+ h2 = MyHash[{ b: 250, c: { c1: 200 } }]
257
+
258
+ h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
259
+ # => { a: 100, b: 450, c: { c1: 300 } }
260
+ ```
261
+
262
+
233
263
  ### DeepFetch
234
264
 
235
265
  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.
@@ -377,13 +407,16 @@ class SafeMash < ::Hashie::Mash
377
407
  end
378
408
 
379
409
  safe_mash = SafeMash.new
380
- safe_mash.zip = 'Test' # => ArgumentError
410
+ safe_mash.zip = 'Test' # => ArgumentError
411
+ safe_mash[:zip] = 'test' # => still ArgumentError
381
412
  ```
382
413
 
383
414
  ## Dash
384
415
 
385
416
  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.
386
417
 
418
+ 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.
419
+
387
420
  ### Example:
388
421
 
389
422
  ```ruby
@@ -391,7 +424,13 @@ class Person < Hashie::Dash
391
424
  property :name, required: true
392
425
  property :age, required: true, message: 'must be set.'
393
426
  property :email
427
+ property :phone, required: -> { email.nil? }, message: 'is required if email is not set.'
428
+ property :pants, required: :weekday?, message: 'are only required on weekdays.'
394
429
  property :occupation, default: 'Rubyist'
430
+
431
+ def weekday?
432
+ [ Time.now.saturday?, Time.now.sunday? ].none?
433
+ end
395
434
  end
396
435
 
397
436
  p = Person.new # => ArgumentError: The property 'name' is required for this Dash.
@@ -1,6 +1,30 @@
1
1
  Upgrading Hashie
2
2
  ================
3
3
 
4
+ ### Upgrading to 3.2.2
5
+
6
+ #### Testing if key defined
7
+
8
+ In versions <= 3.2.1 Hash object being questioned doesn't return a boolean value as it's mentioned in README.md
9
+
10
+ ```ruby
11
+ class MyHash < Hash
12
+ include Hashie::Extensions::MethodAccess
13
+ end
14
+
15
+ h = MyHash.new
16
+ h.abc = 'def'
17
+ h.abc # => 'def'
18
+ h.abc? # => 'def'
19
+ ```
20
+
21
+ In versions >= 3.2.2 it returns a boolean value
22
+
23
+ ```ruby
24
+ h.abc? # => true
25
+ h.abb? # => false
26
+ ```
27
+
4
28
  ### Upgrading to 3.2.1
5
29
 
6
30
  #### Possible coercion changes
@@ -38,4 +38,9 @@ module Hashie
38
38
  autoload :SafeAssignment, 'hashie/extensions/mash/safe_assignment'
39
39
  end
40
40
  end
41
+
42
+ class << self
43
+ include Hashie::Extensions::StringifyKeys::ClassMethods
44
+ include Hashie::Extensions::SymbolizeKeys::ClassMethods
45
+ end
41
46
  end
@@ -26,7 +26,11 @@ module Hashie
26
26
  #
27
27
  # * <tt>:required</tt> - Specify the value as required for this
28
28
  # property, to raise an error if a value is unset in a new or
29
- # existing Dash.
29
+ # existing Dash. If a Proc is provided, it will be run in the
30
+ # context of the Dash instance. If a Symbol is provided, the
31
+ # property it represents must not be nil. The property is only
32
+ # required if the value is truthy.
33
+ #
30
34
  # * <tt>:message</tt> - Specify custom error message for required property
31
35
  #
32
36
  def self.property(property_name, options = {})
@@ -40,7 +44,7 @@ module Hashie
40
44
 
41
45
  unless instance_methods.map(&:to_s).include?("#{property_name}=")
42
46
  define_method(property_name) { |&block| self.[](property_name, &block) }
43
- property_assignment = property_name.to_s.concat('=').to_sym
47
+ property_assignment = "#{property_name}=".to_sym
44
48
  define_method(property_assignment) { |value| self.[]=(property_name, value) }
45
49
  end
46
50
 
@@ -48,8 +52,10 @@ module Hashie
48
52
  @subclasses.each { |klass| klass.property(property_name, options) }
49
53
  end
50
54
 
51
- if options.delete(:required)
52
- required_properties[property_name] = options.delete(:message) || "is required for #{name}."
55
+ condition = options.delete(:required)
56
+ if condition
57
+ message = options.delete(:message) || "is required for #{name}."
58
+ required_properties[property_name] = { condition: condition, message: message }
53
59
  else
54
60
  fail ArgumentError, 'The :message option should be used with :required option.' if options.key?(:message)
55
61
  end
@@ -90,7 +96,8 @@ module Hashie
90
96
 
91
97
  self.class.defaults.each_pair do |prop, value|
92
98
  self[prop] = begin
93
- value.dup
99
+ val = value.dup
100
+ val.is_a?(Proc) && val.arity > 0 ? val.call(self) : val
94
101
  rescue TypeError
95
102
  value
96
103
  end
@@ -180,19 +187,30 @@ module Hashie
180
187
  end
181
188
 
182
189
  def assert_property_set!(property)
183
- fail_property_required_error!(property) if send(property).nil?
190
+ fail_property_required_error!(property) if send(property).nil? && required?(property)
184
191
  end
185
192
 
186
193
  def assert_property_required!(property, value)
187
- fail_property_required_error!(property) if self.class.required?(property) && value.nil?
194
+ fail_property_required_error!(property) if value.nil? && required?(property)
188
195
  end
189
196
 
190
197
  def fail_property_required_error!(property)
191
- fail ArgumentError, "The property '#{property}' #{self.class.required_properties[property]}"
198
+ fail ArgumentError, "The property '#{property}' #{self.class.required_properties[property][:message]}"
192
199
  end
193
200
 
194
201
  def fail_no_property_error!(property)
195
202
  fail NoMethodError, "The property '#{property}' is not defined for #{self.class.name}."
196
203
  end
204
+
205
+ def required?(property)
206
+ return false unless self.class.required?(property)
207
+
208
+ condition = self.class.required_properties[property][:condition]
209
+ case condition
210
+ when Proc then !!(instance_exec(&condition))
211
+ when Symbol then !!(send(condition))
212
+ else !!(condition)
213
+ end
214
+ end
197
215
  end
198
216
  end
@@ -86,6 +86,9 @@ module Hashie
86
86
  end
87
87
 
88
88
  module ClassMethods
89
+ attr_writer :key_coercions
90
+ protected :key_coercions=
91
+
89
92
  # Set up a coercion rule such that any time the specified
90
93
  # key is set it will be coerced into the specified class.
91
94
  # Coercion will occur by first attempting to call Class.coerce
@@ -101,16 +104,15 @@ module Hashie
101
104
  # coerce_key :user, User
102
105
  # end
103
106
  def coerce_key(*attrs)
104
- @key_coercions ||= {}
105
107
  into = attrs.pop
106
- attrs.each { |key| @key_coercions[key] = into }
108
+ attrs.each { |key| key_coercions[key] = into }
107
109
  end
108
110
 
109
111
  alias_method :coerce_keys, :coerce_key
110
112
 
111
113
  # Returns a hash of any existing key coercions.
112
114
  def key_coercions
113
- @key_coercions || {}
115
+ @key_coercions ||= {}
114
116
  end
115
117
 
116
118
  # Returns the specific key coercion for the specified key,
@@ -149,10 +151,10 @@ module Hashie
149
151
  end
150
152
 
151
153
  if options[:strict]
152
- (@strict_value_coercions ||= {})[from] = into
154
+ strict_value_coercions[from] = into
153
155
  else
154
156
  while from.superclass && from.superclass != Object
155
- (@lenient_value_coercions ||= {})[from] = into
157
+ lenient_value_coercions[from] = into
156
158
  from = from.superclass
157
159
  end
158
160
  end
@@ -160,11 +162,11 @@ module Hashie
160
162
 
161
163
  # Return all value coercions that have the :strict rule as true.
162
164
  def strict_value_coercions
163
- @strict_value_coercions || {}
165
+ @strict_value_coercions ||= {}
164
166
  end
165
167
  # Return all value coercions that have the :strict rule as false.
166
168
  def lenient_value_coercions
167
- @value_coercions || {}
169
+ @lenient_value_coercions ||= {}
168
170
  end
169
171
 
170
172
  # Fetch the value coercion, if any, for the specified object.
@@ -172,6 +174,12 @@ module Hashie
172
174
  from = value.class
173
175
  strict_value_coercions[from] || lenient_value_coercions[from]
174
176
  end
177
+
178
+ def inherited(klass)
179
+ super
180
+
181
+ klass.key_coercions = key_coercions
182
+ end
175
183
  end
176
184
  end
177
185
  end
@@ -2,28 +2,33 @@ module Hashie
2
2
  module Extensions
3
3
  module DeepMerge
4
4
  # Returns a new hash with +self+ and +other_hash+ merged recursively.
5
- def deep_merge(other_hash)
6
- dup.deep_merge!(other_hash)
5
+ def deep_merge(other_hash, &block)
6
+ dup.deep_merge!(other_hash, &block)
7
7
  end
8
8
 
9
9
  # Returns a new hash with +self+ and +other_hash+ merged recursively.
10
10
  # Modifies the receiver in place.
11
- def deep_merge!(other_hash)
12
- _recursive_merge(self, other_hash)
11
+ def deep_merge!(other_hash, &block)
12
+ return self unless other_hash.is_a?(::Hash)
13
+ _recursive_merge(self, other_hash, &block)
13
14
  self
14
15
  end
15
16
 
16
17
  private
17
18
 
18
- def _recursive_merge(hash, other_hash)
19
- if other_hash.is_a?(::Hash) && hash.is_a?(::Hash)
20
- other_hash.each do |k, v|
21
- hash[k] = hash.key?(k) ? _recursive_merge(hash[k], v) : v
22
- end
23
- hash
24
- else
25
- other_hash
19
+ def _recursive_merge(hash, other_hash, &block)
20
+ other_hash.each do |k, v|
21
+ hash[k] = if hash.key?(k) && hash[k].is_a?(::Hash) && v.is_a?(::Hash)
22
+ _recursive_merge(hash[k], v, &block)
23
+ else
24
+ if hash.key?(k) && block_given?
25
+ block.call(k, hash[k], v)
26
+ else
27
+ v
28
+ end
29
+ end
26
30
  end
31
+ hash
27
32
  end
28
33
  end
29
34
  end
@@ -107,8 +107,8 @@ module Hashie
107
107
  regular_writer convert_key(key), convert_value(value)
108
108
  end
109
109
 
110
- def indifferent_fetch(key, *args)
111
- regular_fetch convert_key(key), *args
110
+ def indifferent_fetch(key, *args, &block)
111
+ regular_fetch convert_key(key), *args, &block
112
112
  end
113
113
 
114
114
  def indifferent_delete(key)
@@ -2,10 +2,13 @@ module Hashie
2
2
  module Extensions
3
3
  module Mash
4
4
  module SafeAssignment
5
- def assign_property(name, value)
6
- fail ArgumentError, "The property #{name} clashes with an existing method." if methods.include?(name.to_sym)
5
+ def custom_writer(key, *args) #:nodoc:
6
+ fail ArgumentError, "The property #{key} clashes with an existing method." if methods.include?(key.to_sym)
7
+ super
8
+ end
7
9
 
8
- self[name] = value
10
+ def []=(*args)
11
+ custom_writer(*args)
9
12
  end
10
13
  end
11
14
  end
@@ -33,9 +33,19 @@ module Hashie
33
33
  end
34
34
 
35
35
  def method_missing(name, *args)
36
- return self[name.to_s] if key?(name.to_s)
37
- return self[name.to_sym] if key?(name.to_sym)
38
- super
36
+ if key?(name)
37
+ self[name]
38
+ else
39
+ sname = name.to_s
40
+ if key?(sname)
41
+ self[sname]
42
+ elsif sname[-1] == '?'
43
+ kname = sname[0..-2]
44
+ key?(kname) || key?(kname.to_sym)
45
+ else
46
+ super
47
+ end
48
+ end
39
49
  end
40
50
  end
41
51
 
@@ -8,10 +8,7 @@ module Hashie
8
8
  # test.stringify_keys!
9
9
  # test # => {'abc' => 'def'}
10
10
  def stringify_keys!
11
- keys.each do |k|
12
- stringify_keys_recursively!(self[k])
13
- self[k.to_s] = delete(k)
14
- end
11
+ StringifyKeys.stringify_keys!(self)
15
12
  self
16
13
  end
17
14
 
@@ -21,30 +18,51 @@ module Hashie
21
18
  dup.stringify_keys!
22
19
  end
23
20
 
24
- protected
21
+ module ClassMethods
22
+ # Stringify all keys recursively within nested
23
+ # hashes and arrays.
24
+ # @api private
25
+ def stringify_keys_recursively!(object)
26
+ case object
27
+ when self.class
28
+ object.stringify_keys!
29
+ when ::Array
30
+ object.each do |i|
31
+ stringify_keys_recursively!(i)
32
+ end
33
+ when ::Hash
34
+ stringify_keys!(object)
35
+ end
36
+ end
25
37
 
26
- # Stringify all keys recursively within nested
27
- # hashes and arrays.
28
- def stringify_keys_recursively!(object)
29
- if self.class === object
30
- object.stringify_keys!
31
- elsif ::Array === object
32
- object.each do |i|
33
- stringify_keys_recursively!(i)
38
+ # Convert all keys in the hash to strings.
39
+ #
40
+ # @param [::Hash] hash
41
+ # @example
42
+ # test = {:abc => 'def'}
43
+ # test.stringify_keys!
44
+ # test # => {'abc' => 'def'}
45
+ def stringify_keys!(hash)
46
+ hash.keys.each do |k|
47
+ stringify_keys_recursively!(hash[k])
48
+ hash[k.to_s] = hash.delete(k)
34
49
  end
35
- object
36
- elsif object.respond_to?(:stringify_keys!)
37
- object.stringify_keys!
38
- elsif ::Hash === object
39
- object.keys.each do |k|
40
- stringify_keys_recursively!(object[k])
41
- object[k.to_s] = object.delete(k)
50
+ hash
51
+ end
52
+
53
+ # Return a copy of hash with all keys converted
54
+ # to strings.
55
+ # @param [::Hash] hash
56
+ def stringify_keys(hash)
57
+ hash.dup.tap do | new_hash |
58
+ stringify_keys! new_hash
42
59
  end
43
- object
44
- else
45
- object
46
60
  end
47
61
  end
62
+
63
+ class << self
64
+ include ClassMethods
65
+ end
48
66
  end
49
67
  end
50
68
  end