{"id":13394915,"url":"https://github.com/basecamp/geared_pagination","last_synced_at":"2025-07-20T03:31:15.834Z","repository":{"id":19292566,"uuid":"86708201","full_name":"basecamp/geared_pagination","owner":"basecamp","description":"Paginate Active Record sets at variable speeds","archived":false,"fork":false,"pushed_at":"2025-03-17T21:38:35.000Z","size":79,"stargazers_count":879,"open_issues_count":7,"forks_count":22,"subscribers_count":26,"default_branch":"master","last_synced_at":"2025-07-18T05:34:45.504Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/basecamp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-30T13:52:58.000Z","updated_at":"2025-06-29T19:56:50.000Z","dependencies_parsed_at":"2024-01-13T03:01:02.036Z","dependency_job_id":"70827437-6601-4572-a438-7da6ed249a16","html_url":"https://github.com/basecamp/geared_pagination","commit_stats":{"total_commits":44,"total_committers":14,"mean_commits":3.142857142857143,"dds":0.8181818181818181,"last_synced_commit":"48031efd40173fa59795c8e0a7441872fcf8cca1"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/basecamp/geared_pagination","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/basecamp%2Fgeared_pagination","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/basecamp%2Fgeared_pagination/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/basecamp%2Fgeared_pagination/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/basecamp%2Fgeared_pagination/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/basecamp","download_url":"https://codeload.github.com/basecamp/geared_pagination/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/basecamp%2Fgeared_pagination/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266063099,"owners_count":23870716,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-30T17:01:36.027Z","updated_at":"2025-07-20T03:31:15.817Z","avatar_url":"https://github.com/basecamp.png","language":"Ruby","funding_links":[],"categories":["Ruby","Gems"],"sub_categories":["Performance Optimization"],"readme":"# Geared Pagination\n\nMost pagination schemes use a fixed page size. Page 1 returns as many elements as page 2. But that's\nfrequently not the most sensible way to page through a large recordset when you care about serving the\ninitial request as quickly as possible. This is particularly the case when using the pagination scheme\nin combination with an infinite scrolling UI.\n\nGeared Pagination allows you to define different ratios. By default, we will return 15 elements on page 1,\n30 on page 2, 50 on page 3, and 100 from page 4 and forward. This has proven to be a very sensible set of\nratios for much of the Basecamp UIs. But you can of course tweak the ratios, use fewer, or even none at all,\nif a certain page calls for a fixed-rate scheme.\n\nOn JSON actions that set a page, we'll also automatically set Link and X-Total-Count headers for APIs\nto be able to page through a recordset.\n\n## Example\n\n```ruby\nclass MessagesController \u003c ApplicationController\n  def index\n    set_page_and_extract_portion_from Message.order(created_at: :desc)\n  end\nend\n\n# app/views/messages/index.html.erb\n\nShowing page \u003c%= @page.number %\u003e of \u003c%= @page.recordset.page_count %\u003e (\u003c%= @page.recordset.records_count %\u003e total messages):\n\n\u003c%= render @page.records %\u003e\n\n\u003c% if @page.last? %\u003e\n  No more pages!\n\u003c% else %\u003e\n  \u003c%= link_to \"Next page\", messages_path(page: @page.next_param) %\u003e\n\u003c% end %\u003e\n\n```\n\n## Cursor-based pagination\n\nBy default, Geared Pagination uses *offset-based pagination*: the `page` query parameter contains the page number. Each page’s records are located using a query with an `OFFSET` clause, like so:\n\n```sql\nSELECT *\nFROM messages\nORDER BY created_at DESC\nLIMIT 30\nOFFSET 15\n```\n\nYou may prefer to use *cursor-based pagination* instead. In cursor-based pagination, the `page` parameter contains a “cursor” describing the last row of the previous page. Each page’s records are located using a query with conditions that only match records after the previous page. For example, if the last record on the previous page had a `created_at` value of `2019-01-24T12:35:26.381Z` and an ID of `7354857`, the current page’s records would be found with a query like this one:\n\n```sql\nSELECT *\nFROM messages\nWHERE (created_at = '2019-01-24T12:35:26.381Z' AND id \u003c 7354857)\nOR created_at \u003c '2019-01-24T12:35:26.381Z'\nORDER BY created_at DESC, id DESC\nLIMIT 30\n```\n\nGeared Pagination supports cursor-based pagination. To use it, pass the `:ordered_by` option to `set_page_and_extract_portion_from` in your controllers. Provide the orders to apply to the paginated relation:\n\n```ruby\nset_page_and_extract_portion_from Message.all, ordered_by: { created_at: :desc, id: :desc }\n```\n\nGeared Pagination uses the ordered attributes (in the above example, `created_at` and `id`) to generate cursors:\n\n```erb\n\u003c%= link_to \"Next page\", messages_path(page: @page.next_param) %\u003e\n\u003c!-- \u003ca href=\"/messages?page=eyJwYWdlX251...\"\u003eNext page\u003c/a\u003e --\u003e\n```\n\nCursors encode the information Geared Pagination needs to query for the corresponding page’s records: the page number for choosing a page size, and the values of each of the ordered attributes (`created_at` and `id`).\n\n### When should I use cursor-based pagination?\n\nCursor-based pagination can outperform offset-based pagination when paginating deeply into a large number of records. DBs commonly execute queries with `OFFSET` clauses by counting past `OFFSET` records one at a time, so each page in offset-based pagination takes slightly longer to load than the last. With cursor-based pagination and an appropriate index, the DB can jump directly to the beginning of each page without scanning.\n\nThe tradeoff is that Geared Pagination only supports cursor-based pagination on simple relations with simple, column-only orders. Cursor-based pagination also won’t perform better than offset-based pagination without an ordered index. Stick with offset-based pagination if:\n* You need complex ordering on a complex relation\n* You’re paginating a small and/or bounded number of records\n\n## Caching\n\nTo account for the current page in fragment caches, include the `@page` directly.\nThat includes the current page number and gear ratios.\n\nFragment caching a message's comments:\n```ruby\n\u003c% cache [ @message, @page ] do %\u003e\n  \u003c%= render @page.records %\u003e\n\u003c% end %\u003e\n```\n\nNOTE: The page does not include cache keys for all the records. That would require loading all the records,\ndefeating the purpose of using the cache. Use a parent record, like a message that's touched when\nnew comments are posted, as the cache key instead.\n\n## ETags\n\nWhen a controller action sets an ETag and uses geared pagination, the current page and gear ratios are\nautomatically included in the ETag.\n\n## License\nGeared Pagination is released under the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbasecamp%2Fgeared_pagination","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbasecamp%2Fgeared_pagination","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbasecamp%2Fgeared_pagination/lists"}