Skip to content

Commit

Permalink
Add dependent parameters feature
Browse files Browse the repository at this point in the history
  • Loading branch information
felipefava committed Jul 3, 2020
1 parent e319b7a commit 93124a5
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 1 deletion.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,31 @@ end
This means that in the request params you expect a valid email value in the key `email_address`,
but in your controller you will access with the key `email`.

### Dependent Parameters
If you want to receive and validate a parameter only if another one is given, you can use
the `is_given` option.

```ruby
some_action.request do |params|
params.optional :label, type: :string
params.required :description, type: :string, if_given: :label
#...
params.required :card_type, inclusion: %w(credit_card debit_card)
params.required :ccv, if_given: { card_type: lambda { |value| value == 'credit_card' } }
end
```

On the example above, the param `description` will be only validated if the param `label` is present.
RequestParamsValidation will use the method `blank?` to check that. On the other hand, the param
`ccv` will only be validated if the param `type_card` is equal to the string `credit_card`.

Notice that if the global option `filter_params` is set to `true` (default behaviour), then the
dependent parameters will be filtered from the `params object` if they haven't beeen validated.
This way we make sure to only receive those parameters that have been validated against our request
definitions.

Be aware that if you rename a param, then you should use the new name in the `if_given` option.

---
### NOTE

Expand Down
29 changes: 28 additions & 1 deletion lib/request_params_validation/definitions/param.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module RequestParamsValidation
module Definitions
class Param
attr_reader :key, :required, :allow_blank, :type, :rename_as, :transform, :decimal_precision,
:inclusion, :length, :value, :format, :custom_validation, :elements
:inclusion, :length, :value, :format, :custom_validation, :if_given, :elements

def initialize(options, &block)
@key = options[:key]
Expand All @@ -23,6 +23,7 @@ def initialize(options, &block)
@value = build_value_option(options[:value])
@format = build_format_option(options[:format])
@custom_validation = build_custom_validation_option(options[:validate])
@if_given = build_if_given_option(options[:if_given])

@elements = build_elements_option(options[:elements], &block)
@sub_definition = build_sub_definition(&block)
Expand Down Expand Up @@ -82,6 +83,18 @@ def transform?
!!@transform
end

def skip?(request_params)
return false unless @if_given

if_given_param_value = request_params[@if_given.param]

if @if_given.function
!@if_given.function.call(if_given_param_value)
else
if_given_param_value.blank?
end
end

private

def build_inclusion_option(inclusion)
Expand Down Expand Up @@ -158,6 +171,20 @@ def build_custom_validation_option(validation)
Struct.new(:function, :message).new(function, message)
end

def build_if_given_option(if_given)
case if_given
when String, Symbol
param = if_given.to_sym
when Hash
param = if_given.first.try(:first)
function = if_given.first.try(:last)
end

return unless param

Struct.new(:param, :function).new(param, function)
end

def build_elements_option(elements, &block)
return unless @type == Params::ARRAY_TYPE

Expand Down
4 changes: 4 additions & 0 deletions lib/request_params_validation/params.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ module Params

def self.validate!(definition, params)
definition.each do |param_definition|
next if param_definition.skip?(params)

validate_and_coerce_param(param_definition, params)
end

Expand Down Expand Up @@ -42,6 +44,8 @@ def self.filter_params(definition, params, extra_keys = [])
return params if definition.empty?

params_keys = definition.map do |param_definition|
next if param_definition.skip?(params)

key = param_definition.rename? ? param_definition.rename_as : param_definition.key

if param_definition.sub_definition
Expand Down
88 changes: 88 additions & 0 deletions spec/request_params_validation/if_given_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
RSpec.describe ApplicationController, type: :controller do
describe 'if_given option' do
let(:define_params) do
lambda do |params|
params.optional :key_1, as: key_1_rename
params.required :key_2, if_given: if_given_option
end
end

let(:if_given_option) { :key_1 }
let(:key_1_rename) { nil }

let(:key_1_value) { 'key 1 value' }
let(:key_2_value) { 'key 2 value' }

let(:request_params) { { key_1: key_1_value, key_2: key_2_value } }

before { post :dummy, body: request_params.to_json, as: :json }

it { expect(controller.params[:key_1]).to eq(key_1_value) }
it { expect(controller.params[:key_2]).to eq(key_2_value) }

context 'when key_2 is not present' do
let(:key_2_value) { [nil, ''].sample }

it 'returns the correct error message' do
expect(response.body).to eq(build_error_response(:missing_param, param_key: :key_2))
end
end

context 'when key_1 is not present' do
let(:key_1_value) { [nil, ''].sample }

it { expect(controller.params[:key_1]).to eq(key_1_value) }
it { expect(controller.params.key?(:key_2)).to eq(false) }

context 'when key_2 is not present' do
let(:key_2_value) { [nil, ''].sample }

it { expect(controller.params[:key_1]).to eq(key_1_value) }
it { expect(controller.params.key?(:key_2)).to eq(false) }
end
end

context 'when key_1 has been renamed' do
let(:key_1_rename) { :renamed_key_1 }

it { expect(controller.params[:renamed_key_1]).to eq(key_1_value) }
it { expect(controller.params.key?(:key_2)).to eq(false) }

context 'when if_given_option matches with the renamed key' do
let(:if_given_option) { :renamed_key_1 }

it { expect(controller.params[:renamed_key_1]).to eq(key_1_value) }
it { expect(controller.params[:key_2]).to eq(key_2_value) }
end
end

context 'when if_given_option has custom matcher' do
let(:if_given_option) { { key_1: -> (val) { val == 'expected value' } } }

let(:key_1_value) { 'expected value' }

it { expect(controller.params[:key_1]).to eq(key_1_value) }
it { expect(controller.params[:key_2]).to eq(key_2_value) }

context 'when key_2 is not present' do
let(:key_2_value) { [nil, ''].sample }

it 'returns the correct error message' do
expect(response.body).to eq(build_error_response(:missing_param, param_key: :key_2))
end
end

context 'when key_1 has not the expected value of the option if_given' do
let(:key_1_value) { 'not expected value' }

it { expect(controller.params[:key_1]).to eq(key_1_value) }
it { expect(controller.params.key?(:key_2)).to eq(false) }

context 'when key_2 is not present' do
it { expect(controller.params[:key_1]).to eq(key_1_value) }
it { expect(controller.params.key?(:key_2)).to eq(false) }
end
end
end
end
end

0 comments on commit 93124a5

Please sign in to comment.