batmanjs/batman — pull request #920 by angelini (2013-11-29 03:57:55+00:00)
Title: New paginator

## Paginator

This PR includes the foundation work for the new paginator.

This is my third attempt at solving pagination, and I think I have finally in a boiled it down to the bare minimum API. As you probably noticed, this whole PR is only about 100 LOC.

It is composed of three parts:

**Batman.Query**

This object is used to define model search constraints and uses a chaining API inspired by ActiveRecord.

```coffee
Users.where(admin: true).limit(10).order('created_at DESC')
```

**Model::search**

This method takes a query object and passes it off to the model's storage adapter. The adapter is then in charge of requesting the data based on the query's search constraints.

This method is never called directly but is used by `Batman.Query::load`

```coffee
Users.offset(10).load (errors, users) -> console.log(users)
```

**Batman.Paginator**

This is the base paginator class. It actually has no methods and does nothing but create a query object. It is then extended by `Batman.OffsetPaginator` and `Batman.RelativePaginator`. These paginators actually implement the base pagination methods: `[first, next, prev]`

A paginators only responsibility is to modify it's internal query object and pass it off to `Model::search`.

```coffee
class Sample.TestsController extends Batman.Controller
index: ->
paginator = new Batman.OffsetPaginator(Sample.Test, limit: 2)
@set('paginator', paginator).first (err, samples) =>
@set('models', samples)

next: ->
@get('paginator').next (err, samples) =>
@set('models', samples)

prev: ->
@get('paginator').prev (err, samples) =>
@set('models', samples)
```

## Where to go from here

Obviously these 100 lines do not begin to cover all of the use cases of Shopify's internal paginator, but I would like to cover how they can be re-created using this as a foundation.

**Different Search Backends**

Our current paginator is responsible for selecting which search backend is required to perform a specific pagination, it is also responsible for building the request to query that backend.

In this implementation that responsibility is handed off to the model and it's storage adapter. Every model knows how it's specifically queried and this logic is no longer bundled into pagination. This also allows us to re-use the new search method for queries unrelated to pagination.

**Updating the current URL**

We currently observe all kinds of properties on the paginator to update the page's URL as the user browses to give them the same results upon a hard refresh.

With this implementation, a pagination is completely described by a single query object. To update the URL, we would simply need to implement a `toUrlParam` method on `Batman.Query` and observe it's value.

**Paginating Filters**

To implement pagination filters, such as the ones currently used in Shopify, we would need to create a `Filter` object capable of converting it's current state into query constraints. When a user issues a search, each filter's constraints would be chained into the paginator's query object.

**Tests And Feature X**

This is obviously far from complete, I simply wanted to get people's opinion on the implementation before I continued with this design. I think in terms of features, everything else should be developed within Shopify as to keep the core logic as simple and re-usable as possible.

Let me know what you think

@kristianpd @pushrax @nickjs @nsimmons @jahfer @BlakeMesdag @jbruton @ibawt @davemo @DrewMartin