Skip to content

Commit 750ccad

Browse files
balexandkarmi
authored andcommitted
[MODEL] Added support for will_paginate pagination library
This patch adds support for the [`will_paginate`](https://github.com/mislav/will_paginate) gem, functionally equivalent to the Kaminari integration. Closes elastic#49
1 parent 104f91f commit 750ccad

File tree

7 files changed

+266
-7
lines changed

7 files changed

+266
-7
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ This repository contains ActiveModel, ActiveRecord and Ruby on Rails integration
88
* ActiveRecord::Relation-based wrapper for returning search results as records
99
* Convenience model methods such as `search`, `mapping`, `import`, etc
1010
* Rake tasks for importing the data
11-
* Kaminari-based pagination support
12-
* Integration with Rails's instrumentation framework
11+
* Support for Kaminari and WillPaginate pagination
12+
* Integration with Rails' instrumentation framework
1313
* Templates for generating example Rails application
1414

1515
Elasticsearch client and Ruby API is provided by the

elasticsearch-model/README.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,10 @@ response.records.each_with_hit { |record, hit| puts "* #{record.title}: #{hit._s
252252
#### Pagination
253253

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

257-
If Kaminari is loaded, use the familiar paging methods:
258+
If Kaminari or WillPaginate is loaded, use the familiar paging methods:
258259

259260
```ruby
260261
response.page(2).results
@@ -271,7 +272,7 @@ In a Rails controller, use the the `params[:page]` parameter to paginate through
271272
@articles.next_page
272273
# => 3
273274
```
274-
To initialize and include the pagination support manually:
275+
To initialize and include the Kaminari pagination support manually:
275276

276277
```ruby
277278
Kaminari::Hooks.init

elasticsearch-model/elasticsearch-model.gemspec

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Gem::Specification.new do |s|
3636

3737
s.add_development_dependency "oj"
3838
s.add_development_dependency "kaminari"
39+
s.add_development_dependency "will_paginate"
3940
# NOTE: Do not add Mongoid here, keep only in 3/4 files
4041

4142
s.add_development_dependency "minitest", "~> 4.0"

elasticsearch-model/lib/elasticsearch/model.rb

+4-1
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@
3131

3232
require 'elasticsearch/model/ext/active_record'
3333

34-
if defined?(::Kaminari)
34+
case
35+
when defined?(::Kaminari)
3536
Elasticsearch::Model::Response::Response.__send__ :include, Elasticsearch::Model::Response::Pagination::Kaminari
37+
when defined?(::WillPaginate)
38+
Elasticsearch::Model::Response::Response.__send__ :include, Elasticsearch::Model::Response::Pagination::WillPaginate
3639
end
3740

3841
module Elasticsearch

elasticsearch-model/lib/elasticsearch/model/response/pagination.rb

+65
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,71 @@ def total_count
9393
results.total
9494
end
9595
end
96+
97+
# Allow models to be paginated with the "will_paginate" gem [https://github.com/mislav/will_paginate]
98+
#
99+
module WillPaginate
100+
def self.included(base)
101+
base.__send__ :include, ::WillPaginate::CollectionMethods
102+
103+
# Include the paging methods in results and records
104+
#
105+
methods = [:current_page, :per_page, :total_entries, :total_pages, :previous_page, :next_page, :out_of_bounds?]
106+
Elasticsearch::Model::Response::Results.__send__ :delegate, *methods, to: :response
107+
Elasticsearch::Model::Response::Records.__send__ :delegate, *methods, to: :response
108+
end
109+
110+
# Main pagination method
111+
#
112+
# @example
113+
#
114+
# Article.search('foo').paginate(page: 1, per_page: 30)
115+
#
116+
def paginate(options)
117+
page = [options[:page].to_i, 1].max
118+
per_page = (options[:per_page] || klass.per_page).to_i
119+
120+
search.definition.update size: per_page,
121+
from: (page - 1) * per_page
122+
self
123+
end
124+
125+
# Return the current page
126+
#
127+
def current_page
128+
search.definition[:from] / per_page + 1 if search.definition[:from] && per_page
129+
end
130+
131+
# Pagination method
132+
#
133+
# @example
134+
#
135+
# Article.search('foo').page(2)
136+
#
137+
def page(num)
138+
paginate(page: num, per_page: per_page) # shorthand
139+
end
140+
141+
# Return or set the "size" value
142+
#
143+
# @example
144+
#
145+
# Article.search('foo').per_page(15).page(2)
146+
#
147+
def per_page(num = nil)
148+
if num.nil?
149+
search.definition[:size]
150+
else
151+
paginate(page: current_page, per_page: num) # shorthand
152+
end
153+
end
154+
155+
# Returns the total number of results
156+
#
157+
def total_entries
158+
results.total
159+
end
160+
end
96161
end
97162

98163
end

elasticsearch-model/test/unit/response_pagination_test.rb elasticsearch-model/test/unit/response_pagination_kaminari_test.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
require 'test_helper'
22

3-
class Elasticsearch::Model::ResponsePaginationTest < Test::Unit::TestCase
3+
class Elasticsearch::Model::ResponsePaginationKaminariTest < Test::Unit::TestCase
44
context "Response pagination" do
55
class ModelClass
66
include ::Kaminari::ConfigurationMethods
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
require 'test_helper'
2+
require 'will_paginate'
3+
require 'will_paginate/collection'
4+
5+
class Elasticsearch::Model::ResponsePaginationWillPaginateTest < Test::Unit::TestCase
6+
context "Response pagination" do
7+
class ModelClass
8+
def self.index_name; 'foo'; end
9+
def self.document_type; 'bar'; end
10+
11+
# WillPaginate adds this method to models (see WillPaginate::PerPage module)
12+
def self.per_page
13+
33
14+
end
15+
end
16+
17+
# Subsclass Response so we can include WillPaginate module without conflicts with Kaminari.
18+
class WillPaginateResponse < Elasticsearch::Model::Response::Response
19+
include Elasticsearch::Model::Response::Pagination::WillPaginate
20+
end
21+
22+
RESPONSE = { 'took' => '5', 'timed_out' => false, '_shards' => {'one' => 'OK'},
23+
'hits' => { 'total' => 100, 'hits' => (1..100).to_a.map { |i| { _id: i } } } }
24+
25+
setup do
26+
@search = Elasticsearch::Model::Searching::SearchRequest.new ModelClass, '*'
27+
@response = WillPaginateResponse.new ModelClass, @search, RESPONSE
28+
@response.klass.stubs(:client).returns mock('client')
29+
30+
@expected_methods = [
31+
# methods needed by WillPaginate::CollectionMethods
32+
:current_page,
33+
:per_page,
34+
:total_entries,
35+
36+
# methods defined by WillPaginate::CollectionMethods
37+
:total_pages,
38+
:previous_page,
39+
:next_page,
40+
:out_of_bounds?,
41+
]
42+
end
43+
44+
should "have pagination methods" do
45+
assert_respond_to @response, :paginate
46+
47+
@expected_methods.each do |method|
48+
assert_respond_to @response, method
49+
end
50+
end
51+
52+
context "response.results" do
53+
should "have pagination methods" do
54+
@expected_methods.each do |method|
55+
assert_respond_to @response.results, method
56+
end
57+
end
58+
end
59+
60+
context "response.records" do
61+
should "have pagination methods" do
62+
@expected_methods.each do |method|
63+
@response.klass.stubs(:find).returns([])
64+
assert_respond_to @response.records, method
65+
end
66+
end
67+
end
68+
69+
context "#paginate method" do
70+
should "set from/size using defaults" do
71+
@response.klass.client
72+
.expects(:search)
73+
.with do |definition|
74+
assert_equal 0, definition[:from]
75+
assert_equal 33, definition[:size]
76+
end
77+
.returns(RESPONSE)
78+
79+
assert_nil @response.search.definition[:from]
80+
assert_nil @response.search.definition[:size]
81+
82+
@response.paginate(page: nil).to_a
83+
assert_equal 0, @response.search.definition[:from]
84+
assert_equal 33, @response.search.definition[:size]
85+
end
86+
87+
should "set from/size using default per_page" do
88+
@response.klass.client
89+
.expects(:search)
90+
.with do |definition|
91+
assert_equal 33, definition[:from]
92+
assert_equal 33, definition[:size]
93+
end
94+
.returns(RESPONSE)
95+
96+
assert_nil @response.search.definition[:from]
97+
assert_nil @response.search.definition[:size]
98+
99+
@response.paginate(page: 2).to_a
100+
assert_equal 33, @response.search.definition[:from]
101+
assert_equal 33, @response.search.definition[:size]
102+
end
103+
104+
should "set from/size using custom page and per_page" do
105+
@response.klass.client
106+
.expects(:search)
107+
.with do |definition|
108+
assert_equal 18, definition[:from]
109+
assert_equal 9, definition[:size]
110+
end
111+
.returns(RESPONSE)
112+
113+
assert_nil @response.search.definition[:from]
114+
assert_nil @response.search.definition[:size]
115+
116+
@response.paginate(page: 3, per_page: 9).to_a
117+
assert_equal 18, @response.search.definition[:from]
118+
assert_equal 9, @response.search.definition[:size]
119+
end
120+
121+
should "searches for page 1 if specified page is < 1" do
122+
@response.klass.client
123+
.expects(:search)
124+
.with do |definition|
125+
assert_equal 0, definition[:from]
126+
assert_equal 33, definition[:size]
127+
end
128+
.returns(RESPONSE)
129+
130+
assert_nil @response.search.definition[:from]
131+
assert_nil @response.search.definition[:size]
132+
133+
@response.paginate(page: "-1").to_a
134+
assert_equal 0, @response.search.definition[:from]
135+
assert_equal 33, @response.search.definition[:size]
136+
end
137+
end
138+
139+
context "#page and #per_page shorthand methods" do
140+
should "set from/size using default per_page" do
141+
@response.page(5)
142+
assert_equal 132, @response.search.definition[:from]
143+
assert_equal 33, @response.search.definition[:size]
144+
end
145+
146+
should "set from/size when calling #page then #per_page" do
147+
@response.page(5).per_page(3)
148+
assert_equal 12, @response.search.definition[:from]
149+
assert_equal 3, @response.search.definition[:size]
150+
end
151+
152+
should "set from/size when calling #per_page then #page" do
153+
@response.per_page(3).page(5)
154+
assert_equal 12, @response.search.definition[:from]
155+
assert_equal 3, @response.search.definition[:size]
156+
end
157+
end
158+
159+
context "#current_page method" do
160+
should "return 1 by default" do
161+
@response.paginate({})
162+
assert_equal 1, @response.current_page
163+
end
164+
165+
should "return current page number" do
166+
@response.paginate(page: 3, per_page: 9)
167+
assert_equal 3, @response.current_page
168+
end
169+
170+
should "return nil if not pagination set" do
171+
assert_equal nil, @response.current_page
172+
end
173+
end
174+
175+
context "#per_page method" do
176+
should "return value set in paginate call" do
177+
@response.paginate(per_page: 8)
178+
assert_equal 8, @response.per_page
179+
end
180+
end
181+
182+
context "#total_entries method" do
183+
should "return total from response" do
184+
@response.expects(:results).returns(mock('results', total: 100))
185+
assert_equal 100, @response.total_entries
186+
end
187+
end
188+
end
189+
end

0 commit comments

Comments
 (0)