summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Cashwell <ccashwell@gmail.com>2014-12-11 13:27:15 -0500
committerChris Cashwell <ccashwell@gmail.com>2014-12-11 23:08:25 -0500
commitfb5941e2d51cfac8424ed43adbb823f6bd90ef6f (patch)
treeea47fe2c337409aeab7dd7c50b14b5edd09194c0
parentb51ea4d64202113e3a7d7577596595b2244bac94 (diff)
downloadhashie-fb5941e2d51cfac8424ed43adbb823f6bd90ef6f.tar.gz
Support for conditionally required Dash properties
-rw-r--r--CHANGELOG.md1
-rw-r--r--README.md8
-rw-r--r--lib/hashie/dash.rb29
-rw-r--r--spec/hashie/dash_spec.rb19
4 files changed, 51 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 36a89d0..2b01f7a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
* [#247](https://github.com/intridea/hashie/pull/247): Fixed #stringify_keys and #symbolize_keys collision with ActiveSupport - [@bartoszkopinski](https://github.com/bartoszkopinski).
* [#249](https://github.com/intridea/hashie/pull/249): SafeAssignment will now also protect hash-style assignments - [@jrochkind](https://github.com/jrochkind).
* [#251](https://github.com/intridea/hashie/pull/251): Add block to indifferent access #fetch - [@jgraichen](https://github.com/jgraichen).
+* [#252](https://github.com/intridia/hashie/pull/252): Add support for conditionally required Hashie::Dash attributes - [@ccashwell](https://github.com/ccashwell).
* Your contribution here.
## 3.3.2 (11/26/2014)
diff --git a/README.md b/README.md
index 48e5df9..a658c52 100644
--- a/README.md
+++ b/README.md
@@ -385,6 +385,8 @@ safe_mash[:zip] = 'test' # => still ArgumentError
Dash is an extended Hash that has a discrete set of defined properties and only those properties may be set on the hash. Additionally, you can set defaults for each property. You can also flag a property as required. Required properties will raise an exception if unset. Another option is message for required properties, which allow you to add custom messages for required property.
+You can also conditionally require certain properties by passing a Proc or Symbol. If a Proc is provided, it will be run in the context of the Dash instance. If a Symbol is provided, the value returned for the property or method of the same name will be evaluated. The property will be required if the result of the conditional is truthy.
+
### Example:
```ruby
@@ -392,7 +394,13 @@ class Person < Hashie::Dash
property :name, required: true
property :age, required: true, message: 'must be set.'
property :email
+ property :phone, required: -> { email.nil? }, message: 'is required if email is not set.'
+ property :pants, required: :weekday?, message: 'are only required on weekdays.'
property :occupation, default: 'Rubyist'
+
+ def weekday?
+ [ Time.now.saturday?, Time.now.sunday? ].none?
+ end
end
p = Person.new # => ArgumentError: The property 'name' is required for this Dash.
diff --git a/lib/hashie/dash.rb b/lib/hashie/dash.rb
index 9381ddd..22f6bc8 100644
--- a/lib/hashie/dash.rb
+++ b/lib/hashie/dash.rb
@@ -26,7 +26,11 @@ module Hashie
#
# * <tt>:required</tt> - Specify the value as required for this
# property, to raise an error if a value is unset in a new or
- # existing Dash.
+ # existing Dash. If a Proc is provided, it will be run in the
+ # context of the Dash instance. If a Symbol is provided, the
+ # property it represents must not be nil. The property is only
+ # required if the value is truthy.
+ #
# * <tt>:message</tt> - Specify custom error message for required property
#
def self.property(property_name, options = {})
@@ -48,8 +52,10 @@ module Hashie
@subclasses.each { |klass| klass.property(property_name, options) }
end
- if options.delete(:required)
- required_properties[property_name] = options.delete(:message) || "is required for #{name}."
+ condition = options.delete(:required)
+ if condition
+ message = options.delete(:message) || "is required for #{name}."
+ required_properties[property_name] = { condition: condition, message: message }
else
fail ArgumentError, 'The :message option should be used with :required option.' if options.key?(:message)
end
@@ -180,19 +186,30 @@ module Hashie
end
def assert_property_set!(property)
- fail_property_required_error!(property) if send(property).nil?
+ fail_property_required_error!(property) if send(property).nil? && required?(property)
end
def assert_property_required!(property, value)
- fail_property_required_error!(property) if self.class.required?(property) && value.nil?
+ fail_property_required_error!(property) if value.nil? && required?(property)
end
def fail_property_required_error!(property)
- fail ArgumentError, "The property '#{property}' #{self.class.required_properties[property]}"
+ fail ArgumentError, "The property '#{property}' #{self.class.required_properties[property][:message]}"
end
def fail_no_property_error!(property)
fail NoMethodError, "The property '#{property}' is not defined for #{self.class.name}."
end
+
+ def required?(property)
+ return false unless self.class.required?(property)
+
+ condition = self.class.required_properties[property][:condition]
+ case condition
+ when Proc then !!(instance_exec(&condition))
+ when Symbol then !!(send(condition))
+ else !!(condition)
+ end
+ end
end
end
diff --git a/spec/hashie/dash_spec.rb b/spec/hashie/dash_spec.rb
index 2e2b23f..a6e8866 100644
--- a/spec/hashie/dash_spec.rb
+++ b/spec/hashie/dash_spec.rb
@@ -425,6 +425,25 @@ describe SubclassedTest do
end
end
+class ConditionallyRequiredTest < Hashie::Dash
+ property :username
+ property :password, required: -> { !username.nil? }, message: 'must be set, too.'
+end
+
+describe ConditionallyRequiredTest do
+ it 'does not allow a conditionally required property to be set to nil if required' do
+ expect { ConditionallyRequiredTest.new(username: 'bob.smith', password: nil) }.to raise_error(ArgumentError, "The property 'password' must be set, too.")
+ end
+
+ it 'allows a conditionally required property to be set to nil if not required' do
+ expect { ConditionallyRequiredTest.new(username: nil, password: nil) }.not_to raise_error
+ end
+
+ it 'allows a conditionally required property to be set if required' do
+ expect { ConditionallyRequiredTest.new(username: 'bob.smith', password: '$ecure!') }.not_to raise_error
+ end
+end
+
class MixedPropertiesTest < Hashie::Dash
property :symbol
property 'string'