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 +4 -4
- data/CHANGELOG.md +12 -3
- data/README.md +84 -4
- data/lib/hashie.rb +1 -0
- data/lib/hashie/extensions/dash/property_translation.rb +167 -0
- data/lib/hashie/extensions/deep_find.rb +18 -0
- data/lib/hashie/mash.rb +0 -8
- data/lib/hashie/trash.rb +2 -117
- data/lib/hashie/version.rb +1 -1
- data/spec/hashie/extensions/coercion_spec.rb +61 -0
- data/spec/hashie/mash_spec.rb +16 -0
- data/spec/support/ruby_version.rb +10 -0
- metadata +25 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dbee5492036900b505f6bf76d5133640e02b525b
|
4
|
+
data.tar.gz: 5a358bc6a11697e33e7d2c806d39b01d79cb3dba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 660342313eec115f629d20bc0d68fa1f1ee6361860234dbf2a90f707fe92d2c609bb03533748d3fbb0ea8dbab4197f332281c2c70fdad87fb0a34485e0b2ae56
|
7
|
+
data.tar.gz: ee46052bf7249f60c4c23825f782f19b879582f8cb009a156644b3c89f7aba1a76a19f3f01f235b4915e00e5b773e2559a32bde349ae7d541d2bc0c6f19c4163
|
data/CHANGELOG.md
CHANGED
@@ -1,11 +1,16 @@
|
|
1
|
-
## 3.4.
|
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 (
|
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
|
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
|
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 [
|
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
|
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
|
data/lib/hashie.rb
CHANGED
@@ -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
|
data/lib/hashie/mash.rb
CHANGED
data/lib/hashie/trash.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/hashie/version.rb
CHANGED
@@ -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
|
data/spec/hashie/mash_spec.rb
CHANGED
@@ -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
|
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.
|
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-
|
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.
|
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/
|
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/
|
136
|
-
- spec/hashie/
|
137
|
-
- spec/hashie/
|
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/
|
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/
|
152
|
+
- spec/hashie/extensions/symbolize_keys_spec.rb
|
146
153
|
- spec/hashie/extensions/mash/safe_assignment_spec.rb
|
147
|
-
- spec/hashie/extensions/
|
148
|
-
- spec/hashie/extensions/
|
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/
|
151
|
-
- spec/hashie/
|
152
|
-
- spec/hashie/
|
153
|
-
- spec/hashie/
|
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
|