Added support for coercing enumerables and collections
This commit is contained in:
parent
4ab9a3361a
commit
f09c5f68f9
|
@ -1,5 +1,6 @@
|
|||
## Next Release
|
||||
|
||||
* [#177](https://github.com/intridea/hashie/pull/177): Added support for coercing enumerables and collections - [@gregory](https://github.com/gregory).
|
||||
* [#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).
|
||||
* [#171](https://github.com/intridea/hashie/pull/171): Include Trash and Dash class name when raising `NoMethodError` - [@gregory](https://github.com/gregory).
|
||||
* [#172](https://github.com/intridea/hashie/pull/172): Added Dash and Trash#update_attributes! - [@gregory](https://github.com/gregory).
|
||||
|
|
44
README.md
44
README.md
|
@ -52,6 +52,50 @@ class SpecialHash < Hash
|
|||
end
|
||||
```
|
||||
|
||||
### Coercing Collections
|
||||
|
||||
```ruby
|
||||
class Tweet < Hash
|
||||
include Hashie::Extensions::Coercion
|
||||
coerce_key :mentions, Array[User]
|
||||
coerce_key :friends, Set[User]
|
||||
end
|
||||
|
||||
user_hash = { name: "Bob" }
|
||||
mentions_hash= [user_hash, user_hash]
|
||||
friends_hash = [user_hash]
|
||||
tweet = Tweet.new(mentions: mentions_hash, friends: friends_hash)
|
||||
# => automatically calls User.coerce(user_hash) or
|
||||
# User.new(user_hash) if that isn't present on each element of the array
|
||||
|
||||
tweet.mentions.map(&:class) # => [User, User]
|
||||
tweet.friends.class # => Set
|
||||
```
|
||||
|
||||
### Hash attributes Coercion
|
||||
|
||||
```ruby
|
||||
class Relation
|
||||
def initialize(string)
|
||||
@relation = string
|
||||
end
|
||||
end
|
||||
|
||||
class Tweet < Hash
|
||||
include Hashie::Extensions::Coercion
|
||||
coerce_key :relations, Hash[User => Relation]
|
||||
end
|
||||
|
||||
user_hash = { name: "Bob" }
|
||||
relations_hash= { user_hash => "father", user_hash => "friend" }
|
||||
tweet = Tweet.new(relations: relations_hash)
|
||||
tweet.relations.map { |k,v| [k.class, v.class] } # => [[User, Relation], [User, Relation]]
|
||||
tweet.relations.class # => Hash
|
||||
|
||||
# => automatically calls User.coerce(user_hash) on each key
|
||||
# and Relation.new on each value since Relation doesn't define the `coerce` class method
|
||||
```
|
||||
|
||||
### KeyConversion
|
||||
|
||||
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`.
|
||||
|
|
|
@ -10,17 +10,27 @@ module Hashie
|
|||
def []=(key, value)
|
||||
into = self.class.key_coercion(key) || self.class.value_coercion(value)
|
||||
|
||||
if value && into
|
||||
if into.respond_to?(:coerce)
|
||||
value = into.coerce(value)
|
||||
else
|
||||
value = into.new(value)
|
||||
end
|
||||
return super(key, value) unless value && into
|
||||
return super(key, coerce_or_init(into).call(value)) unless into.is_a?(Enumerable)
|
||||
|
||||
if into.class >= Hash
|
||||
key_coerce = coerce_or_init(into.flatten[0])
|
||||
value_coerce = coerce_or_init(into.flatten[-1])
|
||||
value = Hash[value.map { |k, v| [key_coerce.call(k), value_coerce.call(v)] }]
|
||||
else # Enumerable but not Hash: Array, Set
|
||||
value_coerce = coerce_or_init(into.first)
|
||||
value = into.class.new(value.map { |v| value_coerce.call(v) })
|
||||
end
|
||||
|
||||
super(key, value)
|
||||
end
|
||||
|
||||
def coerce_or_init(type)
|
||||
type.respond_to?(:coerce) ? ->(v) { type.coerce(v) } : ->(v) { type.new(v) }
|
||||
end
|
||||
|
||||
private :coerce_or_init
|
||||
|
||||
def custom_writer(key, value, _convert = true)
|
||||
self[key] = value
|
||||
end
|
||||
|
|
|
@ -50,6 +50,48 @@ describe Hashie::Extensions::Coercion do
|
|||
expect(instance[:bar]).to be_coerced
|
||||
end
|
||||
|
||||
it 'supports coercion for Array' do
|
||||
subject.coerce_key :foo, Array[Coercable]
|
||||
|
||||
instance[:foo] = %w('bar', 'bar2')
|
||||
expect(instance[:foo]).to all(be_coerced)
|
||||
expect(instance[:foo]).to be_a(Array)
|
||||
end
|
||||
|
||||
it 'supports coercion for Set' do
|
||||
subject.coerce_key :foo, Set[Coercable]
|
||||
|
||||
instance[:foo] = Set.new(%w('bar', 'bar2'))
|
||||
expect(instance[:foo]).to all(be_coerced)
|
||||
expect(instance[:foo]).to be_a(Set)
|
||||
end
|
||||
|
||||
it 'supports coercion for Set of primitive' do
|
||||
subject.coerce_key :foo, Set[Initializable]
|
||||
|
||||
instance[:foo] = %w('bar', 'bar2')
|
||||
expect(instance[:foo].map(&:value)).to all(eq 'String')
|
||||
expect(instance[:foo]).to be_none { |v| v.coerced? }
|
||||
expect(instance[:foo]).to be_a(Set)
|
||||
end
|
||||
|
||||
it 'supports coercion for Hash' do
|
||||
subject.coerce_key :foo, Hash[Coercable => Coercable]
|
||||
|
||||
instance[:foo] = { 'bar_key' => 'bar_value', 'bar2_key' => 'bar2_value' }
|
||||
expect(instance[:foo].keys).to all(be_coerced)
|
||||
expect(instance[:foo].values).to all(be_coerced)
|
||||
expect(instance[:foo]).to be_a(Hash)
|
||||
end
|
||||
|
||||
it 'supports coercion for Hash with primitive as value' do
|
||||
subject.coerce_key :foo, Hash[Coercable => Initializable]
|
||||
|
||||
instance[:foo] = { 'bar_key' => '1', 'bar2_key' => '2' }
|
||||
expect(instance[:foo].values.map(&:value)).to all(eq 'String')
|
||||
expect(instance[:foo].keys).to all(be_coerced)
|
||||
end
|
||||
|
||||
it 'calls #new if no coerce method is available' do
|
||||
subject.coerce_key :foo, Initializable
|
||||
|
||||
|
|
Loading…
Reference in New Issue