hashie 3.4.1 → 3.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: baf45b7b9cc1092c508f33cb545eb5abeb80fbb1
4
- data.tar.gz: a4ec3c9ca8c979fdaf484ecdcf3afc61c7f024ed
3
+ metadata.gz: dbee5492036900b505f6bf76d5133640e02b525b
4
+ data.tar.gz: 5a358bc6a11697e33e7d2c806d39b01d79cb3dba
5
5
  SHA512:
6
- metadata.gz: 0fccd3accf920a79b1f0085cf12a3d6c6b972338b153c296af295f5462fa56a15e18b5a0bebd950861a26fac4160e60b830ca66c15edcfcd1a7717b426c8c4c3
7
- data.tar.gz: a68c79809154aeab6320943e7239a441ca62f328936e774b5b4219c9a68764481f4251a91586a4d88764ed6b23b41870d58dc1ca33b52003bff559aa0f319965
6
+ metadata.gz: 660342313eec115f629d20bc0d68fa1f1ee6361860234dbf2a90f707fe92d2c609bb03533748d3fbb0ea8dbab4197f332281c2c70fdad87fb0a34485e0b2ae56
7
+ data.tar.gz: ee46052bf7249f60c4c23825f782f19b879582f8cb009a156644b3c89f7aba1a76a19f3f01f235b4915e00e5b773e2559a32bde349ae7d541d2bc0c6f19c4163
@@ -1,11 +1,16 @@
1
- ## 3.4.1
1
+ ## 3.4.2 (6/2/2015)
2
+
3
+ * [#292](https://github.com/intridea/hashie/pull/292): Removed `Mash#id` and `Mash#type` - [@jrochkind](https://github.com/jrochkind).
4
+ * [#297](https://github.com/intridea/hashie/pull/297): Extracted `Trash`'s behavior into a new `Dash::PropertyTranslation` extension - [@michaelherold](https://github.com/michaelherold).
5
+
6
+ ## 3.4.1 (3/31/2015)
2
7
 
3
8
  * [#269](https://github.com/intridea/hashie/pull/272): Added Hashie::Extensions::DeepLocate - [@msievers](https://github.com/msievers).
4
9
  * [#270](https://github.com/intridea/hashie/pull/277): Fixed ArgumentError raised when using IndifferentAccess and HashWithIndifferentAccess - [@gardenofwine](https://github.com/gardenofwine).
5
10
  * [#281](https://github.com/intridea/hashie/pull/281): Added #reverse_merge to Mash to override ActiveSupport's version - [@mgold](https://github.com/mgold).
6
11
  * [#282](https://github.com/intridea/hashie/pull/282): Fixed coercions in a subclass accumulating in the superclass - [@maxlinc](https://github.com/maxlinc), [@martinstreicher](https://github.com/martinstreicher).
7
12
 
8
- ## 3.4.0 (02/02/2015)
13
+ ## 3.4.0 (2/02/2015)
9
14
 
10
15
  * [#271](https://github.com/intridea/hashie/pull/271): Added ability to define defaults based on current hash - [@gregory](https://github.com/gregory).
11
16
  * [#247](https://github.com/intridea/hashie/pull/247): Fixed #stringify_keys and #symbolize_keys collision with ActiveSupport - [@bartoszkopinski](https://github.com/bartoszkopinski).
@@ -72,6 +77,10 @@
72
77
  * [#148](https://github.com/intridea/hashie/pull/148): Consolidated Hashie::Hash#stringify_keys implementation - [@dblock](https://github.com/dblock).
73
78
  * [#149](https://github.com/intridea/hashie/issues/149): Allow IgnoreUndeclared and DeepMerge to be used with undeclared properties - [@jhaesus](https://github.com/jhaesus).
74
79
 
80
+ ## 2.1.2 (5/12/2014)
81
+
82
+ * [#169](https://github.com/intridea/hashie/pull/169): Hash#to_hash will also convert nested objects that implement `to_hash` - [@gregory](https://github.com/gregory).
83
+
75
84
  ## 2.1.1 (4/12/2014)
76
85
 
77
86
  * [#144](https://github.com/intridea/hashie/issues/144): Fixed regression invoking `to_hash` with no parameters - [@mbleigh](https://github.com/mbleigh).
@@ -81,7 +90,7 @@
81
90
  * [#134](https://github.com/intridea/hashie/pull/134): Add deep_fetch extension for nested access - [@tylerdooling](https://github.com/tylerdooling).
82
91
  * Removed support for Ruby 1.8.7 - [@dblock](https://github.com/dblock).
83
92
  * Ruby style now enforced with Rubocop - [@dblock](https://github.com/dblock).
84
- * [#138](https://github.com/intridea/hashie/pull/138): Added Hashie#Rash, a hash whose keys can be regular expressions or ranges - [@epitron](https://github.com/epitron).
93
+ * [#138](https://github.com/intridea/hashie/pull/138): Added Hashie::Rash, a hash whose keys can be regular expressions or ranges - [@epitron](https://github.com/epitron).
85
94
  * [#131](https://github.com/intridea/hashie/pull/131): Added IgnoreUndeclared, an extension to silently ignore undeclared properties at intialization - [@righi](https://github.com/righi).
86
95
  * [#136](https://github.com/intridea/hashie/issues/136): Removed Hashie::Extensions::Structure - [@markiz](https://github.com/markiz).
87
96
  * [#107](https://github.com/intridea/hashie/pull/107): Fixed excessive value conversions, poor performance of deep merge in Hashie::Mash - [@davemitchell](https://github.com/dblock), [@dblock](https://github.com/dblock).
data/README.md CHANGED
@@ -20,7 +20,7 @@ $ gem install hashie
20
20
 
21
21
  ## Upgrading
22
22
 
23
- You're reading the documentation for the next release of Hashie, which should be 3.3.2. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version. The current stable release is [3.3.1](https://github.com/intridea/hashie/blob/v3.3.1/README.md).
23
+ You're reading the documentation for the stable release of Hashie, which is 3.4.2. Please read [UPGRADING](UPGRADING.md) when upgrading from a previous version.
24
24
 
25
25
  ## Hash Extensions
26
26
 
@@ -145,6 +145,41 @@ class Tweet < Hash
145
145
  end
146
146
  ```
147
147
 
148
+ #### A note on circular coercion
149
+
150
+ Since `coerce_key` is a class-level method, you cannot have circular coercion without the use of a proc. For example:
151
+
152
+ ```ruby
153
+ class CategoryHash < Hash
154
+ include Hashie::Extensions::Coercion
155
+ include Hashie::Extensions::MergeInitializer
156
+
157
+ coerce_key :products, Array[ProductHash]
158
+ end
159
+
160
+ class ProductHash < Hash
161
+ include Hashie::Extensions::Coercion
162
+ include Hashie::Extensions::MergeInitializer
163
+
164
+ coerce_key :categories, Array[CategoriesHash]
165
+ end
166
+ ```
167
+
168
+ This will fail with a `NameError` for `CategoryHash::ProductHash` because `ProductHash` is not defined at the point that `coerce_key` is happening for `CategoryHash`.
169
+
170
+ To work around this, you can use a coercion proc. For example, you could do:
171
+
172
+ ```ruby
173
+ class CategoryHash < Hash
174
+ # ...
175
+ coerce_key :products, ->(value) do
176
+ return value.map { |v| ProductHash.new(v) } if value.respond_to?(:map)
177
+
178
+ ProductHash.new(value)
179
+ end
180
+ end
181
+ ```
182
+
148
183
  ### KeyConversion
149
184
 
150
185
  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`.
@@ -154,7 +189,7 @@ Hashie also has a utility method for converting keys on a Hash without a mixin:
154
189
  ```ruby
155
190
  Hashie.symbolize_keys! hash # => Symbolizes keys of hash.
156
191
  Hashie.symbolize_keys hash # => Returns a copy of hash with keys symbolized.
157
- Hashie.stringify_keys hash # => Stringifies keys of hash.
192
+ Hashie.stringify_keys! hash # => Stringifies keys of hash.
158
193
  Hashie.stringify_keys hash # => Returns a copy of hash with keys stringified.
159
194
  ```
160
195
 
@@ -524,13 +559,58 @@ p = Tricky.new('trick' => 'two')
524
559
  p.trick # => NoMethodError
525
560
  ```
526
561
 
562
+ ### Dash Extension: PropertyTranslation
563
+
564
+ The `Hashie::Extensions::Dash::PropertyTranslation` mixin extends a Dash with
565
+ the ability to remap keys from a source hash.
566
+
567
+ ### Example from inconsistent APIs
568
+
569
+ Property translation is useful when you need to read data from another
570
+ application -- such as a Java API -- where the keys are named differently from
571
+ Ruby conventions.
572
+
573
+ ```ruby
574
+ class PersonHash < Hashie::Dash
575
+ include Hashie::Extensions::Dash::PropertyTranslation
576
+
577
+ property :first_name, from: :firstName
578
+ property :last_name, from: :lastName
579
+ property :first_name, from: :f_name
580
+ property :last_name, from: :l_name
581
+ end
582
+
583
+ person = PersonHash.new(firstName: 'Michael', l_name: 'Bleigh')
584
+ person[:first_name] #=> 'Michael'
585
+ person[:last_name] #=> 'Bleigh
586
+ ```
587
+
588
+ ### Example using translation lambdas
589
+
590
+ You can also use a lambda to translate the value. This is particularly useful
591
+ when you want to ensure the type of data you're wrapping.
592
+
593
+ ```ruby
594
+ class DataModelHash < Hashie::Dash
595
+ include Hashie::Extensions::Dash::PropertyTranslation
596
+
597
+ property :id, transform_with: ->(value) { value.to_i }
598
+ property :created_at, from: :created, with: ->(value) { Time.parse(value) }
599
+ end
600
+
601
+ model = DataModelHash.new(id: '123', created: '2014-04-25 22:35:28')
602
+ model.id.class #=> Fixnum
603
+ model.created_at.class #=> Time
604
+ ```
605
+
527
606
  ### Mash and Rails 4 Strong Parameters
528
607
 
529
- To enable compatibility with Rails 4 use the [hashie_rails](http://rubygems.org/gems/hashie_rails) gem.
608
+ To enable compatibility with Rails 4 use the [hashie-forbidden_attributes](https://github.com/Maxim-Filimonov/hashie-forbidden_attributes) gem.
530
609
 
531
610
  ## Trash
532
611
 
533
- A Trash is a Dash that allows you to translate keys on initialization. It is used like so:
612
+ A Trash is a Dash that allows you to translate keys on initialization. It mixes
613
+ in the PropertyTranslation mixin by default and is used like so:
534
614
 
535
615
  ```ruby
536
616
  class Person < Hashie::Trash
@@ -33,6 +33,7 @@ module Hashie
33
33
 
34
34
  module Dash
35
35
  autoload :IndifferentAccess, 'hashie/extensions/dash/indifferent_access'
36
+ autoload :PropertyTranslation, 'hashie/extensions/dash/property_translation'
36
37
  end
37
38
 
38
39
  module Mash
@@ -0,0 +1,167 @@
1
+ module Hashie
2
+ module Extensions
3
+ module Dash
4
+ # Extends a Dash with the ability to remap keys from a source hash.
5
+ #
6
+ # Property translation is useful when you need to read data from another
7
+ # application -- such as a Java API -- where the keys are named
8
+ # differently from Ruby conventions.
9
+ #
10
+ # == Example from inconsistent APIs
11
+ #
12
+ # class PersonHash < Hashie::Dash
13
+ # include Hashie::Extensions::Dash::PropertyTranslation
14
+ #
15
+ # property :first_name, from :firstName
16
+ # property :last_name, from: :lastName
17
+ # property :first_name, from: :f_name
18
+ # property :last_name, from: :l_name
19
+ # end
20
+ #
21
+ # person = PersonHash.new(firstName: 'Michael', l_name: 'Bleigh')
22
+ # person[:first_name] #=> 'Michael'
23
+ # person[:last_name] #=> 'Bleigh'
24
+ #
25
+ # You can also use a lambda to translate the value. This is particularly
26
+ # useful when you want to ensure the type of data you're wrapping.
27
+ #
28
+ # == Example using translation lambdas
29
+ #
30
+ # class DataModelHash < Hashie::Dash
31
+ # include Hashie::Extensions::Dash::PropertyTranslation
32
+ #
33
+ # property :id, transform_with: ->(value) { value.to_i }
34
+ # property :created_at, from: :created, with: ->(value) { Time.parse(value) }
35
+ # end
36
+ #
37
+ # model = DataModelHash.new(id: '123', created: '2014-04-25 22:35:28')
38
+ # model.id.class #=> Fixnum
39
+ # model.created_at.class #=> Time
40
+ module PropertyTranslation
41
+ def self.included(base)
42
+ base.instance_variable_set(:@transforms, {})
43
+ base.instance_variable_set(:@translations_hash, {})
44
+ base.extend(ClassMethods)
45
+ base.send(:include, InstanceMethods)
46
+ end
47
+
48
+ module ClassMethods
49
+ attr_reader :transforms, :translations_hash
50
+
51
+ # Ensures that any inheriting classes maintain their translations.
52
+ #
53
+ # * <tt>:default</tt> - The class inheriting the translations.
54
+ def inherited(klass)
55
+ super
56
+ klass.instance_variable_set(:@transforms, transforms.dup)
57
+ klass.instance_variable_set(:@translations_hash, translations_hash.dup)
58
+ end
59
+
60
+ def permitted_input_keys
61
+ @permitted_input_keys ||= properties.map { |property| inverse_translations.fetch property, property }
62
+ end
63
+
64
+ # Defines a property on the Trash. Options are as follows:
65
+ #
66
+ # * <tt>:default</tt> - Specify a default value for this property, to be
67
+ # returned before a value is set on the property in a new Dash.
68
+ # * <tt>:from</tt> - Specify the original key name that will be write only.
69
+ # * <tt>:with</tt> - Specify a lambda to be used to convert value.
70
+ # * <tt>:transform_with</tt> - Specify a lambda to be used to convert value
71
+ # without using the :from option. It transform the property itself.
72
+ def property(property_name, options = {})
73
+ super
74
+
75
+ options[:from] = options[:from] if options[:from]
76
+
77
+ if options[:from]
78
+ if property_name == options[:from]
79
+ fail ArgumentError, "Property name (#{property_name}) and :from option must not be the same"
80
+ end
81
+
82
+ translations_hash[options[:from]] ||= {}
83
+ translations_hash[options[:from]][property_name] = options[:with] || options[:transform_with]
84
+
85
+ define_method "#{options[:from]}=" do |val|
86
+ self.class.translations_hash[options[:from]].each do |name, with|
87
+ self[name] = with.respond_to?(:call) ? with.call(val) : val
88
+ end
89
+ end
90
+ else
91
+ if options[:transform_with].respond_to? :call
92
+ transforms[property_name] = options[:transform_with]
93
+ end
94
+ end
95
+ end
96
+
97
+ def transformed_property(property_name, value)
98
+ transforms[property_name].call(value)
99
+ end
100
+
101
+ def transformation_exists?(name)
102
+ transforms.key? name
103
+ end
104
+
105
+ def translation_exists?(name)
106
+ translations_hash.key? name
107
+ end
108
+
109
+ def translations
110
+ @translations ||= {}.tap do |h|
111
+ translations_hash.each do |(property_name, property_translations)|
112
+ if property_translations.size > 1
113
+ h[property_name] = property_translations.keys
114
+ else
115
+ h[property_name] = property_translations.keys.first
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ def inverse_translations
122
+ @inverse_translations ||= {}.tap do |h|
123
+ translations_hash.each do |(property_name, property_translations)|
124
+ property_translations.keys.each do |k|
125
+ h[k] = property_name
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ module InstanceMethods
133
+ # Sets a value on the Dash in a Hash-like way.
134
+ #
135
+ # Note: Only works on pre-existing properties.
136
+ def []=(property, value)
137
+ if self.class.translation_exists? property
138
+ send("#{property}=", value)
139
+ elsif self.class.transformation_exists? property
140
+ super property, self.class.transformed_property(property, value)
141
+ elsif property_exists? property
142
+ super
143
+ end
144
+ end
145
+
146
+ # Deletes any keys that have a translation
147
+ def initialize_attributes(attributes)
148
+ return unless attributes
149
+ attributes_copy = attributes.dup.delete_if do |k, v|
150
+ if self.class.translations_hash.include?(k)
151
+ self[k] = v
152
+ true
153
+ end
154
+ end
155
+ super attributes_copy
156
+ end
157
+
158
+ # Raises an NoMethodError if the property doesn't exist
159
+ def property_exists?(property)
160
+ fail_no_property_error!(property) unless self.class.property?(property)
161
+ true
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -5,7 +5,16 @@ module Hashie
5
5
  # a key and returns the first occurrence of the key.
6
6
  #
7
7
  # options = {user: {location: {address: '123 Street'}}}
8
+ # options.extend(Hashie::Extensions::DeepFind)
8
9
  # options.deep_find(:address) # => '123 Street'
10
+ #
11
+ # class MyHash < Hash
12
+ # include Hashie::Extensions::DeepFind
13
+ # end
14
+ #
15
+ # my_hash = MyHash.new
16
+ # my_hash[:user] = {location: {address: '123 Street'}}
17
+ # my_hash.deep_find(:address) # => '123 Street'
9
18
  def deep_find(key)
10
19
  _deep_find(key)
11
20
  end
@@ -16,7 +25,16 @@ module Hashie
16
25
  # a key and returns all occurrences of the key.
17
26
  #
18
27
  # options = {users: [{location: {address: '123 Street'}}, {location: {address: '234 Street'}}]}
28
+ # options.extend(Hashie::Extensions::DeepFind)
19
29
  # options.deep_find_all(:address) # => ['123 Street', '234 Street']
30
+ #
31
+ # class MyHash < Hash
32
+ # include Hashie::Extensions::DeepFind
33
+ # end
34
+ #
35
+ # my_hash = MyHash.new
36
+ # my_hash[:users] = [{location: {address: '123 Street'}}, {location: {address: '234 Street'}}]
37
+ # my_hash.deep_find_all(:address) # => ['123 Street', '234 Street']
20
38
  def deep_find_all(key)
21
39
  matches = _deep_find_all(key)
22
40
  matches.empty? ? nil : matches
@@ -92,14 +92,6 @@ module Hashie
92
92
 
93
93
  class << self; alias_method :[], :new; end
94
94
 
95
- def id #:nodoc:
96
- self['id']
97
- end
98
-
99
- def type #:nodoc:
100
- self['type']
101
- end
102
-
103
95
  alias_method :regular_reader, :[]
104
96
  alias_method :regular_writer, :[]=
105
97
 
@@ -1,4 +1,5 @@
1
1
  require 'hashie/dash'
2
+ require 'hashie/extensions/dash/property_translation'
2
3
 
3
4
  module Hashie
4
5
  # A Trash is a 'translated' Dash where the keys can be remapped from a source
@@ -8,122 +9,6 @@ module Hashie
8
9
  # such as a Java api, where the keys are named differently from how we would
9
10
  # in Ruby.
10
11
  class Trash < Dash
11
- # Defines a property on the Trash. Options are as follows:
12
- #
13
- # * <tt>:default</tt> - Specify a default value for this property, to be
14
- # returned before a value is set on the property in a new Dash.
15
- # * <tt>:from</tt> - Specify the original key name that will be write only.
16
- # * <tt>:with</tt> - Specify a lambda to be used to convert value.
17
- # * <tt>:transform_with</tt> - Specify a lambda to be used to convert value
18
- # without using the :from option. It transform the property itself.
19
- def self.property(property_name, options = {})
20
- super
21
-
22
- options[:from] = options[:from] if options[:from]
23
-
24
- if options[:from]
25
- if property_name == options[:from]
26
- fail ArgumentError, "Property name (#{property_name}) and :from option must not be the same"
27
- end
28
-
29
- translations_hash[options[:from]] ||= {}
30
- translations_hash[options[:from]][property_name] = options[:with] || options[:transform_with]
31
-
32
- define_method "#{options[:from]}=" do |val|
33
- self.class.translations_hash[options[:from]].each do |name, with|
34
- self[name] = with.respond_to?(:call) ? with.call(val) : val
35
- end
36
- end
37
- else
38
- if options[:transform_with].respond_to? :call
39
- transforms[property_name] = options[:transform_with]
40
- end
41
- end
42
- end
43
-
44
- class << self
45
- attr_reader :transforms, :translations_hash
46
- end
47
- instance_variable_set('@transforms', {})
48
- instance_variable_set('@translations_hash', {})
49
-
50
- def self.inherited(klass)
51
- super
52
- klass.instance_variable_set('@transforms', transforms.dup)
53
- klass.instance_variable_set('@translations_hash', translations_hash.dup)
54
- end
55
-
56
- # Set a value on the Dash in a Hash-like way. Only works
57
- # on pre-existing properties.
58
- def []=(property, value)
59
- if self.class.translation_exists? property
60
- send("#{property}=", value)
61
- elsif self.class.transformation_exists? property
62
- super property, self.class.transformed_property(property, value)
63
- elsif property_exists? property
64
- super
65
- end
66
- end
67
-
68
- def self.transformed_property(property_name, value)
69
- transforms[property_name].call(value)
70
- end
71
-
72
- def self.translation_exists?(name)
73
- translations_hash.key? name
74
- end
75
-
76
- def self.transformation_exists?(name)
77
- transforms.key? name
78
- end
79
-
80
- def self.permitted_input_keys
81
- @permitted_input_keys ||= properties.map { |property| inverse_translations.fetch property, property }
82
- end
83
-
84
- private
85
-
86
- def self.translations
87
- @translations ||= begin
88
- h = {}
89
- translations_hash.each do |(property_name, property_translations)|
90
- h[property_name] = property_translations.size > 1 ? property_translations.keys : property_translations.keys.first
91
- end
92
- h
93
- end
94
- end
95
-
96
- def self.inverse_translations
97
- @inverse_translations ||= begin
98
- h = {}
99
- translations_hash.each do |(property_name, property_translations)|
100
- property_translations.keys.each do |k|
101
- h[k] = property_name
102
- end
103
- end
104
- h
105
- end
106
- end
107
-
108
- # Raises an NoMethodError if the property doesn't exist
109
- #
110
- def property_exists?(property)
111
- fail_no_property_error!(property) unless self.class.property?(property)
112
- true
113
- end
114
-
115
- private
116
-
117
- # Deletes any keys that have a translation
118
- def initialize_attributes(attributes)
119
- return unless attributes
120
- attributes_copy = attributes.dup.delete_if do |k, v|
121
- if self.class.translations_hash.include?(k)
122
- self[k] = v
123
- true
124
- end
125
- end
126
- super attributes_copy
127
- end
12
+ include Hashie::Extensions::Dash::PropertyTranslation
128
13
  end
129
14
  end
@@ -1,3 +1,3 @@
1
1
  module Hashie
2
- VERSION = '3.4.1'
2
+ VERSION = '3.4.2'
3
3
  end
@@ -437,6 +437,67 @@ describe Hashie::Extensions::Coercion do
437
437
  expect(MyOwnBase.key_coercions).to eq({})
438
438
  end
439
439
  end
440
+
441
+ context 'when using circular coercion' do
442
+ context 'with a proc on one side' do
443
+ class CategoryHash < Hash
444
+ include Hashie::Extensions::Coercion
445
+ include Hashie::Extensions::MergeInitializer
446
+
447
+ coerce_key :products, lambda { |value|
448
+ return value.map { |v| ProductHash.new(v) } if value.respond_to?(:map)
449
+
450
+ ProductHash.new(v)
451
+ }
452
+ end
453
+
454
+ class ProductHash < Hash
455
+ include Hashie::Extensions::Coercion
456
+ include Hashie::Extensions::MergeInitializer
457
+
458
+ coerce_key :categories, Array[CategoryHash]
459
+ end
460
+
461
+ let(:category) { CategoryHash.new(type: 'rubygem', products: [Hashie::Mash.new(name: 'Hashie')]) }
462
+ let(:product) { ProductHash.new(name: 'Hashie', categories: [Hashie::Mash.new(type: 'rubygem')]) }
463
+
464
+ it 'coerces CategoryHash[:products] correctly' do
465
+ expected = [ProductHash]
466
+ actual = category[:products].map(&:class)
467
+
468
+ expect(actual).to eq(expected)
469
+ end
470
+
471
+ it 'coerces ProductHash[:categories] correctly' do
472
+ expected = [CategoryHash]
473
+ actual = product[:categories].map(&:class)
474
+
475
+ expect(actual).to eq(expected)
476
+ end
477
+ end
478
+
479
+ context 'without a proc on either side' do
480
+ it 'fails with a NameError since the other class is not defined yet' do
481
+ attempted_code = lambda do
482
+ class AnotherCategoryHash < Hash
483
+ include Hashie::Extensions::Coercion
484
+ include Hashie::Extensions::MergeInitializer
485
+
486
+ coerce_key :products, Array[AnotherProductHash]
487
+ end
488
+
489
+ class AnotherProductHash < Hash
490
+ include Hashie::Extensions::Coercion
491
+ include Hashie::Extensions::MergeInitializer
492
+
493
+ coerce_key :categories, Array[AnotherCategoryHash]
494
+ end
495
+ end
496
+
497
+ expect { attempted_code.call }.to raise_error(NameError)
498
+ end
499
+ end
500
+ end
440
501
  end
441
502
 
442
503
  describe '#coerce_value' do
@@ -1,5 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'delegate'
3
+ require 'support/ruby_version'
3
4
 
4
5
  describe Hashie::Mash do
5
6
  subject { Hashie::Mash.new }
@@ -346,6 +347,11 @@ describe Hashie::Mash do
346
347
  it 'responds to a set key with a suffix' do
347
348
  %w(= ? ! _).each do |suffix|
348
349
  expect(subject).to be_respond_to(:"abc#{suffix}")
350
+ end
351
+ end
352
+
353
+ it 'is able to access the suffixed key as a method' do
354
+ %w(= ? ! _).each do |suffix|
349
355
  expect(subject.method(:"abc#{suffix}")).to_not be_nil
350
356
  end
351
357
  end
@@ -353,6 +359,16 @@ describe Hashie::Mash do
353
359
  it 'responds to an unknown key with a suffix' do
354
360
  %w(= ? ! _).each do |suffix|
355
361
  expect(subject).to be_respond_to(:"xyz#{suffix}")
362
+ end
363
+ end
364
+
365
+ it 'is able to access an unknown suffixed key as a method' do
366
+ # See https://github.com/intridea/hashie/pull/285 for more information
367
+ if mri22?
368
+ pending 'Bug in MRI 2.2.x means this behavior is broken in those versions'
369
+ end
370
+
371
+ %w(= ? ! _).each do |suffix|
356
372
  expect(subject.method(:"xyz#{suffix}")).to_not be_nil
357
373
  end
358
374
  end
@@ -0,0 +1,10 @@
1
+ def mri22?
2
+ ruby_version.start_with?('ruby_2.2')
3
+ end
4
+
5
+ def ruby_version
6
+ interpreter = Object.const_defined?(:RUBY_ENGINE) && RUBY_ENGINE
7
+ version = Object.const_defined?(:RUBY_VERSION) && RUBY_VERSION
8
+
9
+ "#{interpreter}_#{version}"
10
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hashie
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.4.1
4
+ version: 3.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-03-31 00:00:00.000000000 Z
12
+ date: 2015-06-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -60,6 +60,7 @@ files:
60
60
  - lib/hashie/dash.rb
61
61
  - lib/hashie/extensions/coercion.rb
62
62
  - lib/hashie/extensions/dash/indifferent_access.rb
63
+ - lib/hashie/extensions/dash/property_translation.rb
63
64
  - lib/hashie/extensions/deep_fetch.rb
64
65
  - lib/hashie/extensions/deep_find.rb
65
66
  - lib/hashie/extensions/deep_locate.rb
@@ -105,6 +106,7 @@ files:
105
106
  - spec/hashie/version_spec.rb
106
107
  - spec/spec_helper.rb
107
108
  - spec/support/module_context.rb
109
+ - spec/support/ruby_version.rb
108
110
  homepage: https://github.com/intridea/hashie
109
111
  licenses:
110
112
  - MIT
@@ -125,34 +127,35 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
127
  version: '0'
126
128
  requirements: []
127
129
  rubyforge_project:
128
- rubygems_version: 2.2.2
130
+ rubygems_version: 2.4.5
129
131
  signing_key:
130
132
  specification_version: 4
131
133
  summary: Your friendly neighborhood hash library.
132
134
  test_files:
133
- - spec/hashie/clash_spec.rb
135
+ - spec/spec_helper.rb
136
+ - spec/support/ruby_version.rb
137
+ - spec/support/module_context.rb
138
+ - spec/hashie/parsers/yaml_erb_parser_spec.rb
139
+ - spec/hashie/version_spec.rb
140
+ - spec/hashie/rash_spec.rb
134
141
  - spec/hashie/dash_spec.rb
135
- - spec/hashie/extensions/autoload_spec.rb
136
- - spec/hashie/extensions/coercion_spec.rb
137
- - spec/hashie/extensions/dash/indifferent_access_spec.rb
142
+ - spec/hashie/hash_spec.rb
143
+ - spec/hashie/clash_spec.rb
144
+ - spec/hashie/mash_spec.rb
145
+ - spec/hashie/trash_spec.rb
138
146
  - spec/hashie/extensions/deep_fetch_spec.rb
139
- - spec/hashie/extensions/deep_find_spec.rb
147
+ - spec/hashie/extensions/method_access_spec.rb
148
+ - spec/hashie/extensions/key_conversion_spec.rb
140
149
  - spec/hashie/extensions/deep_locate_spec.rb
141
150
  - spec/hashie/extensions/deep_merge_spec.rb
142
- - spec/hashie/extensions/ignore_undeclared_spec.rb
143
- - spec/hashie/extensions/indifferent_access_spec.rb
144
151
  - spec/hashie/extensions/indifferent_access_with_rails_hwia_spec.rb
145
- - spec/hashie/extensions/key_conversion_spec.rb
152
+ - spec/hashie/extensions/symbolize_keys_spec.rb
146
153
  - spec/hashie/extensions/mash/safe_assignment_spec.rb
147
- - spec/hashie/extensions/merge_initializer_spec.rb
148
- - spec/hashie/extensions/method_access_spec.rb
154
+ - spec/hashie/extensions/deep_find_spec.rb
155
+ - spec/hashie/extensions/ignore_undeclared_spec.rb
156
+ - spec/hashie/extensions/autoload_spec.rb
149
157
  - spec/hashie/extensions/stringify_keys_spec.rb
150
- - spec/hashie/extensions/symbolize_keys_spec.rb
151
- - spec/hashie/hash_spec.rb
152
- - spec/hashie/mash_spec.rb
153
- - spec/hashie/parsers/yaml_erb_parser_spec.rb
154
- - spec/hashie/rash_spec.rb
155
- - spec/hashie/trash_spec.rb
156
- - spec/hashie/version_spec.rb
157
- - spec/spec_helper.rb
158
- - spec/support/module_context.rb
158
+ - spec/hashie/extensions/indifferent_access_spec.rb
159
+ - spec/hashie/extensions/coercion_spec.rb
160
+ - spec/hashie/extensions/merge_initializer_spec.rb
161
+ - spec/hashie/extensions/dash/indifferent_access_spec.rb