paramoid 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f528551c46f70a5d476cb1ddbbeb9e59687a7153112c8962a026ed896b6ba343
4
+ data.tar.gz: ebd4e70f7a7987afcc282db3ef47fbda33841be91e97d98e85874bc039c8047d
5
+ SHA512:
6
+ metadata.gz: d4ab2dfeb44387a4c6bcf7de2aa05832d8efd792e4cefdf867039bc51b1c044554575b005f2b7c10b7fe82d7bfc1e5db0dab6dda4d34fd59256ea52c7721fff8
7
+ data.tar.gz: 43ab66b64849be0b6b5879705f58ed4a337729c15d0aa7313d8db9e6671356fa936b6f06d1e0e955b17d9b4386ef9382aabd386adfd839847aff5df886d2d64a
@@ -0,0 +1,84 @@
1
+ module Paramoid
2
+ class Base
3
+ # @param [ActionController::Parameters] params
4
+ def sanitize(params)
5
+ params = params.permit(*permitted_params)
6
+ scalar_params.transform_params!(params)
7
+ params = default_params.merge(params)
8
+ ensure_required_params!(params)
9
+ end
10
+
11
+ protected
12
+
13
+ # @param [Symbol] name
14
+ # @param [Symbol] as
15
+ # @param [Lambda | NilClass] transformer
16
+ def group!(name, as: nil, transformer: nil)
17
+ key = as || name
18
+ data = Object.new(name, key, nested: List.new, transformer: transformer)
19
+ context << data
20
+ return unless block_given?
21
+
22
+ old_context = context
23
+ @context = data
24
+ yield
25
+ @context = old_context
26
+ end
27
+
28
+ alias list! group!
29
+ alias array! group!
30
+
31
+ # @param [Array<Symbol>] names
32
+ def params!(*names, required: false)
33
+ names.each { |name| param! name, required: required }
34
+ end
35
+
36
+ def param!(name, as: nil, transformer: nil, default: nil, required: false)
37
+ key = as || name
38
+ data = Object.new(name, key, nested: nil, default: default, transformer: transformer, required: required)
39
+ context << data
40
+ end
41
+
42
+ def default!(name, value)
43
+ data = Object.new(name, name, nested: nil, default: value)
44
+ context << data
45
+ end
46
+
47
+ private
48
+
49
+ def context
50
+ @context ||= scalar_params
51
+ end
52
+
53
+ def default_params
54
+ scalar_params.to_defaults
55
+ # @default_params ||= {}.with_indifferent_access
56
+ end
57
+
58
+ def scalar_params
59
+ @scalar_params ||= List.new
60
+ end
61
+
62
+ def ensure_required_params!(params)
63
+ scalar_params.ensure_required_params!(params)
64
+ params
65
+ end
66
+
67
+ def transformers
68
+ @transformers ||= {}.with_indifferent_access
69
+ end
70
+
71
+ def permitted_params
72
+ scalar_params.to_params
73
+ end
74
+
75
+ def transformed_keys
76
+ @transformed_keys ||= scalar_params
77
+ end
78
+
79
+ # @param [ActionController::Parameters] params
80
+ def run_transformers!(params)
81
+ @transformers.each { |key, t| params[key] = t.call(params[key]) }
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,27 @@
1
+ require 'action_controller'
2
+
3
+ module Paramoid
4
+ module Controller
5
+ extend ActiveSupport::Concern
6
+
7
+ def sanitize_params!(&block)
8
+ if block_given?
9
+ sanitized = Paramoid::Base.new
10
+ sanitized.instance_exec(_paramoid_safe_current_user, &block)
11
+ sanitized.sanitize(params)
12
+ else
13
+ base_class_name = self.class.name.demodulize.gsub('Controller', '').singularize
14
+
15
+ "#{base_class_name}ParamsSanitizer".safe_constantize&.new(_paramoid_safe_current_user)&.sanitize(params)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def _paramoid_safe_current_user
22
+ respond_to?(:current_user, true) ? current_user : nil
23
+ end
24
+ end
25
+ end
26
+
27
+ ActionController::Base.include Paramoid::Controller if defined?(ActionController::Base)
@@ -0,0 +1,25 @@
1
+ module Paramoid
2
+ class List < Array
3
+ def to_params
4
+ list = reject(&:nested?).map(&:to_params)
5
+ nested = select(&:nested?).inject({}) { |a, b| a.merge!(b.to_params) }
6
+ nested.present? ? list << nested : list
7
+ end
8
+
9
+ def transform_params!(params)
10
+ each do |params_data|
11
+ params_data.transform_params! params
12
+ end
13
+ end
14
+
15
+ def to_defaults
16
+ inject({}) { |a, b| a.merge!(b.to_defaults) }
17
+ end
18
+
19
+ def ensure_required_params!(params)
20
+ each do |params_data|
21
+ params_data.ensure_required_params! params
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,94 @@
1
+ module Paramoid
2
+ class Object
3
+ # @return [Symbol] the parameter name
4
+ attr_reader :name
5
+ # @return [Symbol] the parameter alias
6
+ attr_reader :alias_name
7
+ # @return [Object] the parameter nested
8
+ attr_reader :nested
9
+ # @return [Object] the parameter default value
10
+ attr_reader :default
11
+ # @return [Lambda, NilClass] the transformer
12
+ attr_reader :transformer
13
+ # @return [TrueClass, FalseClass] required
14
+ attr_reader :required
15
+
16
+ # @param [Symbol] name
17
+ # @param [Symbol] alias_name
18
+ # @param [Object, NilClass] nested
19
+ # @param [Object] default
20
+ # @param [Lambda, NilClass] transformer
21
+ # @param [TrueClass, FalseClass] required
22
+ def initialize(name, alias_name, nested: nil, transformer: nil, default: nil, required: false)
23
+ @name = name
24
+ @alias = alias_name
25
+ @nested = nested
26
+ @default = default
27
+ @transformer = transformer
28
+ @required = required
29
+ end
30
+
31
+ # @param [Array, Hash] params
32
+ def transform_params!(params)
33
+ return if @alias == @name
34
+
35
+ if params.is_a?(Array)
36
+ params.each { |param| transform_params!(param) }
37
+ return
38
+ end
39
+
40
+ return unless params.key?(@name)
41
+
42
+ @nested.transform_params!(params[@name]) if nested?
43
+ params[@alias] = params.delete(@name) unless @alias == @name
44
+ end
45
+
46
+ def to_params
47
+ if nested?
48
+ { @name.to_sym => @nested.to_params }
49
+ else
50
+ @name
51
+ end
52
+ end
53
+
54
+ def to_required_params
55
+ if nested?
56
+ @nested.to_required_params
57
+ else
58
+ @required ? [@name] : []
59
+ end
60
+ end
61
+
62
+ def ensure_required_params!(params)
63
+ if @required
64
+ raise ActionController::ParameterMissing, output_key unless params&.key?(output_key)
65
+ raise ActionController::ParameterMissing, output_key if params[output_key].nil?
66
+ end
67
+
68
+ @nested.ensure_required_params!(params[output_key]) if nested?
69
+
70
+ params
71
+ end
72
+
73
+ def to_defaults
74
+ if nested?
75
+ nested_defaults = @nested.to_defaults
76
+ (nested_defaults.present? ? { output_key => @nested.to_defaults } : {}).with_indifferent_access
77
+ else
78
+ (@default ? { output_key => @default } : {}).with_indifferent_access
79
+ end
80
+ end
81
+
82
+ def output_key
83
+ @output_key ||= (@alias || @name).to_s
84
+ end
85
+
86
+ def <<(value)
87
+ @nested << value
88
+ end
89
+
90
+ def nested?
91
+ !@nested.nil?
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,3 @@
1
+ module Paramoid
2
+ VERSION = '1.0.0'
3
+ end
data/lib/paramoid.rb ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Paramoid
4
+ extend ActiveSupport::Autoload
5
+
6
+ autoload :Object
7
+ autoload :List
8
+ autoload :Base
9
+ # autoload :Controller
10
+ end
11
+
12
+ require 'paramoid/controller'
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ describe PersonParamsSanitizer, type: :controller do
4
+ let(:params) do
5
+ ActionController::Parameters.new(params_hash)
6
+ end
7
+
8
+ subject { described_class.new(user) }
9
+ let(:sanitized) { subject.sanitize(params) }
10
+
11
+ describe 'when params are valid' do
12
+ let(:user) { double(admin?: false) }
13
+ let(:params_hash) do
14
+ {
15
+ current_user_id: 2,
16
+ first_name: 'John',
17
+ last_name: 'Doe',
18
+ # TODO: Implement transformers
19
+ # email: 'Hello@MyCustomMAIL.COM',
20
+ role: 'some_role',
21
+ unwanted: 'hello',
22
+ an_object_filtered: { name: 'value' },
23
+ an_array_filtered: [{ name: 'value' }],
24
+ an_array_unfiltered: [1, 2, 3, 4, 5]
25
+ }
26
+ end
27
+
28
+ it 'filters out unwanted params' do
29
+ expect(sanitized).not_to have_key(:unwanted)
30
+ end
31
+
32
+ it 'filters out object values if it\'s not a group' do
33
+ expect(sanitized).not_to have_key('an_object_filtered')
34
+ end
35
+
36
+ it 'filters out object values if it\'s not an array' do
37
+ expect(sanitized).not_to have_key('an_array_filtered')
38
+ end
39
+
40
+ it 'keeps only allowed params' do
41
+ expect(sanitized).to eq(
42
+ {
43
+ 'current_user_id' => 2,
44
+ 'first_name' => 'John',
45
+ 'last_name' => 'Doe',
46
+ # TODO: Implement transformers
47
+ # 'email' => 'hello@mycustommail.com',
48
+ 'some_default' => 1,
49
+ 'an_array_unfiltered' => [1, 2, 3, 4, 5]
50
+ }
51
+ )
52
+ end
53
+
54
+ context 'when the required value is not set' do
55
+ let(:params_hash) do
56
+ {}
57
+ end
58
+ it 'raises an error' do
59
+ expect { sanitized }.to raise_error(ActionController::ParameterMissing)
60
+ end
61
+ end
62
+
63
+ context 'when the default is set' do
64
+ let(:params_hash) do
65
+ { some_default: 2, current_user_id: 1 }
66
+ end
67
+ let 'it replaces the default value' do
68
+ expect(sanitized['some_default']).to eq(2)
69
+ end
70
+ end
71
+
72
+ context 'when the user is an admin' do
73
+ let(:user) { double(admin?: true) }
74
+
75
+ it 'keeps role param' do
76
+ expect(sanitized).to have_key('role')
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'controllers' do
4
+ let(:params_hash) do
5
+ {
6
+ current_user_id: 2,
7
+ first_name: 'John',
8
+ last_name: 'Doe',
9
+ # TODO: Implement transformers
10
+ # email: 'Hello@MyCustomMAIL.COM',
11
+ role: 'some_role',
12
+ unwanted: 'hello',
13
+ an_object_filtered: { name: 'value' },
14
+ an_array_filtered: [{ name: 'value' }],
15
+ an_array_unfiltered: [1, 2, 3, 4, 5]
16
+ }
17
+ end
18
+
19
+ describe PeopleInlineController, type: :controller do
20
+ let(:params) do
21
+ ActionController::Parameters.new(params_hash)
22
+ end
23
+
24
+ it 'sanitizes params in a block' do
25
+ allow_any_instance_of(described_class).to receive(:params).and_return(params)
26
+ expect_any_instance_of(Paramoid::Base).to receive(:instance_exec).and_call_original
27
+
28
+ subject.index
29
+ end
30
+ end
31
+
32
+ describe PeopleController, type: :controller do
33
+ let(:params) do
34
+ ActionController::Parameters.new(params_hash)
35
+ end
36
+
37
+ it 'sanitizes params in a block' do
38
+ allow_any_instance_of(described_class).to receive(:params).and_return(params)
39
+ expect_any_instance_of(PersonParamsSanitizer).to receive(:sanitize)
40
+ subject.index
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,123 @@
1
+ require 'spec_helper'
2
+
3
+ describe Paramoid::Object do
4
+ let(:name) { :some_param }
5
+ let(:alias_name) { :some_param }
6
+ let(:required) { false }
7
+ let(:default) { nil }
8
+ let(:transformer) { nil }
9
+ let(:nested) { nil }
10
+ subject do
11
+ described_class.new(name, alias_name, nested: nested, transformer: transformer, default: default,
12
+ required: required)
13
+ end
14
+ let(:sanitized) { subject.sanitize(params) }
15
+
16
+ describe '#to_params' do
17
+ it 'returns the name' do
18
+ expect(subject.to_params).to eq(name)
19
+ end
20
+
21
+ context 'when it\'s nested' do
22
+ let(:nested) { described_class.new(:nested, nil) }
23
+
24
+ it 'returns the child' do
25
+ expect(subject.to_params).to eq({ name => nested.to_params })
26
+ end
27
+ end
28
+ end
29
+
30
+ describe '#to_defaults' do
31
+ it 'returns an empty value' do
32
+ expect(subject.to_defaults).to eq({})
33
+ end
34
+
35
+ context 'when there\'s a default' do
36
+ let(:default) { 'some_default' }
37
+
38
+ it 'returns the default value' do
39
+ expect(subject.to_defaults).to eq({ name.to_s => default })
40
+ end
41
+
42
+ context 'when there\'s an alias' do
43
+ let(:alias_name) { :alias_name }
44
+
45
+ it 'returns the default value with the alias' do
46
+ expect(subject.to_defaults).to eq({ alias_name.to_s => default })
47
+ end
48
+ end
49
+ end
50
+
51
+ context 'when it\'s nested' do
52
+ let(:nested) { described_class.new(:nested, :nested, default: 'my_child_default') }
53
+
54
+ it 'returns the child' do
55
+ expect(subject.to_defaults).to eq({ name.to_s => nested.to_defaults })
56
+ end
57
+ end
58
+ end
59
+
60
+ describe '#to_required_params' do
61
+ it 'returns an empty list' do
62
+ expect(subject.to_required_params).to eq([])
63
+ end
64
+
65
+ context 'when it\'s required' do
66
+ let(:required) { true }
67
+
68
+ it 'returns the name' do
69
+ expect(subject.to_required_params).to eq([name])
70
+ end
71
+ end
72
+
73
+ context 'when it\'s nested' do
74
+ let(:nested) { described_class.new(:nested, nil, required: true) }
75
+
76
+ it 'returns the child' do
77
+ expect(subject.to_params).to eq({ name => nested.to_params })
78
+ end
79
+ end
80
+ end
81
+
82
+ describe '#ensure_required_params!' do
83
+ let(:params_hash) { { 'not_this_param' => 'some_value' } }
84
+ let(:params) { ActionController::Parameters.new(params_hash) }
85
+
86
+ it 'returns the params' do
87
+ expect(subject.ensure_required_params!(params)).to eq(params)
88
+ end
89
+
90
+ context 'when it\'s required' do
91
+ let(:required) { true }
92
+
93
+ it 'raises an error' do
94
+ expect do
95
+ subject.ensure_required_params!(params)
96
+ end.to raise_error(ActionController::ParameterMissing,
97
+ 'param is missing or the value is empty: some_param')
98
+ end
99
+ end
100
+
101
+ context 'when it\'s nested' do
102
+ let(:nested) { described_class.new(:nested, nil, required: true) }
103
+
104
+ it 'raises an error' do
105
+ expect do
106
+ subject.ensure_required_params!(params)
107
+ end.to raise_error(ActionController::ParameterMissing,
108
+ 'param is missing or the value is empty: nested')
109
+ end
110
+
111
+ context 'and it\'s required' do
112
+ let(:required) { true }
113
+
114
+ it 'raises an error on the parent param' do
115
+ expect do
116
+ subject.ensure_required_params!(params)
117
+ end.to raise_error(ActionController::ParameterMissing,
118
+ 'param is missing or the value is empty: some_param')
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,11 @@
1
+ require 'action_controller'
2
+ require 'rspec'
3
+ require 'paramoid'
4
+
5
+ I18n.enforce_available_locales = false
6
+ RSpec::Expectations.configuration.warn_about_potential_false_positives = false
7
+
8
+ Dir[File.expand_path('support/*.rb', __dir__)].each { |f| require f }
9
+
10
+ RSpec.configure do |config|
11
+ end
@@ -0,0 +1,32 @@
1
+ require 'ostruct'
2
+
3
+ class PeopleInlineController < ActionController::Base
4
+ def index
5
+ create_params
6
+ end
7
+
8
+ protected
9
+
10
+ def create_params
11
+ sanitize_params! do
12
+ params! :first_name, :last_name
13
+ param! :email, transformer: ->(data) { data&.downcase }
14
+ end
15
+ end
16
+ end
17
+
18
+ class PeopleController < ActionController::Base
19
+ def index
20
+ create_params
21
+ end
22
+
23
+ protected
24
+
25
+ def current_user
26
+ OpenStruct.new(admin?: true)
27
+ end
28
+
29
+ def create_params
30
+ sanitize_params!
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ class PersonParamsSanitizer < Paramoid::Base
2
+ # @param [User] user
3
+ def initialize(user = nil)
4
+ params! :first_name, :last_name, :gender
5
+
6
+ param! :email, transformer: ->(data) { data&.downcase }
7
+
8
+ param! :current_user_id, required: true
9
+
10
+ param! :an_object_filtered
11
+ param! :an_array_filtered
12
+
13
+ array! :an_array_unfiltered
14
+
15
+ param! :role if user.admin?
16
+
17
+ default! :some_default, 1
18
+
19
+ group! :contact, as: :contact_attributes do
20
+ params! :id, :first_name, :last_name, :birth_date, :birth_place, :phone, :role, :fiscal_code
21
+ end
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: paramoid
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Mònade
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-05-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: actionpack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '8'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '5'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '8'
33
+ - !ruby/object:Gem::Dependency
34
+ name: activesupport
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '5'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '8'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '5'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '8'
53
+ - !ruby/object:Gem::Dependency
54
+ name: rspec
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '3'
60
+ type: :development
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '3'
67
+ - !ruby/object:Gem::Dependency
68
+ name: rubocop
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ description: Paramoid is a gem that extends Rails Strong Parameters, allowing to declare
82
+ complex params structures with a super cool DSL, supporting required params, default
83
+ values, groups, arrays and more.
84
+ email: team@monade.io
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - lib/paramoid.rb
90
+ - lib/paramoid/base.rb
91
+ - lib/paramoid/controller.rb
92
+ - lib/paramoid/list.rb
93
+ - lib/paramoid/object.rb
94
+ - lib/paramoid/version.rb
95
+ - spec/paramoid/base_spec.rb
96
+ - spec/paramoid/controller_spec.rb
97
+ - spec/paramoid/object_spec.rb
98
+ - spec/spec_helper.rb
99
+ - spec/support/controllers.rb
100
+ - spec/support/params.rb
101
+ homepage: https://rubygems.org/gems/paramoid
102
+ licenses:
103
+ - MIT
104
+ metadata: {}
105
+ post_install_message:
106
+ rdoc_options: []
107
+ require_paths:
108
+ - lib
109
+ required_ruby_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: 2.7.0
114
+ required_rubygems_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ requirements: []
120
+ rubygems_version: 3.2.33
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: Getting paranoid about your Rails application params? Try paramoid!
124
+ test_files:
125
+ - spec/paramoid/base_spec.rb
126
+ - spec/paramoid/controller_spec.rb
127
+ - spec/paramoid/object_spec.rb
128
+ - spec/spec_helper.rb
129
+ - spec/support/controllers.rb
130
+ - spec/support/params.rb