While optimizing database performance in a Rails app, sometimes we stop using ActiveRecord in the critical parts.
Avoiding ActiveRecord models gives us the freedom of writing complex SQL queries and working with them as with plain objects.
But it also becomes tricky when you want to pass them to other Rails components - like forms and pagination.
In my case, I had a plain SQL query that I passed directly to PostgreSQL using the pg gem.
Later when I passed the query results to a Rails view, I realized that I also need to paginate results when presenting them to a user.
Kaminari (which I am using) is a de-facto pagination solution for Rails apps.
Then how to paginate a custom non-ActiveRecord query with Kaminari?
Beside of ActiveRecord integration, Kaminari also provides API for paginating plain arrays:
Imagine fetching 100k records from the table, loading them into Ruby memory and then slicing only 10 of them
to render the first page. Sounds horrible, right?
To write a better solution, let’s review how Kaminari works with ActiveRecord:
Technically, @users is an instance of ActiveRecord::Relation.
that comes with Kaminari accepts ActiveRecord::Relation, which should respond to these 3 methods:
current_page - returns current page number
total_pages - returns total number of pages
all - returns an array of rows for the current page
What if we write our own relation object that would behave exactly like ActiveRecord::Relation and then simply pass it to paginate?
Here is the final code of the Relation class:
How do we use it with a view and a controller?
It works: params[:page] is passed to the query and Kaminari renders pagination for it.
I digged into writing my own relation object mostly because of the interest.
In most of the cases, the problem can be solved by creating a PostgreSQL view:
And using this view from an ActiveRecord model:
The result will be the same.
It’s up to you to use a view or to write a relation object, through the first option may not be possible
for some kind of queries.
Follow me on Twitter to get recent updates: @kirshatrov