Skip to content

Commit

Permalink
feat: preparing for version 2
Browse files Browse the repository at this point in the history
BREAKING CHANGES:
- All methods can now be called without ! (param, params, default, etc...)
- `array` and `group` are not identical anymore. `array` will accepts an array of values, while `group` will accept a single hash.
- Removed to_defaults, replacing it with apply_defaults!
  • Loading branch information
ProGM committed Sep 27, 2023
1 parent 7a324d3 commit ef0aa69
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 40 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
# Changelog
All notable changes to this project made by Monade Team are documented in this file. For info refer to [email protected]

## [2.0.0-BETA] - 2023-09-27
### Added
- `transformer` option to param, that allows to transform the value received from a parameter before assigning it to the sanitized hash.
- `default` now works also with nested structures.

### Changed
- [BREAKING] All methods can now be called without ! (param, params, default, etc...)
- [BREAKING] `array` and `group` are not identical anymore. `array` will accepts an array of values, while `group` will accept a single hash.
- [BREAKING] internal, removed to_defaults, replacing it with apply_defaults!

### Fixed
- Missing params in nested structures are now reported correctly


## [1.0.0] - 2022-05-28
### Added
- First release
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,6 @@ end

## TODOs
* Params type checking and regexp-based validations
* Value transformers

About Monade
----------------
Expand Down
1 change: 0 additions & 1 deletion lib/paramoid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ module Paramoid
autoload :Object
autoload :List
autoload :Base
# autoload :Controller
end

require 'paramoid/controller'
48 changes: 31 additions & 17 deletions lib/paramoid/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ class Base
# @param [ActionController::Parameters] params
def sanitize(params)
params = params.permit(*permitted_params)
scalar_params.transform_params!(params)
params = default_params.merge(params)
context.transform_params!(params)
context.apply_defaults!(params)
ensure_required_params!(params)
end

Expand All @@ -13,39 +13,53 @@ def sanitize(params)
# @param [Symbol] name
# @param [Symbol] as
# @param [Lambda | NilClass] transformer
def group!(name, as: nil, transformer: nil)
key = as || name
data = Object.new(name, key, nested: List.new, transformer: transformer)
context << data
return unless block_given?
def group(name, as: nil, transformer: nil, &block)
_nest_rule(name, as: as, transformer: transformer, nesting_type: :object, &block)
end

old_context = context
@context = data
yield
@context = old_context
def list(name, as: nil, transformer: nil, &block)
_nest_rule(name, as: as, transformer: transformer, nesting_type: :list, &block)
end

alias list! group!
alias array! group!
alias array list

# @param [Array<Symbol>] names
def params!(*names, required: false)
names.each { |name| param! name, required: required }
def params(*names, required: false)
names.each { |name| param name, required: required }
end

def param!(name, as: nil, transformer: nil, default: nil, required: false)
def param(name, as: nil, transformer: nil, default: nil, required: false)
key = as || name
data = Object.new(name, key, nested: nil, default: default, transformer: transformer, required: required)
context << data
end

def default!(name, value)
def default(name, value)
data = Object.new(name, name, nested: nil, default: value)
context << data
end

alias param! param
alias params! params
alias group! group
alias default! default
alias array! array
alias list! list

private

def _nest_rule(name, nesting_type:, as: nil, transformer: nil)
key = as || name
data = Object.new(name, key, nested: List.new, nesting_type: nesting_type, transformer: transformer)
context << data
return unless block_given?

old_context = context
@context = data
yield
@context = old_context
end

def context
@context ||= scalar_params
end
Expand Down
13 changes: 11 additions & 2 deletions lib/paramoid/list.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module Paramoid
class List < Array
def to_params
Expand All @@ -12,13 +14,20 @@ def transform_params!(params)
end
end

def apply_defaults!(params)
each do |params_data|
params = params_data.apply_defaults! params
end
params
end

def to_defaults
inject({}) { |a, b| a.merge!(b.to_defaults) }
end

def ensure_required_params!(params)
def ensure_required_params!(params, path: [])
each do |params_data|
params_data.ensure_required_params! params
params_data.ensure_required_params! params, path: path
end
end
end
Expand Down
74 changes: 65 additions & 9 deletions lib/paramoid/object.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
module Paramoid
# rubocop:disable Metrics/ClassLength
class Object
# @return [Symbol] the parameter name
attr_reader :name
Expand All @@ -19,26 +20,27 @@ class Object
# @param [Object] default
# @param [Lambda, NilClass] transformer
# @param [TrueClass, FalseClass] required
def initialize(name, alias_name, nested: nil, transformer: nil, default: nil, required: false)
def initialize(name, alias_name, nested: nil, nesting_type: nil, transformer: nil, default: nil, required: false)
@name = name
@alias = alias_name
@nested = nested
@default = default
@transformer = transformer
@required = required
@nesting_type = nesting_type
end

# @param [Array, Hash] params
def transform_params!(params)
return if @alias == @name

if params.is_a?(Array)
params.each { |param| transform_params!(param) }
return
end

return unless params.key?(@name)

params[@name] = @transformer.call(params[@name]) if @transformer.respond_to?(:call)

@nested.transform_params!(params[@name]) if nested?
params[@alias] = params.delete(@name) unless @alias == @name
end
Expand All @@ -59,21 +61,63 @@ def to_required_params
end
end

def ensure_required_params!(params)
if @required
raise ActionController::ParameterMissing, output_key unless params&.key?(output_key)
raise ActionController::ParameterMissing, output_key if params[output_key].nil?
def ensure_required_params!(params, path: [])
current_path = [*path, @name]

_raise_on_missing_parameter!(params, output_key, current_path) if @required

if nested?
if params.is_a?(Array)
params.each { |param| @nested.ensure_required_params!(param[output_key], path: current_path) }
else
@nested.ensure_required_params!(params ? params[output_key] : nil, path: current_path)
end
end

@nested.ensure_required_params!(params[output_key]) if nested?
params
end

def apply_defaults!(params)
return apply_nested_defaults!(params) if nested?

return params unless @default

return params if @nesting_type == :list && !params

params ||= {}
apply_scalar_defaults!(params)

params
end

def apply_scalar_defaults!(params)
if params.is_a?(Array)
params.map! { |param| apply_scalar_defaults!(param) }
else
params[output_key] ||= @default
end
params
end

def apply_nested_defaults!(params)
return params unless params

params ||= @nesting_type == :list ? [] : {}
if params.is_a?(Array)
params.map! { |param| @nested.apply_defaults!(param[output_key]) }
else
return params unless params[output_key]

result = @nested.apply_defaults!(params[output_key])
params[output_key] = result if result
end
params
end

def to_defaults
if nested?
nested_defaults = @nested.to_defaults
(nested_defaults.present? ? { output_key => @nested.to_defaults } : {}).with_indifferent_access
(nested_defaults.present? ? { output_key => nested_defaults } : {}).with_indifferent_access
else
(@default ? { output_key => @default } : {}).with_indifferent_access
end
Expand All @@ -90,5 +134,17 @@ def <<(value)
def nested?
!@nested.nil?
end

private

def _raise_on_missing_parameter!(params, key, current_path)
return if _param_exist?(params, key)

raise ActionController::ParameterMissing, current_path.join('.')
end

def _param_exist?(params, key)
params&.key?(key) && !params[key].nil?
end
end
end
15 changes: 9 additions & 6 deletions spec/paramoid/base_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'spec_helper'

# rubocop:disable Metrics/BlockLength
describe PersonParamsSanitizer, type: :controller do
let(:params) do
ActionController::Parameters.new(params_hash)
Expand All @@ -15,8 +16,7 @@
current_user_id: 2,
first_name: 'John',
last_name: 'Doe',
# TODO: Implement transformers
# email: '[email protected]',
email: '[email protected]',
role: 'some_role',
unwanted: 'hello',
an_object_filtered: { name: 'value' },
Expand All @@ -38,13 +38,12 @@
end

it 'keeps only allowed params' do
expect(sanitized).to eq(
expect(sanitized.to_unsafe_h).to eq(
{
'current_user_id' => 2,
'first_name' => 'John',
'last_name' => 'Doe',
# TODO: Implement transformers
# 'email' => '[email protected]',
'email' => '[email protected]',
'some_default' => 1,
'an_array_unfiltered' => [1, 2, 3, 4, 5]
}
Expand All @@ -56,7 +55,10 @@
{}
end
it 'raises an error' do
expect { sanitized }.to raise_error(ActionController::ParameterMissing)
expect { sanitized }.to raise_error(
ActionController::ParameterMissing,
'param is missing or the value is empty: current_user_id'
)
end
end

Expand All @@ -78,3 +80,4 @@
end
end
end
# rubocop:enable Metrics/BlockLength
Loading

0 comments on commit ef0aa69

Please sign in to comment.