From f5d73064a13d3c5de55cb8d963316bd403ca7cf6 Mon Sep 17 00:00:00 2001 From: Reynard Hilman Date: Wed, 22 Jan 2014 14:56:02 -0800 Subject: [PATCH] Add ability to use Grape::Entity documentation in the params block. --- CHANGELOG.md | 1 + README.md | 18 ++++++++- lib/grape/validations.rb | 38 +++++++++++++++--- spec/grape/validations_spec.rb | 70 ++++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99cfcc30c0..0359498688 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Next Release * [#544](https://github.com/intridea/grape/pull/544): The `rescue_from` keyword now handles subclasses of exceptions by default - [@xevix](https://github.com/xevix). * [#545](https://github.com/intridea/grape/pull/545): Added `type` (Array or Hash) support to `requires`, `optional` and `group` - [@bwalex](https://github.com/bwalex). * [#550](https://github.com/intridea/grape/pull/550): Added possibility to define reusable params - [@dm1try](https://github.com/dm1try). +* [#560](https://github.com/intridea/grape/pull/560): Use `Grape::Entity` documentation to define required and optional parameters with `requires using:` - [@reynardmh](https://github.com/reynardmh). * Your contribution here. #### Fixes diff --git a/README.md b/README.md index 690ef9d4da..dfb2ba09e6 100644 --- a/README.md +++ b/README.md @@ -1169,7 +1169,6 @@ The following example exposes statuses. ```ruby module API - module Entities class Status < Grape::Entity expose :user_name @@ -1196,6 +1195,23 @@ module API end ``` +You can use entity documentation directly in the params block with `using: Entity.documentation`. + +```ruby +module API + class Statuses < Grape::API + version 'v1' + + desc 'Create a status', { + requires :all, except: [:ip], using: API::Entities::Status.documentation.except(:id) + } + post '/status' do + Status.create! params + end + end +end +``` + You can present with multiple entities using an optional Symbol argument. ```ruby diff --git a/lib/grape/validations.rb b/lib/grape/validations.rb index 7bbde1a5be..82897656eb 100644 --- a/lib/grape/validations.rb +++ b/lib/grape/validations.rb @@ -108,13 +108,16 @@ def should_validate?(parameters) def requires(*attrs, &block) orig_attrs = attrs.clone - validations = { presence: true } - validations.merge!(attrs.pop) if attrs.last.is_a?(Hash) - validations[:type] ||= Array if block_given? - validates(attrs, validations) + opts = attrs.last.is_a?(Hash) ? attrs.pop : nil - block_given? ? new_scope(orig_attrs, &block) : - push_declared_params(attrs) + if opts && opts[:using] + require_required_and_optional_fields(attrs.first, opts) + else + validate_attributes(attrs, opts, &block) + + block_given? ? new_scope(orig_attrs, &block) : + push_declared_params(attrs) + end end def optional(*attrs, &block) @@ -172,6 +175,29 @@ def push_declared_params(attrs) private + def require_required_and_optional_fields(context, opts) + if context == :all + optional_fields = opts[:except].is_a?(Array) ? opts[:except] : [opts[:except]].compact + required_fields = opts[:using].keys - optional_fields + else # attrs.first == :none + required_fields = opts[:except].is_a?(Array) ? opts[:except] : [opts[:except]].compact + optional_fields = opts[:using].keys - required_fields + end + required_fields.each do |field| + requires(field, opts[:using][field]) + end + optional_fields.each do |field| + optional(field, opts[:using][field]) + end + end + + def validate_attributes(attrs, opts, &block) + validations = { presence: true } + validations.merge!(opts) if opts + validations[:type] ||= Array if block + validates(attrs, validations) + end + def new_scope(attrs, optional = false, &block) opts = attrs[1] || { type: Array } raise ArgumentError unless opts.keys.to_set.subset? [:type].to_set diff --git a/spec/grape/validations_spec.rb b/spec/grape/validations_spec.rb index a5b26d00ca..1b923e9499 100644 --- a/spec/grape/validations_spec.rb +++ b/spec/grape/validations_spec.rb @@ -78,6 +78,76 @@ def app end end + context 'requires :all using Grape::Entity documentation' do + def define_requires_all + documentation = { + required_field: { type: String }, + optional_field: { type: String } + } + subject.params do + requires :all, except: :optional_field, using: documentation + end + end + before do + define_requires_all + subject.get '/required' do + 'required works' + end + end + + it 'adds entity documentation to declared params' do + define_requires_all + subject.settings[:declared_params].should == [:required_field, :optional_field] + end + + it 'errors when required_field is not present' do + get '/required' + last_response.status.should == 400 + last_response.body.should == 'required_field is missing' + end + + it 'works when required_field is present' do + get '/required', required_field: 'woof' + last_response.status.should == 200 + last_response.body.should == 'required works' + end + end + + context 'requires :none using Grape::Entity documentation' do + def define_requires_none + documentation = { + required_field: { type: String }, + optional_field: { type: String } + } + subject.params do + requires :none, except: :required_field, using: documentation + end + end + before do + define_requires_none + subject.get '/required' do + 'required works' + end + end + + it 'adds entity documentation to declared params' do + define_requires_none + subject.settings[:declared_params].should == [:required_field, :optional_field] + end + + it 'errors when required_field is not present' do + get '/required' + last_response.status.should == 400 + last_response.body.should == 'required_field is missing' + end + + it 'works when required_field is present' do + get '/required', required_field: 'woof' + last_response.status.should == 200 + last_response.body.should == 'required works' + end + end + context 'required with an Array block' do before do subject.params do