summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlbert Salim <albertlimca@gmail.com>2020-09-19 13:02:13 +0800
committerAlbert Salim <albertlimca@gmail.com>2020-09-20 11:26:57 +0800
commit4cd2844357adfe9321e31c02b76c9945baaeffe4 (patch)
treed14c852181ddb262c475c171c69a863c70deee44
parentb24d6dca2c545637bc3cc3ac4d89f565fc27a9d0 (diff)
downloadhashie-4cd2844357adfe9321e31c02b76c9945baaeffe4.tar.gz
Add Hashie::Extensions::Dash::AllowList
Extends a Dash with the ability to accept only predefined values on a property. #61
-rw-r--r--CHANGELOG.md1
-rw-r--r--README.md15
-rw-r--r--lib/hashie.rb1
-rw-r--r--lib/hashie/extensions/dash/predefined_values.rb88
-rw-r--r--spec/hashie/extensions/dash/predefined_values_spec.rb58
5 files changed, 163 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c26a19e..a2b77b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ Any violations of this scheme are considered to be bugs.
* [#523](https://github.com/hashie/hashie/pull/523): Added TOC, ensure a keep-a-changelog formatted CHANGELOG - [@dblock](https://github.com/dblock).
* [#522](https://github.com/hashie/hashie/pull/522): Added eierlegende Wollmilchsau mascot graphic - [@carolineartz](https://github.com/carolineartz).
+* [#530](https://github.com/hashie/hashie/pull/530): Added Hashie::Extensions::Dash::PredefinedValues - [@caalberts](https://github.com/caalberts).
* Your contribution here.
### Changed
diff --git a/README.md b/README.md
index 4280e7b..4cf0214 100644
--- a/README.md
+++ b/README.md
@@ -43,6 +43,7 @@
- [PropertyTranslation](#propertytranslation)
- [Mash and Rails 4 Strong Parameters](#mash-and-rails-4-strong-parameters)
- [Coercion](#coercion-1)
+ - [PredefinedValues](#predefinedvalues)
- [Trash](#trash)
- [Clash](#clash)
- [Rash](#rash)
@@ -968,6 +969,20 @@ class UserHash < Hashie::Dash
end
```
+### PredefinedValues
+
+The `Hashie::Extensions::Dash::PredefinedValues` mixin extends a Dash with
+the ability to accept predefined values on a property.
+
+```ruby
+class UserHash < Hashie::Dash
+ include Hashie::Extensions::PredefinedValues
+
+ property :gender, values: %i[male female prefer_not_to_say]
+ property :age, values: (0..150)
+end
+```
+
## Trash
A Trash is a Dash that allows you to translate keys on initialization. It mixes
diff --git a/lib/hashie.rb b/lib/hashie.rb
index 7f88ed4..8f526c4 100644
--- a/lib/hashie.rb
+++ b/lib/hashie.rb
@@ -41,6 +41,7 @@ module Hashie
autoload :IndifferentAccess, 'hashie/extensions/dash/indifferent_access'
autoload :PropertyTranslation, 'hashie/extensions/dash/property_translation'
autoload :Coercion, 'hashie/extensions/dash/coercion'
+ autoload :PredefinedValues, 'hashie/extensions/dash/predefined_values'
end
module Mash
diff --git a/lib/hashie/extensions/dash/predefined_values.rb b/lib/hashie/extensions/dash/predefined_values.rb
new file mode 100644
index 0000000..2a0b7ca
--- /dev/null
+++ b/lib/hashie/extensions/dash/predefined_values.rb
@@ -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
diff --git a/spec/hashie/extensions/dash/predefined_values_spec.rb b/spec/hashie/extensions/dash/predefined_values_spec.rb
new file mode 100644
index 0000000..a84fe80
--- /dev/null
+++ b/spec/hashie/extensions/dash/predefined_values_spec.rb
@@ -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