From 58c17e9bf986899cbe16bc4990b6d04add0166b3 Mon Sep 17 00:00:00 2001 From: lizihe Date: Thu, 6 Nov 2014 15:06:17 +0800 Subject: [PATCH] Added all_or_none parameter validator. --- CHANGELOG.md | 2 +- README.md | 20 +++++- lib/grape.rb | 1 + lib/grape/locale/en.yml | 1 + .../validations/validators/all_or_none.rb | 20 ++++++ .../validators/all_or_none_spec.rb | 61 +++++++++++++++++++ 6 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 lib/grape/validations/validators/all_or_none.rb create mode 100644 spec/grape/validations/validators/all_or_none_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index dc2ba4b41b..c870868e25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ 0.9.1 (Next) ============ - +* [#803](https://github.com/intridea/grape/pull/803): Added `all_or_none_of` parameter validator - [@loveltyoic](https://github.com/loveltyoic). * [#774](https://github.com/intridea/grape/pull/774): Extended `mutually_exclusive`, `exactly_one_of`, `at_least_one_of` to work inside any kind of group: `requires` or `optional`, `Hash` or `Array` - [@ShPakvel](https://github.com/ShPakvel). * [#743](https://github.com/intridea/grape/pull/743): Added `allow_blank` parameter validator to validate non-empty strings - [@elado](https://github.com/elado). * [#745](https://github.com/intridea/grape/pull/745): Removed `atom+xml`, `rss+xml`, and `jsonapi` content-types - [@akabraham](https://github.com/akabraham). diff --git a/README.md b/README.md index d02e4e5d09..423e21dc31 100644 --- a/README.md +++ b/README.md @@ -643,7 +643,20 @@ params do end ``` -#### Nested `mutually_exclusive`, `exactly_one_of`, `at_least_one_of` +#### `all_or_none_of` + +Parameters can be defined as 'all_or_none_of', ensuring that all or none of parameters gets selected. + +```ruby +params do + optional :beer + optional :wine + optional :juice + all_or_none_of :beer, :wine, :juice +end +``` + +#### Nested `mutually_exclusive`, `exactly_one_of`, `at_least_one_of`, `all_or_none_of` All of these methods can be used at any nested level. @@ -666,6 +679,11 @@ params do optional :icecream mutually_exclusive :cake, :icecream end + optional :recipe do + optional :oil + optional :meat + all_or_none_of :oil, :meat + end end ``` diff --git a/lib/grape.rb b/lib/grape.rb index ec58a2edbc..aa6883fd60 100644 --- a/lib/grape.rb +++ b/lib/grape.rb @@ -138,5 +138,6 @@ class API require 'grape/validations/validators/regexp' require 'grape/validations/validators/values' require 'grape/validations/params_scope' +require 'grape/validations/validators/all_or_none' require 'grape/version' diff --git a/lib/grape/locale/en.yml b/lib/grape/locale/en.yml index c77b1accf1..9badb6a1be 100644 --- a/lib/grape/locale/en.yml +++ b/lib/grape/locale/en.yml @@ -32,4 +32,5 @@ en: mutual_exclusion: 'are mutually exclusive' at_least_one: 'are missing, at least one parameter must be provided' exactly_one: 'are missing, exactly one parameter must be provided' + all_or_none: 'provide all or none of parameters' diff --git a/lib/grape/validations/validators/all_or_none.rb b/lib/grape/validations/validators/all_or_none.rb new file mode 100644 index 0000000000..776198cea1 --- /dev/null +++ b/lib/grape/validations/validators/all_or_none.rb @@ -0,0 +1,20 @@ +module Grape + module Validations + require 'grape/validations/validators/multiple_params_base' + class AllOrNoneOfValidator < MultipleParamsBase + def validate!(params) + super + if scope_requires_params && only_subset_present + raise Grape::Exceptions::Validation, params: all_keys, message_key: :all_or_none + end + params + end + + private + + def only_subset_present + scoped_params.any? { |resource_params| keys_in_common(resource_params).length > 0 && keys_in_common(resource_params).length < attrs.length } + end + end + end +end diff --git a/spec/grape/validations/validators/all_or_none_spec.rb b/spec/grape/validations/validators/all_or_none_spec.rb new file mode 100644 index 0000000000..9d98b8b9b1 --- /dev/null +++ b/spec/grape/validations/validators/all_or_none_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe Grape::Validations::AllOrNoneOfValidator do + describe '#validate!' do + let(:scope) do + Struct.new(:opts) do + def params(arg) + arg + end + + def required?; end + end + end + let(:all_or_none_params) { [:beer, :wine, :grapefruit] } + let(:validator) { described_class.new(all_or_none_params, {}, false, scope.new) } + + context 'when all restricted params are present' do + let(:params) { { beer: true, wine: true, grapefruit: true } } + + it 'does not raise a validation exception' do + expect(validator.validate!(params)).to eql params + end + + context 'mixed with other params' do + let(:mixed_params) { params.merge!(other: true, andanother: true) } + + it 'does not raise a validation exception' do + expect(validator.validate!(mixed_params)).to eql mixed_params + end + end + end + + context 'when none of the restricted params is selected' do + let(:params) { { somethingelse: true } } + + it 'does not raise a validation exception' do + expect(validator.validate!(params)).to eql params + end + end + + context 'when only a subset of restricted params are present' do + let(:params) { { beer: true, grapefruit: true } } + + it 'raises a validation exception' do + expect { + validator.validate! params + }.to raise_error(Grape::Exceptions::Validation) + end + context 'mixed with other params' do + let(:mixed_params) { params.merge!(other: true, andanother: true) } + + it 'raise a validation exception' do + expect { + validator.validate! params + }.to raise_error(Grape::Exceptions::Validation) + end + end + end + + end +end