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)
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