Eager Pagination with Elasticsearch-rails

So you’re searching millions of records with Elasticsearch-model but want to eagerly load all associations when rendering your page for performance reasons. But wait, you’re using Kaminari for pagination, and when you includes(:association), you lose the pagination support.

Below, you’ll see how a simple delegator will relieve your woes and eagerly load associations with support for pagination.

The Problem

Controller

@gifts = Gift.search('flower').page(params[:page]).records.includes(:user)

View

= paginate @gifts

Error!

undefined method `current_page' for #<Gift::ActiveRecord_Relation:0x007fa1845de2d8>

The Solution

Controller

records = Gift.search('flower').page(params[:page]).records
@gifts = EagerPagination.new(records, :scope_with_eager_loading)

Model

class Gift
  include Elasticsearch::Model
  belongs_to :user
  scope :scope_with_eager_loading, -> { includes(:user) }
end

Delegator

class EagerPagination < SimpleDelegator
  attr_reader :records, :scope

  def initialize(records, scope)
    super(records)
    @records = records
    @scope = scope
  end

  def each
    records.public_send(scope).each do |r|
      yield r
    end
  end
end

What’s going on here? EagerPagination overrides the def each method to use the eager loading scope, but delegates all other calls to the underlying records object that still has Kaminari pagination support.

My initial solution started to dive into the Elasticsearch-model implementation, but in the end I felt that the ES gem shouldn’t burden itself with the responsibilities of handing AR scopes.

I’m happy with how light this final outcome is. Simplicity is king.

Full example can be found in the gist below.

comments powered by Disqus