Skip to content

Commit

Permalink
Merge pull request #530 from caalberts/dash-allowed-values
Browse files Browse the repository at this point in the history
Hashie::Extensions::Dash::PredefinedValues
  • Loading branch information
dblock committed Sep 24, 2020
2 parents b24d6dc + 4cd2844 commit a30327a
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions README.md
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/hashie.rb
Expand Up @@ -41,6 +41,7 @@ module Dash
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
Expand Down
88 changes: 88 additions & 0 deletions 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
58 changes: 58 additions & 0 deletions 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

0 comments on commit a30327a

Please sign in to comment.