Skip to content

Commit

Permalink
added headers extra (#141)
Browse files Browse the repository at this point in the history
* added headers extra
* Use "Link" instead of "Links" for header extra (#142)
* added customizable headers; normalized hash and headers; code improvements; updated doc and tests
  • Loading branch information
ddnexus authored Mar 12, 2019
1 parent 9a26b04 commit 8bd19d6
Show file tree
Hide file tree
Showing 11 changed files with 285 additions and 35 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ Use the official extras, or write your own in just a few lines. Extras add speci

### Feature Extras

- [headers](http://ddnexus.github.io/pagy/extras/headers): Add [RFC-8288](https://tools.ietf.org/html/rfc8288) compilant http response headers (and other helpers) useful for API pagination
- [i18n](http://ddnexus.github.io/pagy/extras/i18n): Use the `I18n` gem instead of the pagy implementation
- [items](http://ddnexus.github.io/pagy/extras/items): Allow the client to request a custom number of items per page with an optional selector UI
- [overflow](http://ddnexus.github.io/pagy/extras/overflow): Allow for easy handling of overflowing pages
Expand Down
10 changes: 8 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require "rubocop/rake_task" unless ENV['SKIP_RUBOCOP']
Rake::TestTask.new(:test_common) do |t|
t.libs << "test"
t.libs << "lib"
t.test_files = FileList.new.include("test/**/*_test.rb").exclude('test/**/i18n_test.rb', 'test/**/items_test.rb', 'test/**/overflow_test.rb', 'test/**/trim_test.rb', 'test/**/elasticsearch_rails_test.rb', 'test/**/searchkick_test.rb')
t.test_files = FileList.new.include("test/**/*_test.rb").exclude('test/**/i18n_test.rb', 'test/**/items_test.rb', 'test/**/overflow_test.rb', 'test/**/trim_test.rb', 'test/**/elasticsearch_rails_test.rb', 'test/**/searchkick_test.rb', 'test/**/support_test.rb')
end

Rake::TestTask.new(:test_extra_i18n) do |t|
Expand Down Expand Up @@ -46,7 +46,13 @@ Rake::TestTask.new(:test_extra_elasticsearch) do |t|
t.test_files = FileList['test/**/elasticsearch_rails_test.rb', 'test/**/searchkick_test.rb']
end

task :test => [:test_common, :test_extra_items, :test_extra_i18n, :test_extra_overflow, :test_extra_trim, :test_extra_elasticsearch ]
Rake::TestTask.new(:test_support) do |t|
t.libs << "test"
t.libs << "lib"
t.test_files = FileList['test/**/support_test.rb']
end

task :test => [:test_common, :test_extra_items, :test_extra_i18n, :test_extra_overflow, :test_extra_trim, :test_extra_elasticsearch, :test_support ]

if ENV['SKIP_RUBOCOP']
task :default => [:test]
Expand Down
1 change: 1 addition & 0 deletions docs/_layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ <h1 id="site-title">{{ site.title | default: site.github.repository_name }} <a c
<a href="{{ site.baseurl }}/extras/countless"><p class="indent1" {% if page.title == 'Countless' %}id="active"{% endif %} >Countless</p></a>
<a href="{{ site.baseurl }}/extras/elasticsearch_rails"><p class="indent1" {% if page.title == 'Elasticsearch Rails' %}id="active"{% endif %} >Elasticsearch Rails</p></a>
<a href="{{ site.baseurl }}/extras/foundation"><p class="indent1" {% if page.title == 'Foundation' %}id="active"{% endif %} >Foundation</p></a>
<a href="{{ site.baseurl }}/extras/headers"><p class="indent1" {% if page.title == 'Headers' %}id="active"{% endif %} >Headers</p></a>
<a href="{{ site.baseurl }}/extras/i18n"><p class="indent1" {% if page.title == 'I18n' %}id="active"{% endif %} >I18n</p></a>
<a href="{{ site.baseurl }}/extras/items"><p class="indent1" {% if page.title == 'Items' %}id="active"{% endif %} >Items</p></a>
<a href="{{ site.baseurl }}/extras/overflow"><p class="indent1" {% if page.title == 'Overflow' %}id="active"{% endif %} >Overflow</p></a>
Expand Down
35 changes: 18 additions & 17 deletions docs/extras.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,24 @@ title: Extras

Pagy comes with a few optional extensions/extras:

| Extra | Description | Links |
|:----------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `array` | Paginate arrays efficiently avoiding expensive array-wrapping and without overriding | [array.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/array.rb), [documentation](extras/array.md) |
| `bootstrap` | Add nav, responsive and compact helpers for the Bootstrap [pagination component](https://getbootstrap.com/docs/4.1/components/pagination) | [bootstrap.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/bootstrap.rb), [documentation](extras/bootstrap.md) |
| `bulma` | Add nav, responsive and compact helpers for the Bulma [pagination component](https://bulma.io/documentation/components/pagination) | [bulma.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/bulma.rb), [documentation](extras/bulma.md) |
| `countless` | Paginate without any count, saving one query per rendering | [countless.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/countless.rb), [documentation](extras/countless.md) |
| `elasticsearch_rails` | Paginate `elasticsearch_rails` gem results efficiently | [elasticsearch_rails.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/elasticsearch_rails.rb), [documentation](extras/elasticsearch_rails.md) |
| `foundation` | Add nav, responsive and compact helpers for the Foundation [pagination component](https://foundation.zurb.com/sites/docs/pagination.html) | [foundation.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/foundation.rb), [documentation](extras/foundation.md) |
| `i18n` | Use the `I18n` gem instead of the pagy implementation | [i18n.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/i81n.rb), [documentation](extras/i18n.md) |
| `items` | Allow the client to request a custom number of items per page with a ready to use selector UI | [items.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/items.rb), [documentation](extras/items.md) |
| `materialize` | Add nav, responsive and compact helpers for the Materialize CSS [pagination component](https://materializecss.com/pagination.html) | [materialize.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/materialize.rb), [documentation](extras/materialize.md) |
| `overflow` | Allow for easy handling of overflowing pages | [overflow.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/overflow.rb), [documentation](extras/overflow.md) |
| `plain` | Add responsive and compact plain/unstyled helpers | [plain.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/plain.rb), [documentation](extras/plain.md) |
| `searchkick` | Paginate arrays efficiently avoiding expensive array-wrapping and without overriding | [searchkick.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/searchkick.rb), [documentation](extras/searchkick.md) |
| `semantic` | Add nav, responsive and compact helpers for the Semantic UI CSS [pagination component](https://semantic-ui.com/collections/menu.html) | [semantic.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/semantic.rb), [documentation](extras/semantic.md) |
| `support` | Extra support for features like: incremental, infinite, auto-scroll pagination | [support.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/support.rb), [documentation](extras/support.md) |
| `trim` | Remove the `page=1` param from links | [trim.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/trim.rb), [documentation](extras/trim.md) |
| Extra | Description | Links |
|:----------------------|:------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `array` | Paginate arrays efficiently avoiding expensive array-wrapping and without overriding | [array.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/array.rb), [documentation](extras/array.md) |
| `bootstrap` | Add nav, responsive and compact helpers for the Bootstrap [pagination component](https://getbootstrap.com/docs/4.1/components/pagination) | [bootstrap.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/bootstrap.rb), [documentation](extras/bootstrap.md) |
| `bulma` | Add nav, responsive and compact helpers for the Bulma [pagination component](https://bulma.io/documentation/components/pagination) | [bulma.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/bulma.rb), [documentation](extras/bulma.md) |
| `countless` | Paginate without any count, saving one query per rendering | [countless.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/countless.rb), [documentation](extras/countless.md) |
| `elasticsearch_rails` | Paginate `elasticsearch_rails` gem results efficiently | [elasticsearch_rails.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/elasticsearch_rails.rb), [documentation](extras/elasticsearch_rails.md) |
| `foundation` | Add nav, responsive and compact helpers for the Foundation [pagination component](https://foundation.zurb.com/sites/docs/pagination.html) | [foundation.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/foundation.rb), [documentation](extras/foundation.md) |
| `headers` | Add [RFC-8288](https://tools.ietf.org/html/rfc8288) compilant http response headers (and other helpers) useful for API pagination | [headers.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/headers.rb), [documentation](extras/headers.md) |
| `i18n` | Use the `I18n` gem instead of the pagy implementation | [i18n.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/i81n.rb), [documentation](extras/i18n.md) |
| `items` | Allow the client to request a custom number of items per page with a ready to use selector UI | [items.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/items.rb), [documentation](extras/items.md) |
| `materialize` | Add nav, responsive and compact helpers for the Materialize CSS [pagination component](https://materializecss.com/pagination.html) | [materialize.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/materialize.rb), [documentation](extras/materialize.md) |
| `overflow` | Allow for easy handling of overflowing pages | [overflow.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/overflow.rb), [documentation](extras/overflow.md) |
| `plain` | Add responsive and compact plain/unstyled helpers | [plain.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/plain.rb), [documentation](extras/plain.md) |
| `searchkick` | Paginate arrays efficiently avoiding expensive array-wrapping and without overriding | [searchkick.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/searchkick.rb), [documentation](extras/searchkick.md) |
| `semantic` | Add nav, responsive and compact helpers for the Semantic UI CSS [pagination component](https://semantic-ui.com/collections/menu.html) | [semantic.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/semantic.rb), [documentation](extras/semantic.md) |
| `support` | Extra support for features like: incremental, infinite, auto-scroll pagination | [support.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/support.rb), [documentation](extras/support.md) |
| `trim` | Remove the `page=1` param from links | [trim.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/trim.rb), [documentation](extras/trim.md) |

## Synopsis

Expand Down
128 changes: 128 additions & 0 deletions docs/extras/headers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
title: Headers
---
# Headers Extra

This extra implements the [RFC-8288](https://tools.ietf.org/html/rfc8288) compilant http response headers (and other helpers) useful for API pagination.

- No need for an extra dependency
- No need to learn yet another interface
- It saves quite a lot of memory and CPU
- It works with any pagy object (including `Pagy::Countless`) regardless the type of collection
- It offers more explicit flexibility and simplicity

## Synopsis

See [extras](../extras.md) for general usage info.

In the `pagy.rb` initializer:

```ruby
require 'pagy/extras/headers'
```

In your controller action:

```ruby
# paginate as usual with any pagy_* backend constructor
pagy, records = pagy(Product.all)
# explicitly merge the headers to the response
pagy_headers_merge(pagy)
render json: records
```

### Suggestions

Instead of explicitly merging the headers before each rendering, you can get them automatically merged (application-wide and when `@pagy` is available), by overriding the `render` method in your application controller:

```ruby
def render(*args, &block)
pagy_headers_merge(@pagy) if @pagy
super
end

# and use it in any action (notice @pagy)
@pagy, records = pagy(Product.all)
render json: records
```

If your code in different actions is similar enough, you can encapsulate the statements in a custom `pagy_render` method in your application controller. For example:

```ruby
def pagy_render(collection, vars={})
pagy, records = pagy(collection, vars) # any pagy_* backend constructor works
pagy_headers_merge(pagy)
render json: records
end

# and use it in your standard actions:
pagy_render(Product.all)
```

## Files

- [headers.rb](https://github.com/ddnexus/pagy/blob/master/lib/pagy/extras/headers.rb)

## Headers

This extra generates the standard `Link` header defined in the
[RFC-8288](https://tools.ietf.org/html/rfc8288), and adds 3 customizable headers useful for pagination: `Page-Items`, `Total-Pages` and `Total-Count` headers.

Example of the default HTTP headers produced:

```
Link <https://example.com:8080/foo?page=1>; rel="first", <https://example.com:8080/foo?page=2>; rel="prev", <https://example.com:8080/foo?page=4>; rel="next", <https://example.com:8080/foo?page=50>; rel="last"
Page-Items 20
Total-Pages 50
Total-Count 1000
```

#### Customize the header names

If you are replacing any of the existing API-pagination gems in some already working app, you may want to customize the header names so you will not have to change the client app that consumes them. You can do so by using the `:headers` variable, set as usual at the global level or instance level.

For example, the following will change the header names and will suppres the `:pages` ('Total-Pages') header:

```ruby
# globally
Pagy::VARS[:headers] = {items: 'Per-Page', pages: false, count: 'Total'}
# or for single instance
pagy, records = pagy(collection, items: 'Per-Page', pages: false, count: 'Total'}
```

#### Countless Pagination

If your requirements allow to save one count-query per rendering by using the `pagy_countless` constructor, the headers will miss only the `rel="last"` link and the `Total-Count` (unknown with countless pagination).

Example of HTTP headers produced from a `Pagy::Countless` object:

```
Link <https://example.com:8080/foo?page=1>; rel="first", <https://example.com:8080/foo?page=2>; rel="prev", <https://example.com:8080/foo?page=4>; rel="next"
Page-Items 20
```

## Methods

This extra adds a few methods available in your controllers.

### pagy_headers_merge(pagy)

This method relies on the `response` method in your controller returning a `Rack::Response` object.

You should use it before rendering: it simply merges the `pagy_headers` to the `response.headers` internaly.

**Notice**: If your app doesn't implement the `response` object that way, you can still use the hash returned by the `pagy_headers` method to custom-send the http headers with whatever is your app way.

### pagy_headers(pagy)

This method generates a hash of [RFC-8288](https://tools.ietf.org/html/rfc8288) compliant http headers to send with the response. It is internally used by the `pagy_headers_merge` method, so you usually don't need to use it directly.

### pagy_headers_hash(pagy)

This method generates a hash structure of the headers, useful if you want to include some meta-data within your json. For example:

```ruby
render json: records.as_json.merge!(meta: {pagination: pagy_headers_hash(pagy)})
```


8 changes: 1 addition & 7 deletions docs/how-to.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,13 +414,7 @@ When the count caching is not an option, you may want to use the [countless extr

## Adding HTTP headers

The HTTP pagination headers are useful for APIs and Pagy will implement that feature in a next release (probably v2.1.0). Meanwhile there are a few gems that support Pagy and add the HTTP headers to the response for you in a quite automatic way.

Please, take a look at:

- [api-pagination](https://github.com/davidcelis/api-pagination)
- [pager-api](https://github.com/IcaliaLabs/pager-api)
- [paginate-responder](https://github.com/jgraichen/paginate-responder)
Pagy implements the [RFC-8288](https://tools.ietf.org/html/rfc8288) compilant http response headers (and other helpers) useful for API pagination: no need of other dependencies. See the [headers extra](http://ddnexus.github.io/pagy/extras/headers) documentation and examples.

## Using the pagy_info helper

Expand Down
Loading

0 comments on commit 8bd19d6

Please sign in to comment.