diff --git a/lib/rails_param/param.rb b/lib/rails_param/param.rb index 5a294c0..09a6b9b 100644 --- a/lib/rails_param/param.rb +++ b/lib/rails_param/param.rb @@ -1,5 +1,14 @@ module RailsParam + attr_accessor :rails_params + def param!(name, type, options = {}, &block) - ParamEvaluator.new(params).param!(name, type, options, &block) + hierarchy = ParamEvaluator.new(params).param!(name, type, options, &block) + + @rails_params = + if params.is_a?(ActionController::Parameters) + params.permit(hierarchy) + else + params + end end end diff --git a/lib/rails_param/param_evaluator.rb b/lib/rails_param/param_evaluator.rb index ba0aa04..6bbdd0c 100644 --- a/lib/rails_param/param_evaluator.rb +++ b/lib/rails_param/param_evaluator.rb @@ -2,13 +2,14 @@ module RailsParam class ParamEvaluator attr_accessor :params - def initialize(params, context = nil) + def initialize(params, context = nil, hierarchy = nil) @params = params @context = context + @hierarchy = hierarchy || {} end def param!(name, type, options = {}, &block) - name = name.is_a?(Integer)? name : name.to_s + @child_key = name = name.is_a?(Integer)? name : name.to_s return unless params.include?(name) || check_param_presence?(options[:default]) || options[:required] parameter_name = @context ? "#{@context}[#{name}]" : name @@ -33,7 +34,11 @@ def param!(name, type, options = {}, &block) ) end - recurse_on_parameter(parameter, &block) if block_given? + update_hierarchy(type, name) + + if block_given? + @hierarchy[@child_key] = recurse_on_parameter(parameter, &block) + end # apply transformation parameter.transform if options[:transform] @@ -43,10 +48,40 @@ def param!(name, type, options = {}, &block) # set params value params[name] = parameter.value + + [@hierarchy, parameter.value] end private + def update_hierarchy(type, name) + # calculate next child type + @child = + if type == Array && !block_given? + [] + elsif type == Hash || type == Array + {} + else + name + end + + if type == Hash || type == Array + # intermediate nodes + if @hierarchy.is_a?(Hash) + @hierarchy[name] = @child + @child_key = name + else + @hierarchy << @child + @child_key = @hierarchy.size - 1 + end + else + # this is a leaf + @hierarchy = [] if @hierarchy.is_a?(Hash) + @hierarchy << name + @child_key = @hierarchy.size - 1 + end + end + def recurse_on_parameter(parameter, &block) return if parameter.value.nil? @@ -55,7 +90,8 @@ def recurse_on_parameter(parameter, &block) if element.is_a?(Hash) || element.is_a?(ActionController::Parameters) recurse element, "#{parameter.name}[#{i}]", &block else - parameter.value[i] = recurse({ i => element }, parameter.name, i, &block) # supply index as key unless value is hash + _, value = recurse({ i => element }, parameter.name, i, &block) # supply index as key unless value is hash + parameter.value[i] = value end end else @@ -66,7 +102,7 @@ def recurse_on_parameter(parameter, &block) def recurse(element, context, index = nil) raise InvalidParameterError, 'no block given' unless block_given? - yield(ParamEvaluator.new(element, context), index) + yield(ParamEvaluator.new(element, context, @hierarchy[@child_key]), index) end def check_param_presence? param diff --git a/spec/rails_param/param_spec.rb b/spec/rails_param/param_spec.rb index 0dc536a..86220a4 100644 --- a/spec/rails_param/param_spec.rb +++ b/spec/rails_param/param_spec.rb @@ -804,5 +804,52 @@ def params; end end end + + describe "permitting" do + it 'permits all nested attributes' do + input_params = { + mimmo: { + 'foo' => { 'bar' => BigDecimal(1), 'baz' => 2 }, + 'arr' => [1, 2, 3] + } + } + allow(controller).to receive(:params).and_return(ActionController::Parameters.new(input_params)) + safe_params = controller.param! :mimmo, Hash do |mimmo| + mimmo.param! :foo, Hash do |p| + p.param! :bar, BigDecimal + p.param! :baz, Float + end + mimmo.param! :arr, Array + end + + expect(safe_params).to be_permitted + expect(safe_params.to_h.with_indifferent_access).to eq({ + 'mimmo' => { + 'foo' => { 'bar' => BigDecimal(1), 'baz' => 2 }, + 'arr' => [1, 2, 3] + } + }) + end + + it 'permits only specified attributes' do + input_params = { + mimmo: { + 'foo' => { 'bar' => 1, 'baz' => 2 }, + 'arr' => [1, 2, 3] + } + } + allow(controller).to receive(:params).and_return(ActionController::Parameters.new(input_params)) + safe_params = controller.param! :mimmo, Hash do |mimmo| + mimmo.param! :arr, Array + end + + expect(safe_params).to be_permitted + expect(safe_params.to_h.with_indifferent_access).to eq({ + 'mimmo' => { + 'arr' => [1, 2, 3] + } + }) + end + end end end