ruby-on-railsstrong-parameters

Declare that the value in strong params must NOT be an array or of a one scalar type


Application accepts a GET request to index products and accepts page param (which should be an integer).

e.g. http://localhost/products?page=2

Is there a way to ensure that it really is an integer?

Or actually the problem is: Is there a way to ensure that it is NOT an array?

e.g. http://localhost/products?page[]=2 or http://localhost/products?page[random_key]=2

This will result in an error somewhere in the code.

Now, I know that there is a way to declare it should be an array or a json:

To declare that the value in params must be an array of permitted scalar values, map the key to an empty array:

params.permit(id: [])

And the easiest method I can think of is literally to test if it is NOT an array:

run_the_code unless params[:page].is_a?(Array)
# or
run_the_code if params[:page].is_a?(Integer)

But isn't there a simpler way to use ActionParameters API for example?

e.g.

params.permit(page: :integer)

Solution

  • The strong parameters API is really only designed to do one thing - slice the parameters down to a whitelist to avoid mass assignment vulnerabilities where a malicous user manages to assign properties they should not have been able to assign. It's not concerned with validating the presence or type of the parameters. That's not its job.

    You don't have to worry about it being an array if you just use .permit(:page) since the permitted scalar types doesn't include hashes or arrays. But this really has more to with preventing exploits where a setter method could accept a hash then validating the type of the parameters.

    There is also the small fact that formdata pairs (both in the query and request body) are not typed - its all just strings and its really just the backend that converts the parameters into its expected types. params[:page].is_a?(Integer) will thus always be false. Beyond parsing out hashes and arrays Rack doesn't actually have any built in type hinting like for example ?page(i)=2 like you'll find in some frameworks.

    Typically in Rails the model handles typecasting the user input and providing validations.

    # this is ridiculously overkill
    class Paginator
      include ActiveModel::Model
      include ActiveModel::Attributes
      attribute :page, :integer
      attribute :per_page, :integer
      validates :page, numericality: { only_integer: true }
      validates :per_page, numericality: { only_integer: true }
    
      def to_query
        attributes.symbolize_keys.compact_blank
      end
    end
    
    paginator = Paginator.new(params.permit(:page, :per_page))
    if paginator.valid?
      Othermodel.paginate(**paginator.to_query)
    end
    

    However there are gems that provide alternative approaches such as dry-validations or you can just simply write a method which rejects the parameter if it cannot be coerced into a non-zero integer.

    private 
    
    def page
      params.permit(:page).to_i.then{ |n| n.zero? ? nil : n }
    end