diff options
author | Michael Herold <michael.j.herold@gmail.com> | 2018-02-05 20:10:23 -0600 |
---|---|---|
committer | Michael Herold <michael.j.herold@gmail.com> | 2018-02-06 18:42:02 -0600 |
commit | 0bd18eeb5bcbbc17d476573ae8a311fc339b6c82 (patch) | |
tree | 3d5201c99fa176c2be635cf9c2b3fa1a02a3019e | |
parent | 3c6149cdd72ab8cc87cf008cc222514e30ef7470 (diff) | |
download | hashie-0bd18eeb5bcbbc17d476573ae8a311fc339b6c82.tar.gz |
Allow codependent Dash attributes to initialize
Definition: Codependent properties
A set of two or more properties who have "required" validations that are based
on each other.
Example:
```ruby
class OneOrMore < Hashie::Dash
property :a, required: -> { b.nil? }
property :b, required: -> { a.nil? }
end
```
When constructing a Dash via the merge initializer, codependent properties have
their "required" validation run too early when their values are set to `nil`,
which causes an `ArgumentError` to be raised in the case that the first property
is set to `nil`.
This is an order-dependence bug that is fixed by this commit. By pruning off
`nil` values only during initialization via the merge initializer, we can
prevent this problem from occurring for the case of `nil` values.
However, this is an indication of a larger problem with the architecture of
Dash. We should be setting all the properties before running the validations.
Rearchitecting this will be quite an undertaking.
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | lib/hashie/dash.rb | 13 | ||||
-rw-r--r-- | spec/hashie/dash_spec.rb | 25 |
3 files changed, 37 insertions, 2 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a79834..8dbfc93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ scheme are considered to be bugs. * [#435](https://github.com/intridea/hashie/pull/435): Mash `default_proc`s are now propagated down to nested sub-Hashes - [@michaelherold](https://github.com/michaelherold). * [#436](https://github.com/intridea/hashie/pull/436): Ensure that `Hashie::Extensions::IndifferentAccess` injects itself after a non-destructive merge - [@michaelherold](https://github.com/michaelherold). +* [#437](https://github.com/intridea/hashie/pull/437): Allow codependent properties to be set on Dash - [@michaelherold](https://github.com/michaelherold). * Your contribution here. ### Security diff --git a/lib/hashie/dash.rb b/lib/hashie/dash.rb index 4ac7676..a111e4b 100644 --- a/lib/hashie/dash.rb +++ b/lib/hashie/dash.rb @@ -160,7 +160,7 @@ module Hashie end def update_attributes!(attributes) - initialize_attributes(attributes) + update_attributes(attributes) self.class.defaults.each_pair do |prop, value| self[prop] = begin @@ -175,9 +175,18 @@ module Hashie private def initialize_attributes(attributes) + return unless attributes + + cleaned_attributes = attributes.reject { |_attr, value| value.nil? } + update_attributes(cleaned_attributes) + end + + def update_attributes(attributes) + return unless attributes + attributes.each_pair do |att, value| self[att] = value - end if attributes + end end def assert_property_exists!(property) diff --git a/spec/hashie/dash_spec.rb b/spec/hashie/dash_spec.rb index 87e5bc3..fe789de 100644 --- a/spec/hashie/dash_spec.rb +++ b/spec/hashie/dash_spec.rb @@ -402,6 +402,31 @@ describe DashTest do expect(subject.count).to eq subject.class.defaults[:count] end end + + context 'codependent attributes' do + let(:codependent) do + Class.new(Hashie::Dash) do + property :a, required: -> { b.nil? }, message: 'is required if b is not set.' + property :b, required: -> { a.nil? }, message: 'is required if a is not set.' + end + end + + it 'does not raise an error when only the first property is set' do + expect { codependent.new(a: 'ant', b: nil) }.not_to raise_error + end + + it 'does not raise an error when only the second property is set' do + expect { codependent.new(a: nil, b: 'bat') }.not_to raise_error + end + + it 'does not raise an error when both properties are set' do + expect { codependent.new(a: 'ant', b: 'bat') }.not_to raise_error + end + + it 'raises an error when neither property is set' do + expect { codependent.new(a: nil, b: nil) }.to raise_error(ArgumentError) + end + end end end |