{"id":13484390,"url":"https://github.com/glebm/order_query","last_synced_at":"2025-11-11T11:37:14.022Z","repository":{"id":14886207,"uuid":"17609855","full_name":"glebm/order_query","owner":"glebm","description":"Find next / previous Active Record(s) in one query","archived":false,"fork":false,"pushed_at":"2024-11-15T18:37:53.000Z","size":188,"stargazers_count":511,"open_issues_count":4,"forks_count":22,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-03T19:08:49.021Z","etag":null,"topics":["activerecord","keyset-paging","pagination","rails","ruby"],"latest_commit_sha":null,"homepage":"","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/glebm.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","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":"2014-03-10T21:50:30.000Z","updated_at":"2025-03-09T20:18:19.000Z","dependencies_parsed_at":"2022-07-23T11:16:25.691Z","dependency_job_id":"b34220fb-8df9-413d-9a3a-3cab142808e1","html_url":"https://github.com/glebm/order_query","commit_stats":{"total_commits":165,"total_committers":11,"mean_commits":15.0,"dds":0.08484848484848484,"last_synced_commit":"4a8113891733a43703d9b2f5c8e18e52cd1c8ea4"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glebm%2Forder_query","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glebm%2Forder_query/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glebm%2Forder_query/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/glebm%2Forder_query/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/glebm","download_url":"https://codeload.github.com/glebm/order_query/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248335085,"owners_count":21086509,"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":["activerecord","keyset-paging","pagination","rails","ruby"],"created_at":"2024-07-31T17:01:23.529Z","updated_at":"2025-11-11T11:37:14.013Z","avatar_url":"https://github.com/glebm.png","language":"Ruby","readme":"# order_query [![Build Status][travis-badge]][travis] [![Coverage Status][coverage-badge]][coverage]\n\n\u003ca href=\"http://use-the-index-luke.com/no-offset\"\u003e\n  \u003cimg src=\"http://use-the-index-luke.com/img/no-offset.q200.png\" alt=\"100% offset-free\" align=\"right\" width=\"106\" height=\"106\"\u003e\n\u003c/a\u003e\n\nThis gem finds the next or previous record(s) relative to the current one efficiently using [keyset pagination](http://use-the-index-luke.com/no-offset), e.g. for navigation or infinite scroll.\n\n## Installation\n\nAdd to Gemfile:\n\n```ruby\ngem 'order_query', '~\u003e 0.5.6'\n```\n\n## Usage\n\nUse `order_query(scope_name, *order_option)` to create scopes and class methods\nin your model and specify how you want results ordered. A basic example:\n\n```ruby\nclass Post \u003c ActiveRecord::Base\n  include OrderQuery\n  order_query :order_home,\n    [:pinned, [true, false]], # First sort by :pinned over t/f in :desc order\n    [:published_at, :desc] # Next sort :published_at in :desc order\nend\n```\n\nEach order option specified in `order_query` is an array in the following form:\n\n1. Symbol of the attribute name (required).\n2. An array of values to order by, such as `%w(high medium low)` or `[true, false]` (optional).\n3. Sort direction, `:asc` or `:desc` (optional). Default: `:asc`; `:desc` when values to order by are specified.\n4. A hash (optional):\n\n| option     | description                                                                |\n|------------|----------------------------------------------------------------------------|\n| unique     | Unique attribute. Default: `true` for primary key, `false` otherwise.      |\n| sql        | Customize column SQL.                                                      |\n| nulls      | If set to `:first` or `:last`, orders `NULL`s accordingly.                 |\n\nIf no unique column is specified, `[primary_key, :asc]` is used. Unique column must be last.\n\n### Scopes for `ORDER BY`\n\n```ruby\nPost.published.order_home         #=\u003e #\u003cActiveRecord::Relation\u003e\nPost.published.order_home_reverse #=\u003e #\u003cActiveRecord::Relation\u003e\n```\n\n### Before / after, previous / next, and position\n\nFirst, get an `OrderQuery::Point` for the record:\n\n```ruby\np = Post.published.order_home_at(Post.find(31)) #=\u003e #\u003cOrderQuery::Point\u003e\n```\n\nIt exposes these finder methods:\n\n```ruby\np.before   #=\u003e #\u003cActiveRecord::Relation\u003e\np.after    #=\u003e #\u003cActiveRecord::Relation\u003e\np.previous #=\u003e #\u003cPost\u003e\np.next     #=\u003e #\u003cPost\u003e\np.position #=\u003e 5\n```\n\nThe `before` and `after` methods also accept a boolean argument that indicates\nwhether the relation should exclude the given point or not.\nBy default the given point is excluded, if you want to include it,\nuse `before(false)` / `after(false)`.\n\nIf you want to obtain only a chunk (i.e., a page), use `before` or `after`\nwith ActiveRecord's `limit` method:\n\n```ruby\np.after.limit(20) #=\u003e #\u003cActiveRecord::Relation\u003e\n```\n\nLooping to the first / last record is enabled for `next` / `previous` by default. Pass `false` to disable:\n\n```ruby\np = Post.order_home_at(Post.order_home.first)\np.previous        #=\u003e #\u003cPost\u003e\np.previous(false) #=\u003e nil\n```\n\nEven with looping, `nil` will be returned if there is only one record.\n\nYou can also get an `OrderQuery::Point` from an instance and a scope:\n\n```ruby\nposts = Post.published\npost  = posts.find(42)\npost.order_home(posts) #=\u003e #\u003cOrderQuery::Point\u003e\n```\n\n### Dynamic columns\n\nQuery with dynamic order columns using the `seek(*order)` class method:\n\n```ruby\nspace = Post.visible.seek([:id, :desc]) #=\u003e #\u003cOrderQuery::Space\u003e\n```\n\nThis returns an `OrderQuery::Space` that exposes these methods:\n\n```ruby\nspace.scope           #=\u003e #\u003cActiveRecord::Relation\u003e\nspace.scope_reverse   #=\u003e #\u003cActiveRecord::Relation\u003e\nspace.first           #=\u003e scope.first\nspace.last            #=\u003e scope_reverse.first\nspace.at(Post.first)  #=\u003e #\u003cOrderQuery::Point\u003e\n```\n\n`OrderQuery::Space` is also available for defined order_queries:\n\n```ruby\nPost.visible.order_home_space #=\u003e #\u003cOrderQuery::Space\u003e\n```\n\nAlternatively, get an `OrderQuery::Point` using the `seek(scope, *order)` instance method:\n\n```ruby\nPost.find(42).seek(Post.visible, [:id, :desc]) #=\u003e #\u003cOrderQuery::Point\u003e\n# scope defaults to Post.all\nPost.find(42).seek([:id, :desc]) #=\u003e #\u003cOrderQuery::Point\u003e\n```\n\n### Advanced example\n\n```ruby\nclass Post \u003c ActiveRecord::Base\n  include OrderQuery\n  order_query :order_home,\n    # For an array of order values, default direction is :desc\n    # High-priority issues will be ordered first in this example\n    [:priority, %w(high medium low)],\n    # A method and custom SQL can be used instead of an attribute\n    [:valid_votes_count, :desc, sql: '(votes - suspicious_votes)'],\n    # Default sort order for non-array columns is :asc, just like SQL\n    [:updated_at, :desc],\n    # pass unique: true for unique attributes to get more optimized queries\n    # unique is true by default for primary_key\n    [:id, :desc]\n  def valid_votes_count\n    votes - suspicious_votes\n  end\nend\n```\n\n## How it works\n\nInternally this gem builds a query that depends on the current record's values and looks like this:\n\n```sql\n-- Current post: pinned=true published_at='2014-03-21 15:01:35.064096' id=9\nSELECT \"posts\".* FROM \"posts\"  WHERE\n  (\"posts\".\"pinned\" = 'f' OR\n   \"posts\".\"pinned\" = 't' AND (\n      \"posts\".\"published_at\" \u003c '2014-03-21 15:01:35.064096' OR\n      \"posts\".\"published_at\" = '2014-03-21 15:01:35.064096' AND \"posts\".\"id\" \u003c 9))\nORDER BY\n  \"posts\".\"pinned\"='t' DESC, \"posts\".\"pinned\"='f' DESC,\n  \"posts\".\"published_at\" DESC,\n  \"posts\".\"id\" DESC\nLIMIT 1\n```\n\nThe actual query is a bit different because `order_query` wraps the top-level `OR` with a (redundant) non-strict column `x0' AND (x0 OR ...)`\nfor [performance reasons](https://github.com/glebm/order_query/issues/3).\nThis can be disabled with `OrderQuery.wrap_top_level_or = false`.\n\nSee the implementation in [sql/where.rb](/lib/order_query/sql/where.rb).\n\nSee how this affects query planning in Markus Winand's slides on [Pagination done the Right Way](http://use-the-index-luke.com/blog/2013-07/pagination-done-the-postgresql-way).\n\nThis project uses MIT license.\n\n\n[travis]: http://travis-ci.org/glebm/order_query\n[travis-badge]: http://img.shields.io/travis/glebm/order_query.svg\n[gemnasium]: https://gemnasium.com/glebm/order_query\n[coverage]: https://codeclimate.com/github/glebm/order_query\n[coverage-badge]: https://api.codeclimate.com/v1/badges/82e424e9ee2acb02292c/test_coverage\n","funding_links":[],"categories":["Ruby","Pagination"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglebm%2Forder_query","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fglebm%2Forder_query","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglebm%2Forder_query/lists"}