ruby-on-railsstrong-parameters

Rails: Accept 2D array of strings with strong parameters


We have a Rails controller that gets the following data:

params = ActionController::Parameters.new({
    "requests": [{
        "params": {
            "facets": ["user.id", "user.type"],
            "facetFilters": [
                ["user.type:Individual"]
            ]
        }
    }, {
        "params": {
            "facets": "user.type"
        }
    }]
})

We want to use strong parameters to accept this data, but I haven't yet seen a pattern that will let us accept the 2D array in facetFilters. I'm tinkering with the following:

    params[:requests].each do |request|
      request[:permitted] = true
      request[:params].each do |o|
        if ['facets', 'facetFilters'].include?(o.first)
          begin
            o[:permitted] = true 
          rescue
          end
        end
      end
    end

As one can see not all attributes are permitted (the permitted attributes don't get passed to children elements), as this returns:

[<ActionController::Parameters {"params"=><ActionController::Parameters {"facets"=>["user.id", "user.type"], "facetFilters"=>[["user.type:Individual"]]} permitted: false>, "permitted"=>true} permitted: false>, <ActionController::Parameters {"params"=><ActionController::Parameters {"facets"=>"user.type"} permitted: false>, "permitted"=>true} permitted: false>]

And there are lots of permitted: false in there...

Is it possible to accomplish this goal? Any pointers would be super helpful!


Solution

  • The best way I've found to get this done is to convert the 2D array to a 1D array, permit the 1D array, and then convert the array back to 2D.

    nested_facet_filters = []
    (params[:requests] || []).each_with_index do |r, ridx|
      if r[:params].key?(:facetFilters) && r[:params][:facetFilters].kind_of?(Array) && r[:params][:facetFilters].first.kind_of?(Array)
        # flatten the facet filters into a 1D array because Rails can't permit a 2D array
        nested_facet_filters << ridx
        r[:params][:facetFilters] = r[:params][:facetFilters].flatten
      end
    end
    
    permitted = params.permit({
      requests: [
        :query,
        :params => [
          :facets => [],
          :facetFilters => [],
        ]
      ]
    }).to_h
    
    # after permitting the params, we need to convert the facetFilters back into a 2D array
    if nested_facet_filters.present?
      nested_facet_filters.each do |idx|
        # create map from facet key to array of values
        d = {}
        permitted[:requests][idx][:params][:facetFilters].each do |s|
          split = s.split(':')
          facet = split.first
          value = split[1..-1].join(':')
          if d.key?(facet)
            d[facet] << s
          else
            d[facet] = [s]
          end
        end
        # mutate facetFilters back to 2D array for posting to Algolia
        permitted[:requests][idx][:params][:facetFilters] = d.values
      end
    end
    

    There's been a merge request to accept 2D arrays for six years open in the Rails issue tracker [thread]...