Skip to content

Commit 6dc50bc

Browse files
committed
πŸ’Ž Add :typesense_rails paginator (See typesense/typesense-rails#17)
1 parent c3aa853 commit 6dc50bc

File tree

10 files changed

+214
-3
lines changed

10 files changed

+214
-3
lines changed

β€ŽREADME.mdβ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,14 @@ search = Product.pagy_search(params[:q])
124124
@pagy, @response = pagy(:elasticsearch_rails, search)
125125
@pagy, @results = pagy(:meilisearch, search)
126126
@pagy, @results = pagy(:searchkick, search)
127+
@pagy, @results = pagy(:typesense_rails, search)
127128

128129
# Or get pagy from paginated results:
129130
@results = Product.search(params[:q])
130131
@pagy = pagy(:elasticsearch_rails, @results)
131132
@pagy = pagy(:meilisearch, @results)
132133
@pagy = pagy(:searchkick, @results)
134+
@pagy = pagy(:typesense_rails, @results)
133135
```
134136

135137
##### Calendar pagination

β€Ždocs/guides/chose_wisely.mdβ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ This hybrid technique filters by a specific time period (Year, Month, Day, etc.)
106106

107107
### Search platforms
108108

109-
Pagy supports Elasticsearch, Meilisearch, and Searchkick.
109+
Pagy supports Elasticsearch, Meilisearch, Searchkick, and TypesenseRails.
110110

111111
These paginators get the count, limit and results provided by the search platform. Pagy acts as an interface to these underlying gems, using the `:offset` paginator (whithout the shortcomings).
112112

@@ -116,6 +116,7 @@ These paginators get the count, limit and results provided by the search platfor
116116
- [:icon-search: :elasticsearch_rails](../toolbox/paginators/elasticsearch_rails.md) 
117117
[:icon-search: :meilisearch](../toolbox/paginators/meilisearch.md) 
118118
[:icon-search: :searchkick](../toolbox/paginators/searchkick.md)
119+
[:icon-search: :typesense_rails](../toolbox/paginators/typesense_rails.md)
119120
- :icon-check-circle-24: **Best for**: Search Results
120121
- :icon-thumbsup-24: **Pros**: Leverages the engine's native response
121122
- :icon-thumbsdown-24: **Cons**: None (simple interface with search platform gems)

β€Ždocs/guides/how-to.mdβ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,9 @@ You can send selected `@pagy` instance data to the client as JSON using the [dat
204204
See these paginators:
205205

206206
- [elasticsearch_rails](../toolbox/paginators/elasticsearch_rails)
207-
- [searchkick](../toolbox/paginators/searchkick)
208207
- [meilisearch](../toolbox/paginators/meilisearch)
208+
- [searchkick](../toolbox/paginators/searchkick)
209+
- [typesense_rails](../toolbox/paginators/typesense_rails)
209210

210211
==- Paginate by date
211212

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
label: :typesense_rails
3+
icon: search
4+
order: 20
5+
categories:
6+
- Paginators
7+
- Search
8+
---
9+
10+
#
11+
12+
## :icon-search: :typesense_rails
13+
14+
---
15+
16+
`:typesense` is a paginator designed for `Typesense::Rails` results.
17+
18+
#### Configuration
19+
20+
Ensure `Typesense.configuration[:pagination_backend] == nil`.
21+
22+
+++ Active mode
23+
24+
!!!success Pagy searches and paginates
25+
26+
You use the `pagy_search` method in place of the `search` method.
27+
!!!
28+
29+
```ruby Model
30+
extend Pagy::Search
31+
```
32+
33+
```ruby Controller
34+
# Get the collection in one of the following ways
35+
search = Article.pagy_search(params[:q], to_query)
36+
# Paginate it
37+
@pagy, @response = pagy(:typesense_rails, search, **options)
38+
```
39+
40+
+++ Passive Mode
41+
42+
!!!success You search and paginate
43+
44+
Pagy creates its object out of your result.
45+
!!!
46+
47+
```ruby Controller
48+
# Standard results (already paginated)
49+
@results = Model.search(params[:q], to_query, { per_page: 10, page: 10, ...})
50+
# Get the pagy object out of it
51+
@pagy = pagy(:typesense_rails, @results, **options)
52+
```
53+
54+
+++
55+
56+
!!!
57+
58+
Search paginators don't query a DB, but use the same positional technique as [:offset](offset.md) paginators, with shared options and readers.
59+
!!!
60+
61+
==- Options
62+
63+
- `search_method: :my_search`
64+
- Customize the name of the search method (default `:search`)
65+
66+
See also [Offset Options](offset#options)
67+
68+
==- Readers
69+
70+
See [Offset Readers](offset#readers)
71+
72+
===

β€Žgem/lib/pagy.rbβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class Pagy
2828
autoload :ElasticsearchRails, path.join('classes/offset/search')
2929
autoload :Meilisearch, path.join('classes/offset/search')
3030
autoload :Searchkick, path.join('classes/offset/search')
31+
autoload :TypesenseRails, path.join('classes/offset/search')
3132
autoload :Keyset, path.join('classes/keyset/keyset')
3233

3334
OPTIONS = {} # rubocop:disable Style/MutableConstant

β€Žgem/lib/pagy/classes/offset/search.rbβ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ class Meilisearch < SearchBase
2929
end
3030

3131
class Searchkick < SearchBase; end
32+
33+
class TypesenseRails < SearchBase; end
3234
end

β€Žgem/lib/pagy/toolbox/paginators/method.rbβ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ class Pagy
1111
calendar: :CalendarPaginator,
1212
elasticsearch_rails: :ElasticsearchRailsPaginator,
1313
meilisearch: :MeilisearchPaginator,
14-
searchkick: :SearchkickPaginator }.freeze
14+
searchkick: :SearchkickPaginator,
15+
typesense_rails: :TypesenseRailsPaginator }.freeze
1516

1617
path = Pathname.new(__dir__)
1718
paginators.each { |symbol, name| autoload name, path.join(symbol.to_s) }
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../../modules/searcher'
4+
5+
class Pagy
6+
module TypesenseRailsPaginator
7+
module_function
8+
9+
# Paginate from the search object
10+
def paginate(search, options)
11+
if search.is_a?(Search::Arguments) # Active mode
12+
13+
Searcher.wrap(search, options) do
14+
model, arguments, search_options = search
15+
16+
search_options[:per_page] = options[:limit]
17+
search_options[:page] = options[:page]
18+
19+
method = options[:search_method] || TypesenseRails::DEFAULT[:search_method]
20+
results = model.send(method, *arguments, search_options)
21+
options[:count] = results.raw_answer['found']
22+
23+
[TypesenseRails.new(**options), results]
24+
end
25+
26+
else # Passive mode
27+
options[:limit] = search.raw_answer['request_params']['per_page']
28+
options[:page] = search.raw_answer['page']
29+
options[:count] = search.raw_answer['found']
30+
31+
TypesenseRails.new(**options)
32+
end
33+
end
34+
end
35+
end
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# frozen_string_literal: true
2+
3+
require 'pagy/classes/offset/search'
4+
5+
module MockTypesenseRails
6+
RESULTS = {
7+
'*' => (1..1000).map { |n| "doc-#{n}" },
8+
'a' => (1..1000).map { |n| "a-#{n}" },
9+
'b' => (1..1000).map { |n| "b-#{n}" }
10+
}.freeze
11+
12+
class Results
13+
attr_reader :term, :options
14+
15+
def initialize(term, options = {})
16+
@term = term
17+
@options = options
18+
@page = options[:page] || 1
19+
@limit = options[:per_page] || 20
20+
end
21+
22+
def raw_answer
23+
all_results = RESULTS[@term] || []
24+
offset = (@page - 1) * @limit
25+
hits = all_results[offset, @limit] || []
26+
27+
{
28+
'found' => all_results.size,
29+
'page' => @page,
30+
'request_params' => { 'per_page' => @limit },
31+
'hits' => hits
32+
}
33+
end
34+
end
35+
36+
class Model
37+
extend Pagy::Search
38+
39+
def self.search(term, _query_by = nil, options = {})
40+
Results.new(term, options)
41+
end
42+
end
43+
end
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# frozen_string_literal: true
2+
3+
require 'unit/test_helper'
4+
require 'pagy/toolbox/paginators/typesense_rails'
5+
require 'mocks/app'
6+
require 'mocks/typesense_rails'
7+
8+
describe 'Pagy::TypesenseRailsPaginator' do
9+
let(:app) { MockApp.new }
10+
11+
describe '#paginate' do
12+
describe 'Active Mode (pagy_search)' do
13+
it 'paginates with defaults' do
14+
args = MockTypesenseRails::Model.pagy_search('a', 'name')
15+
16+
pagy, results = app.pagy(:typesense_rails, args, page: 1, limit: 10)
17+
18+
_(pagy).must_be_kind_of Pagy::TypesenseRails
19+
_(pagy.count).must_equal 1000
20+
_(pagy.page).must_equal 1
21+
_(pagy.limit).must_equal 10
22+
_(results).must_be_kind_of MockTypesenseRails::Results
23+
_(results.raw_answer['hits'].first).must_equal 'a-1'
24+
_(results.raw_answer['hits'].size).must_equal 10
25+
end
26+
27+
it 'paginates with custom options' do
28+
args = MockTypesenseRails::Model.pagy_search('b', 'name')
29+
30+
pagy, results = app.pagy(:typesense_rails, args, page: 2, limit: 20)
31+
32+
_(pagy.page).must_equal 2
33+
_(pagy.limit).must_equal 20
34+
_(results.raw_answer['page']).must_equal 2
35+
_(results.raw_answer['hits'].first).must_equal 'b-21'
36+
_(results.raw_answer['hits'].last).must_equal 'b-40'
37+
end
38+
end
39+
40+
describe 'Passive Mode (Results object)' do
41+
it 'paginates from existing results' do
42+
results = MockTypesenseRails::Results.new('a', page: 3, per_page: 15)
43+
44+
pagy = app.pagy(:typesense_rails, results)
45+
46+
_(pagy).must_be_kind_of Pagy::TypesenseRails
47+
_(pagy.page).must_equal 3
48+
_(pagy.limit).must_equal 15
49+
_(pagy.count).must_equal 1000
50+
end
51+
end
52+
end
53+
end

0 commit comments

Comments
Β (0)