Skip to content

Support for will_paginate #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This repository contains ActiveModel, ActiveRecord and Ruby on Rails integration
* ActiveRecord::Relation-based wrapper for returning search results as records
* Convenience model methods such as `search`, `mapping`, `import`, etc
* Rake tasks for importing the data
* Kaminari-based pagination support
* Kaminari and WillPaginate based pagination support
* Integration with Rails's instrumentation framework
* Templates for generating example Rails application

Expand Down
5 changes: 3 additions & 2 deletions elasticsearch-model/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,10 @@ response.records.each_with_hit { |record, hit| puts "* #{record.title}: #{hit._s
#### Pagination

You can implement pagination with the `from` and `size` search parameters. However, search results
can be automatically paginated with the [`kaminari`](http://rubygems.org/gems/kaminari) gem.
can be automatically paginated with the [`kaminari`](http://rubygems.org/gems/kaminari) or
[`will_paginate`](https://github.com/mislav/will_paginate) gems.

If Kaminari is loaded, use the familiar paging methods:
If Kaminari or WillPaginate is loaded, use the familiar paging methods:

```ruby
response.page(2).results
Expand Down
1 change: 1 addition & 0 deletions elasticsearch-model/elasticsearch-model.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Gem::Specification.new do |s|

s.add_development_dependency "oj"
s.add_development_dependency "kaminari"
s.add_development_dependency "will_paginate"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If desired, I can remove this development dependency and instead stub WillPaginate::CollectionMethods.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's perfectly fine to have it as a development dependency.

# NOTE: Do not add Mongoid here, keep only in 3/4 files

s.add_development_dependency "minitest", "~> 4.0"
Expand Down
5 changes: 4 additions & 1 deletion elasticsearch-model/lib/elasticsearch/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@

require 'elasticsearch/model/ext/active_record'

if defined?(::Kaminari)
case
when defined?(::Kaminari)
Elasticsearch::Model::Response::Response.__send__ :include, Elasticsearch::Model::Response::Pagination::Kaminari
when defined?(::WillPaginate)
Elasticsearch::Model::Response::Response.__send__ :include, Elasticsearch::Model::Response::Pagination::WillPaginate
end

module Elasticsearch
Expand Down
41 changes: 41 additions & 0 deletions elasticsearch-model/lib/elasticsearch/model/response/pagination.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,47 @@ def total_count
results.total
end
end

# Allow models to be paginated with the "will_paginate" gem [https://github.com/mislav/will_paginate]
#
module WillPaginate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add just a small preamble comment here, similar as what we have for Kaminari at L8?

def self.included(base)
base.__send__ :include, ::WillPaginate::CollectionMethods

methods = [:current_page, :per_page, :total_entries, :total_pages, :previous_page, :next_page, :out_of_bounds?]
Elasticsearch::Model::Response::Results.__send__ :delegate, *methods, to: :response
Elasticsearch::Model::Response::Records.__send__ :delegate, *methods, to: :response
end

def paginate(options)
page = [options[:page].to_i, 1].max
per_page = (options[:per_page] || klass.per_page).to_i

search.definition.update size: per_page,
from: (page - 1) * per_page
self
end

def current_page
search.definition[:from] / per_page + 1 if search.definition[:from] && per_page
end

def page(num)
paginate(page: num, per_page: per_page) # shorthand
end

def per_page(num = nil)
if num.nil?
search.definition[:size]
else
paginate(page: current_page, per_page: num) # shorthand
end
end

def total_entries
results.total
end
end
end

end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'test_helper'

class Elasticsearch::Model::ResponsePaginationTest < Test::Unit::TestCase
class Elasticsearch::Model::ResponsePaginationKaminariTest < Test::Unit::TestCase
context "Response pagination" do
class ModelClass
include ::Kaminari::ConfigurationMethods
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
require 'test_helper'
require 'will_paginate'
require 'will_paginate/collection'

class Elasticsearch::Model::ResponsePaginationWillPaginateTest < Test::Unit::TestCase
context "Response pagination" do
class ModelClass
def self.index_name; 'foo'; end
def self.document_type; 'bar'; end

# WillPaginate adds this method to models (see WillPaginate::PerPage module)
def self.per_page
33
end
end

# Subsclass Response so we can include WillPaginate module without conflicts with Kaminari.
class WillPaginateResponse < Elasticsearch::Model::Response::Response
include Elasticsearch::Model::Response::Pagination::WillPaginate
end

RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'},
'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } }

setup do
@search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*'
@response = WillPaginateResponse.new ModelClass, @search, RESPONSE
@response.klass.stubs(:client).returns mock('client')

@expected_methods = [
# methods needed by WillPaginate::CollectionMethods
:current_page,
:per_page,
:total_entries,

# methods defined by WillPaginate::CollectionMethods
:total_pages,
:previous_page,
:next_page,
:out_of_bounds?,
]
end

should "have pagination methods" do
assert_respond_to @response, :paginate

@expected_methods.each do |method|
assert_respond_to @response, method
end
end

context "response.results" do
should "have pagination methods" do
@expected_methods.each do |method|
assert_respond_to @response.results, method
end
end
end

context "response.records" do
should "have pagination methods" do
@expected_methods.each do |method|
@response.klass.stubs(:find).returns([])
assert_respond_to @response.records, method
end
end
end

context "#paginate method" do
should "set from/size using defaults" do
@response.klass.client
.expects(:search)
.with do |definition|
assert_equal 0, definition[:from]
assert_equal 33, definition[:size]
end
.returns(RESPONSE)

assert_nil @response.search.definition[:from]
assert_nil @response.search.definition[:size]

@response.paginate(page: nil).to_a
assert_equal 0, @response.search.definition[:from]
assert_equal 33, @response.search.definition[:size]
end

should "set from/size using default per_page" do
@response.klass.client
.expects(:search)
.with do |definition|
assert_equal 33, definition[:from]
assert_equal 33, definition[:size]
end
.returns(RESPONSE)

assert_nil @response.search.definition[:from]
assert_nil @response.search.definition[:size]

@response.paginate(page: 2).to_a
assert_equal 33, @response.search.definition[:from]
assert_equal 33, @response.search.definition[:size]
end

should "set from/size using custom page and per_page" do
@response.klass.client
.expects(:search)
.with do |definition|
assert_equal 18, definition[:from]
assert_equal 9, definition[:size]
end
.returns(RESPONSE)

assert_nil @response.search.definition[:from]
assert_nil @response.search.definition[:size]

@response.paginate(page: 3, per_page: 9).to_a
assert_equal 18, @response.search.definition[:from]
assert_equal 9, @response.search.definition[:size]
end

should "searches for page 1 if specified page is < 1" do
@response.klass.client
.expects(:search)
.with do |definition|
assert_equal 0, definition[:from]
assert_equal 33, definition[:size]
end
.returns(RESPONSE)

assert_nil @response.search.definition[:from]
assert_nil @response.search.definition[:size]

@response.paginate(page: "-1").to_a
assert_equal 0, @response.search.definition[:from]
assert_equal 33, @response.search.definition[:size]
end
end

context "#page and #per_page shorthand methods" do
should "set from/size using default per_page" do
@response.page(5)
assert_equal 132, @response.search.definition[:from]
assert_equal 33, @response.search.definition[:size]
end

should "set from/size when calling #page then #per_page" do
@response.page(5).per_page(3)
assert_equal 12, @response.search.definition[:from]
assert_equal 3, @response.search.definition[:size]
end

should "set from/size when calling #per_page then #page" do
@response.per_page(3).page(5)
assert_equal 12, @response.search.definition[:from]
assert_equal 3, @response.search.definition[:size]
end
end

context "#current_page method" do
should "return 1 by default" do
@response.paginate({})
assert_equal 1, @response.current_page
end

should "return current page number" do
@response.paginate(page: 3, per_page: 9)
assert_equal 3, @response.current_page
end

should "return nil if not pagination set" do
assert_equal nil, @response.current_page
end
end

context "#per_page method" do
should "return value set in paginate call" do
@response.paginate(per_page: 8)
assert_equal 8, @response.per_page
end
end

context "#total_entries method" do
should "return total from response" do
@response.expects(:results).returns(mock('results', total: 100))
assert_equal 100, @response.total_entries
end
end
end
end