Added support for coercing enumerables and collections

This commit is contained in:
gregory 2014-06-26 09:28:11 -07:00
parent 4ab9a3361a
commit f09c5f68f9
4 changed files with 103 additions and 6 deletions

View File

@ -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).

View File

@ -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`.

View File

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

View File

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