Where do you check that "?page" is a number?

On this platform I am working with there is a case of automatic requests that are not something we can do much about. But they send requests as

https://www.example.com/articles?page=2%27A=0

and this causes an error on the side of the platform. This gets logged, an email is send it is monitored as an error, but it is not an error for the platform and the platform should just show page=1

The page is not a number. Where do you check that the page is a number and convert it to 1?

In different cases and platforms I have:

  1. Made the check in a concern that is then included in all controllers

  2. Passed the @articles.paginate(page: params[:page]) to the model and the model is doing the check and then I extract this into a concern for all models.

  3. I once had to make this check in rack even before the controller gets hit so that every “page” is automatically converted to a number if it is not a number.

My question is - where do you do it? Why you’ve decided to do it there?

What would happen if you just cast the param to an integer? @articles.paginate(page: params[:page].try(:to_i)). Also, which pagination system are you using? I have never seen this blow up for me when I use Kaminari, so maybe it’s doing something with that attribute before it tries to calculate the offset.

Walter

Will_paginate.

It happens in the index, eg

@materials = @materials.paginate(page: params[:page], per_page: 24)

Yes, params[:page].try(:to_i) is one option. But I need a more complex logic like converting it to one and this will be the same call for all index and it makes sense to have it in a concern that does this.

And this is option 1 from the question - extract the logic to a controller concern

The code for converting the param into a number seems trivial - i’d just put params[:page].to_i into something reusable like a helper or controller concern.

What that number needs to default to is a different issue - I’d duplicate the logic in the controller action if different endpoints have different defaults.

The code itself is trivial. Question is where do you put it. As I understand it you would put it in a controller concern and add this concern to all the controllers.

For those interested - it is a 9 years old discussion right here - Rails: ignore InvalidPage exception in production · Issue #271 · mislav/will_paginate · GitHub.

Sorry for not answering your question, but wanted to remark, that now that will_paginate is officially in maintenance mode it is about time to switch to another solution. I’ve refactored an app from will_paginate to pagy, which is more modern, faster and more powerful. The pagy site has a migration guide, it was not much work.

With pagy you will face a similar issue. There I solved it by adding this method to my ApplicationController

  def page
    p = params[:page].to_i # converts strings to 0 (or the number they start with)
    p.positive? ? p : 1 # when no valid page number is given, set page to 1
  end
1 Like

Thanks. Had to check the news about the maintenance mode myself and yes, it is true. Add note about maintenance mode · mislav/will_paginate@e75f547 · GitHub.

There are alternatives on the page. Will check them.

I ran into this issue just yesterday with Pagy. In my case, Pagy was raising an error if the page number was out of bounds. This was happening because a search engine crawler was hitting page numbers that no longer exist. It also raises for non-numeric page numbers.

Pagy seems to be of the opinion that methods should raise errors unless you give them exactly the arguments that are expected. This seems contrary to the Rails Way. Ruby itself, for example, returns nil rather than raise for a=[1,2,3]; a[5].

This is exacerbated by the fact that, unlike Kaminari, Pagy accesses params[:page] behind the scenes so there is no opportunity to restrict the input; the only option is to rescue.

No, pagy offers “extras” and one of them deals with these number overflows:

Just add this to your initializer:

require "pagy/extras/overflow"

# (options :exception, :empty_page, without countless also :last_page)
Pagy::VARS[:overflow] = :last_page

and you won’t have to rescue an error manually.

2 Likes

Does this work for wrong input - eg, “?page=d7,d003”

which is more modern, faster and more powerful.

What do you mean by modern and faster? How can pagy be faster than will_panigate - for example? Both pagy and will_paginate are sending a request with limit and offset - right?

Overflow only handles numbers larger than the amount of pages. To deal with invalid input, see my little snippet (in the post above) where I define my own page method in the ApplicationController to ensure I always have a positive number as page parameter.

pagy seems to do much less (instantiating less classes, consuming less memory) than the other gems. But when I say “faster” I relied on their own benchmarking:

It offers an extra to not make the second query to get the count (extra: “countless”) to speed it up more: Pagy::Countless | Pagy

Though there is no extra for cursor-pagination as far as I know.

No, pagy offers “extras” and one of them deals with these number overflows

Oh, this is excellent. Thank you so much!

1 Like

@kmitov out of curiosity: what did you decide? To implement an alternative pagination solution or stick with will_paginate for time being?

As I reported the issue with will_paginate, the gem maintainer set the gem to maintenance mode.

So this turned out to be a greater question and I have scheduled it for next few weeks to migrate to kanimari (for example). I am not sure if I will find an answer there, I will have to check.

This is exacerbated by the fact that, unlike Kaminari, Pagy accesses params[:page] behind the scenes so there is no opportunity to restrict the input; the only option is to rescue.

Not really. That automatic handling of the prams[:page] is just for your convenience. You can always exlicitly pass page: params[:page].to_i or whatever to the pagy method and it will not try to find it in the params. FYI, you can also explicitly pass any other variable that you want to override.

1 Like