Custom error messages for required properties in Hashie::Dash subclasses

This commit is contained in:
Petr Balaban 2014-10-07 18:25:55 +03:00
parent 72306d654d
commit 903026ee68
4 changed files with 49 additions and 9 deletions

View File

@ -1,6 +1,7 @@
## Next Release
* Your contribution here.
* [#233](https://github.com/intridea/hashie/pull/233): Custom error messages for required properties in Hashie::Dash subclasses - [@joss](https://github.com/joss).
* [#231](https://github.com/intridea/hashie/pull/231): Added support for coercion on class type that inherit from Hash - [@gregory](https://github.com/gregory).
* [#228](https://github.com/intridea/hashie/pull/228): Made Hashie::Extensions::Parsers::YamlErbParser pass template filename to ERB - [@jperville](https://github.com/jperville).
* [#224](https://github.com/intridea/hashie/pull/224): Merging Hashie::Mash now correctly only calls the block on duplicate values - [@amysutedja](https://github.com/amysutedja).

View File

@ -385,22 +385,26 @@ safe_mash.zip = 'Test' # => ArgumentError
## Dash
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.
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.
### Example:
```ruby
class Person < Hashie::Dash
property :name, required: true
property :age, required: true, message: 'must be set.'
property :email
property :occupation, default: 'Rubyist'
end
p = Person.new # => ArgumentError: The property 'name' is required for this Dash.
p = Person.new(name: 'Bob') # => ArgumentError: The property 'age' must be set.
p = Person.new(name: "Bob")
p.name # => 'Bob'
p.name = nil # => ArgumentError: The property 'name' is required for this Dash.
p = Person.new(name: "Bob", age: 18)
p.name # => 'Bob'
p.name = nil # => ArgumentError: The property 'name' is required for this Dash.
p.age # => 18
p.age = nil # => ArgumentError: The property 'age' must be set.
p.email = 'abc@def.com'
p.occupation # => 'Rubyist'
p.email # => 'abc@def.com'

View File

@ -27,6 +27,7 @@ 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.
# * <tt>:message</tt> - Specify custom error message for required property
#
def self.property(property_name, options = {})
properties << property_name
@ -46,7 +47,12 @@ module Hashie
if defined? @subclasses
@subclasses.each { |klass| klass.property(property_name, options) }
end
required_properties << property_name if options.delete(:required)
if options.delete(:required)
required_properties[property_name] = options.delete(:message) || "is required for #{name}."
else
fail ArgumentError, 'The :message option should be used with :required option.' if options.key?(:message)
end
end
class << self
@ -55,7 +61,7 @@ module Hashie
end
instance_variable_set('@properties', Set.new)
instance_variable_set('@defaults', {})
instance_variable_set('@required_properties', Set.new)
instance_variable_set('@required_properties', {})
def self.inherited(klass)
super
@ -74,7 +80,7 @@ module Hashie
# Check to see if the specified property is
# required.
def self.required?(name)
required_properties.include? name
required_properties.key? name
end
# You may initialize a Dash with an attributes hash
@ -168,7 +174,7 @@ module Hashie
end
def assert_required_attributes_set!
self.class.required_properties.each do |required_property|
self.class.required_properties.each_key do |required_property|
assert_property_set!(required_property)
end
end
@ -182,7 +188,7 @@ module Hashie
end
def fail_property_required_error!(property)
fail ArgumentError, "The property '#{property}' is required for #{self.class.name}."
fail ArgumentError, "The property '#{property}' #{self.class.required_properties[property]}"
end
def fail_no_property_error!(property)

View File

@ -34,6 +34,10 @@ class SubclassedTest < DashTest
property :last_name, required: true
end
class RequiredMessageTest < DashTest
property :first_name, required: true, message: 'must be set.'
end
class DashDefaultTest < Hashie::Dash
property :aliases, default: ['Snake']
end
@ -47,11 +51,20 @@ describe DashTest do
[ArgumentError, "The property '#{property}' is required for #{subject.class.name}."]
end
def property_required_custom_error(property)
[ArgumentError, "The property '#{property}' must be set."]
end
def property_message_without_required_error
[ArgumentError, 'The :message option should be used with :required option.']
end
def no_property_error(property)
[NoMethodError, "The property '#{property}' is not defined for #{subject.class.name}."]
end
subject { DashTest.new(first_name: 'Bob', email: 'bob@example.com') }
let(:required_message) { RequiredMessageTest.new(first_name: 'Bob') }
it('subclasses Hashie::Hash') { should respond_to(:to_mash) }
@ -83,13 +96,29 @@ describe DashTest do
expect { subject.first_name = nil }.to raise_error(*property_required_error('first_name'))
end
it 'errors out when message added to not required property' do
expect do
class DashMessageOptionWithoutRequiredTest < Hashie::Dash
property :first_name, message: 'is required.'
end
end.to raise_error(*property_message_without_required_error)
expect do
class DashMessageOptionWithoutRequiredTest < Hashie::Dash
property :first_name, required: false, message: 'is required.'
end
end.to raise_error(*property_message_without_required_error)
end
context 'writing to properties' do
it 'fails writing a required property to nil' do
expect { subject.first_name = nil }.to raise_error(*property_required_error('first_name'))
expect { required_message.first_name = nil }.to raise_error(*property_required_custom_error('first_name'))
end
it 'fails writing a required property to nil using []=' do
expect { subject[:first_name] = nil }.to raise_error(*property_required_error('first_name'))
expect { required_message[:first_name] = nil }.to raise_error(*property_required_custom_error('first_name'))
end
it 'fails writing to a non-existent property using []=' do