Skip to content

Commit 93124a5

Browse files
committed
Add dependent parameters feature
1 parent e319b7a commit 93124a5

File tree

4 files changed

+145
-1
lines changed

4 files changed

+145
-1
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,31 @@ end
456456
This means that in the request params you expect a valid email value in the key `email_address`,
457457
but in your controller you will access with the key `email`.
458458

459+
### Dependent Parameters
460+
If you want to receive and validate a parameter only if another one is given, you can use
461+
the `is_given` option.
462+
463+
```ruby
464+
some_action.request do |params|
465+
params.optional :label, type: :string
466+
params.required :description, type: :string, if_given: :label
467+
#...
468+
params.required :card_type, inclusion: %w(credit_card debit_card)
469+
params.required :ccv, if_given: { card_type: lambda { |value| value == 'credit_card' } }
470+
end
471+
```
472+
473+
On the example above, the param `description` will be only validated if the param `label` is present.
474+
RequestParamsValidation will use the method `blank?` to check that. On the other hand, the param
475+
`ccv` will only be validated if the param `type_card` is equal to the string `credit_card`.
476+
477+
Notice that if the global option `filter_params` is set to `true` (default behaviour), then the
478+
dependent parameters will be filtered from the `params object` if they haven't beeen validated.
479+
This way we make sure to only receive those parameters that have been validated against our request
480+
definitions.
481+
482+
Be aware that if you rename a param, then you should use the new name in the `if_given` option.
483+
459484
---
460485
### NOTE
461486

lib/request_params_validation/definitions/param.rb

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module RequestParamsValidation
44
module Definitions
55
class Param
66
attr_reader :key, :required, :allow_blank, :type, :rename_as, :transform, :decimal_precision,
7-
:inclusion, :length, :value, :format, :custom_validation, :elements
7+
:inclusion, :length, :value, :format, :custom_validation, :if_given, :elements
88

99
def initialize(options, &block)
1010
@key = options[:key]
@@ -23,6 +23,7 @@ def initialize(options, &block)
2323
@value = build_value_option(options[:value])
2424
@format = build_format_option(options[:format])
2525
@custom_validation = build_custom_validation_option(options[:validate])
26+
@if_given = build_if_given_option(options[:if_given])
2627

2728
@elements = build_elements_option(options[:elements], &block)
2829
@sub_definition = build_sub_definition(&block)
@@ -82,6 +83,18 @@ def transform?
8283
!!@transform
8384
end
8485

86+
def skip?(request_params)
87+
return false unless @if_given
88+
89+
if_given_param_value = request_params[@if_given.param]
90+
91+
if @if_given.function
92+
!@if_given.function.call(if_given_param_value)
93+
else
94+
if_given_param_value.blank?
95+
end
96+
end
97+
8598
private
8699

87100
def build_inclusion_option(inclusion)
@@ -158,6 +171,20 @@ def build_custom_validation_option(validation)
158171
Struct.new(:function, :message).new(function, message)
159172
end
160173

174+
def build_if_given_option(if_given)
175+
case if_given
176+
when String, Symbol
177+
param = if_given.to_sym
178+
when Hash
179+
param = if_given.first.try(:first)
180+
function = if_given.first.try(:last)
181+
end
182+
183+
return unless param
184+
185+
Struct.new(:param, :function).new(param, function)
186+
end
187+
161188
def build_elements_option(elements, &block)
162189
return unless @type == Params::ARRAY_TYPE
163190

lib/request_params_validation/params.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ module Params
77

88
def self.validate!(definition, params)
99
definition.each do |param_definition|
10+
next if param_definition.skip?(params)
11+
1012
validate_and_coerce_param(param_definition, params)
1113
end
1214

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

4446
params_keys = definition.map do |param_definition|
47+
next if param_definition.skip?(params)
48+
4549
key = param_definition.rename? ? param_definition.rename_as : param_definition.key
4650

4751
if param_definition.sub_definition
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
RSpec.describe ApplicationController, type: :controller do
2+
describe 'if_given option' do
3+
let(:define_params) do
4+
lambda do |params|
5+
params.optional :key_1, as: key_1_rename
6+
params.required :key_2, if_given: if_given_option
7+
end
8+
end
9+
10+
let(:if_given_option) { :key_1 }
11+
let(:key_1_rename) { nil }
12+
13+
let(:key_1_value) { 'key 1 value' }
14+
let(:key_2_value) { 'key 2 value' }
15+
16+
let(:request_params) { { key_1: key_1_value, key_2: key_2_value } }
17+
18+
before { post :dummy, body: request_params.to_json, as: :json }
19+
20+
it { expect(controller.params[:key_1]).to eq(key_1_value) }
21+
it { expect(controller.params[:key_2]).to eq(key_2_value) }
22+
23+
context 'when key_2 is not present' do
24+
let(:key_2_value) { [nil, ''].sample }
25+
26+
it 'returns the correct error message' do
27+
expect(response.body).to eq(build_error_response(:missing_param, param_key: :key_2))
28+
end
29+
end
30+
31+
context 'when key_1 is not present' do
32+
let(:key_1_value) { [nil, ''].sample }
33+
34+
it { expect(controller.params[:key_1]).to eq(key_1_value) }
35+
it { expect(controller.params.key?(:key_2)).to eq(false) }
36+
37+
context 'when key_2 is not present' do
38+
let(:key_2_value) { [nil, ''].sample }
39+
40+
it { expect(controller.params[:key_1]).to eq(key_1_value) }
41+
it { expect(controller.params.key?(:key_2)).to eq(false) }
42+
end
43+
end
44+
45+
context 'when key_1 has been renamed' do
46+
let(:key_1_rename) { :renamed_key_1 }
47+
48+
it { expect(controller.params[:renamed_key_1]).to eq(key_1_value) }
49+
it { expect(controller.params.key?(:key_2)).to eq(false) }
50+
51+
context 'when if_given_option matches with the renamed key' do
52+
let(:if_given_option) { :renamed_key_1 }
53+
54+
it { expect(controller.params[:renamed_key_1]).to eq(key_1_value) }
55+
it { expect(controller.params[:key_2]).to eq(key_2_value) }
56+
end
57+
end
58+
59+
context 'when if_given_option has custom matcher' do
60+
let(:if_given_option) { { key_1: -> (val) { val == 'expected value' } } }
61+
62+
let(:key_1_value) { 'expected value' }
63+
64+
it { expect(controller.params[:key_1]).to eq(key_1_value) }
65+
it { expect(controller.params[:key_2]).to eq(key_2_value) }
66+
67+
context 'when key_2 is not present' do
68+
let(:key_2_value) { [nil, ''].sample }
69+
70+
it 'returns the correct error message' do
71+
expect(response.body).to eq(build_error_response(:missing_param, param_key: :key_2))
72+
end
73+
end
74+
75+
context 'when key_1 has not the expected value of the option if_given' do
76+
let(:key_1_value) { 'not expected value' }
77+
78+
it { expect(controller.params[:key_1]).to eq(key_1_value) }
79+
it { expect(controller.params.key?(:key_2)).to eq(false) }
80+
81+
context 'when key_2 is not present' do
82+
it { expect(controller.params[:key_1]).to eq(key_1_value) }
83+
it { expect(controller.params.key?(:key_2)).to eq(false) }
84+
end
85+
end
86+
end
87+
end
88+
end

0 commit comments

Comments
 (0)