Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Hashie::Extensions::Dash::AllowList
Extends a Dash with the ability to accept only predefined values on a property. #61
- Loading branch information
Showing
5 changed files
with
163 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
module Hashie | ||
module Extensions | ||
module Dash | ||
# Extends a Dash with the ability to accept only predefined values on a property. | ||
# | ||
# == Example | ||
# | ||
# class PersonHash < Hashie::Dash | ||
# include Hashie::Extensions::Dash::PredefinedValues | ||
# | ||
# property :gender, values: [:male, :female, :prefer_not_to_say] | ||
# property :age, values: (0..150) # a Range | ||
# end | ||
# | ||
# person = PersonHash.new(gender: :male, age: -1) | ||
# # => ArgumentError: The value '-1' is not accepted for property 'age' | ||
module PredefinedValues | ||
def self.included(base) | ||
base.instance_variable_set(:@values_for_properties, {}) | ||
base.extend(ClassMethods) | ||
base.include(InstanceMethods) | ||
end | ||
|
||
module ClassMethods | ||
attr_reader :values_for_properties | ||
|
||
def inherited(klass) | ||
super | ||
klass.instance_variable_set(:@values_for_properties, values_for_properties.dup) | ||
end | ||
|
||
def property(property_name, options = {}) | ||
super | ||
|
||
return unless (predefined_values = options[:values]) | ||
|
||
assert_predefined_values!(predefined_values) | ||
set_predefined_values(property_name, predefined_values) | ||
end | ||
|
||
private | ||
|
||
def assert_predefined_values!(predefined_values) | ||
return if supported_type?(predefined_values) | ||
|
||
raise ArgumentError, %(`values` accepts an Array or a Range.) | ||
end | ||
|
||
def supported_type?(predefined_values) | ||
[::Array, ::Range].any? { |klass| predefined_values.is_a?(klass) } | ||
end | ||
|
||
def set_predefined_values(property_name, predefined_values) | ||
@values_for_properties[property_name] = predefined_values | ||
end | ||
end | ||
|
||
module InstanceMethods | ||
def initialize(*) | ||
super | ||
|
||
assert_property_values! | ||
end | ||
|
||
private | ||
|
||
def assert_property_values! | ||
self.class.values_for_properties.each_key do |property| | ||
value = send(property) | ||
|
||
if value && !values_for_properties(property).include?(value) | ||
fail_property_value_error!(property) | ||
end | ||
end | ||
end | ||
|
||
def fail_property_value_error!(property) | ||
raise ArgumentError, "Invalid value for property '#{property}'" | ||
end | ||
|
||
def values_for_properties(property) | ||
self.class.values_for_properties[property] | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
require 'spec_helper' | ||
|
||
describe Hashie::Extensions::Dash::PredefinedValues do | ||
let(:extended_dash) do | ||
Class.new(Hashie::Dash) do | ||
include Hashie::Extensions::Dash::PredefinedValues | ||
|
||
property :gender, values: %i[male female prefer_not_to_say] | ||
property :age, values: (0..150) | ||
end | ||
end | ||
|
||
it 'allows value within the predefined list' do | ||
valid_dash = extended_dash.new(gender: :male) | ||
expect(valid_dash.gender).to eq(:male) | ||
end | ||
|
||
it 'rejects value outside the predefined list' do | ||
expect { extended_dash.new(gender: :unicorn) } | ||
.to raise_error(ArgumentError, %(Invalid value for property 'gender')) | ||
end | ||
|
||
it 'accepts a range for predefined list' do | ||
expect { extended_dash.new(age: -1) } | ||
.to raise_error(ArgumentError, %(Invalid value for property 'age')) | ||
end | ||
|
||
it 'allows property to be nil' do | ||
expect { extended_dash.new } | ||
.not_to raise_error | ||
end | ||
|
||
it 'rejects non array or range for predefined list' do | ||
expect do | ||
class DashWithUnsupportedValueType < Hashie::Dash | ||
include Hashie::Extensions::Dash::PredefinedValues | ||
|
||
property :name, values: -> { :foo } | ||
end | ||
end.to raise_error(ArgumentError, %(`values` accepts an Array or a Range.)) | ||
end | ||
|
||
let(:subclass) do | ||
Class.new(extended_dash) do | ||
property :language, values: %i[ruby javascript] | ||
end | ||
end | ||
|
||
it 'passes property predefined list to subclasses' do | ||
expect { subclass.new(gender: :unicorn) } | ||
.to raise_error(ArgumentError, %(Invalid value for property 'gender')) | ||
end | ||
|
||
it 'allows subclass to define predefined list' do | ||
expect { subclass.new(language: :ruby) } | ||
.not_to raise_error | ||
end | ||
end |