diff options
author | Daniel Doubrovkine (dB.) @dblockdotorg <dblock@dblock.org> | 2015-04-27 07:52:52 -0400 |
---|---|---|
committer | Daniel Doubrovkine (dB.) @dblockdotorg <dblock@dblock.org> | 2015-04-27 07:52:52 -0400 |
commit | 78e373301a15b84027a8217cf8fa80fe5400bf13 (patch) | |
tree | 26c7c50c3d37ca8b2826cebf2e7c9472646ebbaa | |
parent | fc4b183eff6ec51270d7ec11e95536f8cf0e304a (diff) | |
parent | fa3ba9f9a6808eb56143e5c11aef52dc2c239d98 (diff) | |
download | hashie-78e373301a15b84027a8217cf8fa80fe5400bf13.tar.gz |
Merge pull request #298 from intridea/circular-coercion-docs
Add documentation about circular coercion
-rw-r--r-- | README.md | 35 | ||||
-rw-r--r-- | spec/hashie/extensions/coercion_spec.rb | 61 |
2 files changed, 96 insertions, 0 deletions
@@ -145,6 +145,41 @@ class Tweet < Hash end ``` +#### A note on circular coercion + +Since `coerce_key` is a class-level method, you cannot have circular coercion without the use of a proc. For example: + +```ruby +class CategoryHash < Hash + include Hashie::Extensions::Coercion + include Hashie::Extensions::MergeInitializer + + coerce_key :products, Array[ProductHash] +end + +class ProductHash < Hash + include Hashie::Extensions::Coercion + include Hashie::Extensions::MergeInitializer + + coerce_key :categories, Array[CategoriesHash] +end +``` + +This will fail with a `NameError` for `CategoryHash::ProductHash` because `ProductHash` is not defined at the point that `coerce_key` is happening for `CategoryHash`. + +To work around this, you can use a coercion proc. For example, you could do: + +```ruby +class CategoryHash < Hash + # ... + coerce_key :products, ->(value) do + return value.map { |v| ProductHash.new(v) } if value.respond_to?(:map) + + ProductHash.new(value) + end +end +``` + ### 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`. diff --git a/spec/hashie/extensions/coercion_spec.rb b/spec/hashie/extensions/coercion_spec.rb index b190686..2cd5887 100644 --- a/spec/hashie/extensions/coercion_spec.rb +++ b/spec/hashie/extensions/coercion_spec.rb @@ -437,6 +437,67 @@ describe Hashie::Extensions::Coercion do expect(MyOwnBase.key_coercions).to eq({}) end end + + context 'when using circular coercion' do + context 'with a proc on one side' do + class CategoryHash < Hash + include Hashie::Extensions::Coercion + include Hashie::Extensions::MergeInitializer + + coerce_key :products, lambda { |value| + return value.map { |v| ProductHash.new(v) } if value.respond_to?(:map) + + ProductHash.new(v) + } + end + + class ProductHash < Hash + include Hashie::Extensions::Coercion + include Hashie::Extensions::MergeInitializer + + coerce_key :categories, Array[CategoryHash] + end + + let(:category) { CategoryHash.new(type: 'rubygem', products: [Hashie::Mash.new(name: 'Hashie')]) } + let(:product) { ProductHash.new(name: 'Hashie', categories: [Hashie::Mash.new(type: 'rubygem')]) } + + it 'coerces CategoryHash[:products] correctly' do + expected = [ProductHash] + actual = category[:products].map(&:class) + + expect(actual).to eq(expected) + end + + it 'coerces ProductHash[:categories] correctly' do + expected = [CategoryHash] + actual = product[:categories].map(&:class) + + expect(actual).to eq(expected) + end + end + + context 'without a proc on either side' do + it 'fails with a NameError since the other class is not defined yet' do + attempted_code = lambda do + class AnotherCategoryHash < Hash + include Hashie::Extensions::Coercion + include Hashie::Extensions::MergeInitializer + + coerce_key :products, Array[AnotherProductHash] + end + + class AnotherProductHash < Hash + include Hashie::Extensions::Coercion + include Hashie::Extensions::MergeInitializer + + coerce_key :categories, Array[AnotherCategoryHash] + end + end + + expect { attempted_code.call }.to raise_error(NameError) + end + end + end end describe '#coerce_value' do |