diff options
author | Chris Cashwell <ccashwell@gmail.com> | 2014-12-11 13:27:15 -0500 |
---|---|---|
committer | Chris Cashwell <ccashwell@gmail.com> | 2014-12-11 23:08:25 -0500 |
commit | fb5941e2d51cfac8424ed43adbb823f6bd90ef6f (patch) | |
tree | ea47fe2c337409aeab7dd7c50b14b5edd09194c0 | |
parent | b51ea4d64202113e3a7d7577596595b2244bac94 (diff) | |
download | hashie-fb5941e2d51cfac8424ed43adbb823f6bd90ef6f.tar.gz |
Support for conditionally required Dash properties
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | README.md | 8 | ||||
-rw-r--r-- | lib/hashie/dash.rb | 29 | ||||
-rw-r--r-- | spec/hashie/dash_spec.rb | 19 |
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) @@ -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' |