hashie 3.4.1 → 3.4.2

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: 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