Skip to content

Instantly share code, notes, and snippets.

@austra
Last active July 12, 2020 21:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save austra/d8f5a1de00f12c253716 to your computer and use it in GitHub Desktop.
Save austra/d8f5a1de00f12c253716 to your computer and use it in GitHub Desktop.
Pagination Headers With Kaminari for API
http://jaketrent.com/post/pagination-headers-with-kaminari/
https://developer.github.com/v3/#pagination
Kaminari provides easy pagination in a rails app. It’s great to use. We’ll make it better by adding a little function to your controllers to provide useful pagination headers.
kaminari pagination
Pagination from Kaminari
Installation is easy with an addition to your Gemfile:
1
gem 'kaminari'
and an install:
1
bundle install
Now, you have a magical page method available on your models. It works like a charm right out of the box.
Let’s say that I have a germs_controller.rb (because it’s a great time of year for that), where I have a list of germs that I want to paginate. I could easily request /api/v1/germs?page=2 and get the second page with this code in the controller:
germs_controller.rb
1
2
3
4
5
6
7
8
9
10
module Api
module V1
class GermsController < ApplicationController
def index
@germs = Germ.page params[:page]
render json: @germs
end
end
end
end
If you want to control the default page size, you can do that in the initializers:
kaminari_config.rb
1
2
3
Kaminari.configure do |config|
config.default_per_page = 20
end
Pagination Headers
There’s another feature that we want to add, and that’s pagination headers. These are going to be HTTP headers that come back in the response that indicate to clients where the relative pages are located in our API. For instance, we asked for page 2, but where might one request the previous and next pages, 1 and 3?
Putting the pagination info into the header follows a pattern used in the github api.
We’ll add a function to our ApplicationController to helps us out:
application_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class ApplicationController < ActionController::API
protected
def set_pagination_header(name, options = {})
scope = instance_variable_get("@#{name}")
request_params = request.query_parameters
url_without_params = request.original_url.slice(0..(request.original_url.index("?")-1)) unless request_params.empty?
url_without_params ||= request.original_url
page = {}
page[:first] = 1 if scope.total_pages > 1 && !scope.first_page?
page[:last] = scope.total_pages if scope.total_pages > 1 && !scope.last_page?
page[:next] = scope.current_page + 1 unless scope.last_page?
page[:prev] = scope.current_page - 1 unless scope.first_page?
pagination_links = []
page.each do |k, v|
new_request_hash= request_params.merge({:page => v})
pagination_links << "<#{url_without_params}?#{new_request_hash.to_param}>; rel=\"#{k}\""
end
headers["Link"] = pagination_links.join(", ")
end
end
This code will provide a Link header in the HTTP response. Its value might look something like this:
Link
1
<http://myapi.com/api/v1/germs?page=1>; rel="prev", <http://myapi.com/api/v1/germs?page=3>; rel="next"
The values of first, last, next, etc are populated from calls to Kaminari methods such as first_page?.
In order to have this header set on a response, we need to add it to a callback in our controller:
germs_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
module Api
module V1
class GermsController < ApplicationController
after_filter only: [:index] { set_pagination_header(:germs) }
def index
@germs = Germ.page params[:page]
render json: @germs
end
end
end
end
Now, a fresh request to /api/v1/germs with or without the page query parameter should return back the Link header in the HTTP response which your client can use to traverse the other pages of data available in your API.
So, there’s one way to get pagination info from Kaminari into your HTTP headers. What would you improve?
@BGMP
Copy link

BGMP commented May 31, 2020

Hi @austra . I'm fairly new to kaminari and rails as a whole, but more new to kaminari to be honest, and I'm struggling with such a simple thing. Wondering around the internet, looking for an answer, I found this gist of yours, and thought... well, maybe I could actually get a response from somebody who has used this in the past.

Here is what I want to do:

Say, for a simple model, you have different table columns, such as id, name, etc... and then I'd want to go about adding a simple # into a new table row of the paginated results, such as this:

# id name
1 wutever one
2 wutever1 two
3 wutever2 three

Literally that simple # index is killing my brain, because how do I get the current table row's index, if I'm in page two, for example? uh, I feel damn stupid lol, let alone how to actually get that to work when I get to sorting things.

@austra
Copy link
Author

austra commented Jun 1, 2020

Hi @BGMP, wow, this an old gist!

Not sure I'm quite following what you are trying to do. Are you trying to paginate your model with some additional attributes? There is some pretty in depth documentation over here: https://github.com/kaminari/kaminari

Kaminari does allow you to paginate generic arrays too, which if I'm understanding you correctly, you might have to use. In your controller you can create an array with your model attributes, and any additional attributes you might want. Then you can paginate that array.
( https://github.com/kaminari/kaminari#paginating-a-generic-array-object )

@BGMP
Copy link

BGMP commented Jun 1, 2020

@austra Hey! thank you so much for answering first of all! Yeah, I have read kaminari's documentation, but it didn't actually help me that much, I guess it just diverts to things which people are actually looking for, not my dumb stuff lol.

Anyhow, yesterday I managed to solve wut I was trying to do, with a bit of math I guess, since I simply wanted a correct index going from one to whatever the row number should be throughout my whole table. This made the trick:

@count = (@players.current_page - 1) * @players.limit_value + 1

Then I pass that under the # column, adding 1 for each iteration and boom, the number is always got right.

I'm now struggling with sorting my data though :(

@austra
Copy link
Author

austra commented Jun 1, 2020

Right on, @BGMP, keep at it, you'll get it!

If you're just trying to display tabular data, you might want to check out something like datatables. It's pretty slick for sorting and paginating table view stuff. Good luck!
https://datatables.net/

@BGMP
Copy link

BGMP commented Jul 12, 2020

@austra holy, I really do miss on the important notifications when github rings the bell at the top right whenever anything, literally anything, which supposedly concerns me, happens on the platform. Missed this one, sorry :(.

For sure, I'll check datatables out! To be honest I feel I struggle with rails because of a general lack of experience - it's the framework I chose for an approach to web development. But hey, that just means I can only get better xD.

I have managed to work out most of the things I wanted to do related to data sorting, and it was just a matter of understanding a bit more of ruby hashes and such, and having the right idea of how to go about using them throughout my controllers.

Anyhow, it's nice to know there's people out there willing to help out, and for that I truly thank you!

P.S. If you like cursed cats, you should lowkey check my github profile.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment