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
161 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::AllowedValues | ||
# | ||
# property :gender, values: [:male, :female, :meat_popsicle] | ||
# property :age, values: 1..150 # a Range | ||
# end | ||
# | ||
# person = PersonHash.new(gender: :male, age: 0) | ||
# # => ArgumentError: The property 'age' must be within 1..150. | ||
module AllowList | ||
def self.included(base) | ||
base.instance_variable_set(:@allow_list_for_properties, {}) | ||
base.extend(ClassMethods) | ||
base.include(InstanceMethods) | ||
end | ||
|
||
module ClassMethods | ||
attr_reader :allow_list_for_properties | ||
|
||
def inherited(klass) | ||
super | ||
klass.instance_variable_set(:@allow_list_for_properties, allow_list_for_properties.dup) | ||
end | ||
|
||
def property(property_name, options = {}) | ||
super | ||
|
||
return unless (allowed_values = options[:allow]) | ||
|
||
assert_allow_list_type!(allowed_values) | ||
define_allow_list(property_name, allowed_values) | ||
end | ||
|
||
private | ||
|
||
def assert_allow_list_type!(allowed_values) | ||
return if supported_type?(allowed_values) | ||
|
||
raise ArgumentError, %(`allow` accepts an Array or a Range.) | ||
end | ||
|
||
def supported_type?(allowed_values) | ||
[::Array, ::Range].any? { |klass| allowed_values.is_a?(klass) } | ||
end | ||
|
||
def define_allow_list(property_name, allowed_values) | ||
@allow_list_for_properties[property_name] = allowed_values | ||
end | ||
end | ||
|
||
module InstanceMethods | ||
def initialize(*) | ||
super | ||
|
||
assert_allowed_property_values! | ||
end | ||
|
||
private | ||
|
||
def assert_allowed_property_values! | ||
self.class.allow_list_for_properties.each_key do |property| | ||
value = send(property) | ||
|
||
if value && !allow_list_for_properties(property).include?(value) | ||
fail_allowed_property_value_error!(property) | ||
end | ||
end | ||
end | ||
|
||
def fail_allowed_property_value_error!(property) | ||
raise ArgumentError, "The property '#{property}' is not in the allow list" | ||
end | ||
|
||
def allow_list_for_properties(property) | ||
self.class.allow_list_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,56 @@ | ||
require 'spec_helper' | ||
|
||
describe Hashie::Extensions::Dash::AllowList do | ||
class DashWithAllowedValues < Hashie::Dash | ||
include Hashie::Extensions::Dash::AllowList | ||
|
||
property :gender, allow: %i[male female prefer_not_to_say] | ||
property :age, allow: (0..150) | ||
end | ||
|
||
it 'allows value within the allow list' do | ||
valid_dash = DashWithAllowedValues.new(gender: :male) | ||
expect(valid_dash.gender).to eq(:male) | ||
end | ||
|
||
it 'rejects value outside the allow list' do | ||
expect { DashWithAllowedValues.new(gender: :unicorn) } | ||
.to raise_error(ArgumentError, %(The property 'gender' is not in the allow list)) | ||
end | ||
|
||
it 'accepts a range for allow list' do | ||
expect { DashWithAllowedValues.new(age: -1) } | ||
.to raise_error(ArgumentError, %(The property 'age' is not in the allow list)) | ||
end | ||
|
||
it 'allows property to be nil' do | ||
expect { DashWithAllowedValues.new } | ||
.not_to raise_error | ||
end | ||
|
||
it 'rejects non array or range for allow list' do | ||
expect do | ||
class DashWithUnsupportedAllowedValueType < Hashie::Dash | ||
include Hashie::Extensions::Dash::AllowList | ||
|
||
property :name, allow: -> { :foo } | ||
end | ||
end.to raise_error(ArgumentError, %(`allow` accepts an Array or a Range.)) | ||
end | ||
|
||
let(:subclass) do | ||
Class.new(DashWithAllowedValues) do | ||
property :language, allow: %i[ruby javascript] | ||
end | ||
end | ||
|
||
it 'passes property allow list to subclasses' do | ||
expect { subclass.new(gender: :unicorn) } | ||
.to raise_error(ArgumentError, %(The property 'gender' is not in the allow list)) | ||
end | ||
|
||
it 'allows subclass to define allow list' do | ||
expect { subclass.new(language: :ruby) } | ||
.not_to raise_error | ||
end | ||
end |