{"id":18544419,"url":"https://github.com/instructure/folio","last_synced_at":"2025-04-09T19:30:44.036Z","repository":{"id":11487431,"uuid":"13960039","full_name":"instructure/folio","owner":"instructure","description":"Folio is a library for pagination. It's meant to be nearly compatible with WillPaginate, but with broader -- yet more well-defined -- semantics to allow for sources whose page identifiers are non-ordinal.","archived":false,"fork":false,"pushed_at":"2017-03-07T09:01:36.000Z","size":65,"stargazers_count":7,"open_issues_count":0,"forks_count":2,"subscribers_count":13,"default_branch":"master","last_synced_at":"2025-03-24T10:49:16.826Z","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/instructure.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-10-29T15:37:04.000Z","updated_at":"2022-07-22T16:32:31.000Z","dependencies_parsed_at":"2022-08-31T05:04:33.106Z","dependency_job_id":null,"html_url":"https://github.com/instructure/folio","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instructure%2Ffolio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instructure%2Ffolio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instructure%2Ffolio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/instructure%2Ffolio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/instructure","download_url":"https://codeload.github.com/instructure/folio/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247814898,"owners_count":21000664,"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-11-06T20:16:28.178Z","updated_at":"2025-04-09T19:30:43.412Z","avatar_url":"https://github.com/instructure.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Folio\n\n`Folio` is a library for pagination. It's meant to be nearly compatible\nwith `WillPaginate`, but with broader -- yet more well-defined --\nsemantics to allow for sources whose page identifiers are non-ordinal\n(i.e. a page identifier of `3` does not necessarily indicate the third\npage).\n\n[![Build Status](https://travis-ci.org/instructure/folio.png?branch=master)](https://travis-ci.org/instructure/folio)\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem 'folio-pagination'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install folio-pagination\n\n### Rails Support\n\nTo use Folio's optional Rails support, you will need to load the 'will_paginate'\ngem into your application along with folio, but don't require it. For\ninstance in your Gemfile:\n\n    gem 'will_paginate', require: false\n\nAnd then you can:\n\n    require 'folio/rails'\n\nThis will load just the necessary portions of the will_paginate gem.\n\n## Usage\n\nThe core `Folio` interface is defined by two mixins. Mixing `Folio` into\na source of items creates a \"folio\" and provides pagination on that\nfolio. Mixing `Folio::Page` into a subset of items from a folio creates\na \"page\" with additional properties relating it to the folio and the\nother pages in the folio.\n\n`Folio` also provides some basic implementations, both standalone and by\nmixing these modules in to familiar classes.\n\n### Pages\n\nYou can mix `Folio::Page` into any `Enumerable`. The mixin gives you\neight attributes and one method:\n\n * `ordinal_pages?` indicates whether the page identifiers in\n   `current_page`, `first_page`, `last_page`, `previous_page`, and\n   `next_page` should be considered ordinal or not.\n\n * `current_page` is the page identifier addressing this page within the\n   folio.\n\n * `per_page` is the number of items requested from the folio when\n   filling this page.\n\n * `first_page` is the page identifier addressing the first page within\n   the folio.\n\n * `last_page` is the page identifier addressing the final page within\n   the folio, if known.\n\n * `next_page` is the page identifier addressing the immediately\n   following page within the folio, if there is one.\n\n * `previous_page` is the page identifier addressing the immediately\n   preceding page within the folio, if there is one and it is known.\n\n * `total_entries` is the number of items in the folio, if known.\n\n * `total_pages` if the number of pages in the folio, if known. It is\n   calculated from `total_entries` and `per_page`.\n\n`ordinal_pages?`, `first_page`, and `last_page` are common to all pages\ncreated by a folio and are configured, as available, when the folio\ncreates a blank page in its `build_page` method (see below).\n\n`current_page`, `per_page`, and `total_entries` control the filling of a\npage and are configured from parameters to the folio's `paginate`\nmethod.\n\n`next_page` and `previous_page` are configured, as available, when the\nfolio fills the configured page in its `fill_page` method (see below).\n\n### Folios\n\nYou can mix `Folio` into any class implementing two methods:\n\n * `build_page` is responsible for instantiating a `Folio::Page` and\n   configuring its `ordinal_pages?`, `first_page`, and `last_page`\n   attributes.\n\n * `fill_page` will receive a `Folio::Page` with the `ordinal_pages?`,\n   `first_page`, `last_page`, `current_page`, `per_page`, and\n   `total_entries` attributes configured, and should populate the page\n   with the corresponding items from the folio. It should also set\n   appropriate values for the `next_page` and `previous_page` attributes\n   on the page. If the value provided in the page's `current_page`\n   cannot be interpreted as addressing a page in the folio,\n   `Folio::InvalidPage` should be raised.\n\nIn return, `Folio` provides a `paginate` method and `per_page`\nattributes for both the folio class and for individual folio instances.\n\nThe `paginate` method coordinates the page creation, configuration, and\npopulation. It takes three parameters: `page`, `per_page`, and\n`total_entries`, each optional.\n\n * `page` configures the page's `current_page`, defaulting to the page's\n   `first_page`.\n\n * `per_page` configures the page's `per_page`, defaulting to the\n   folio's `per_page` attribute.\n\n * `total_entries` configures the page's `total_entries`, if present.\n   otherwise, if the folio implements a `count` method, the page's\n   `total_entries` will be set from that method.\n\nNOTE: providing a `total_entries` parameter of nil will still bypass the\n`count` method, leaving `total_entries` nil. This is useful when the\ncount would be too expensive and you'd rather just leave the number of\nentries unknown.\n\nThe `per_page` attribute added to the folio instance will default to the\n`per_page` attribute from the folio class when unset. The `per_page`\nclass attribute added to the folio class will default to global\n`Folio.per_page` when unset.\n\n### Ordinal Pages and Folios\n\nA typical use case for pagination deals with ordinal page identifiers;\ne.g. \"1\" means the first page, \"2\" means the second page, etc.\n\nAs a matter of convenience for these use cases, additional mixins of\n`Folio::Ordinal` and `Folio::Ordinal::Page` are provided.\n\nMixing `Folio::Ordinal::Page` into an `Enumerable` provides the same\nmethods as `Folio::Page` but with the following overrides:\n\n * `ordinal_pages` is always true\n\n * `first_page` is always 1\n\n * `previous_page` is always either `current_page-1` or nil, depending\n   on how `current_page` relates to `first_page`.\n\n * `next_page` can only be set if `total_pages` is unknown. if\n   `total_pages` is known, `next_page` will be either `current_page+1`\n   or nil, depending on how `current_page` relates to `last_page`. if\n   `total_pages` is unknown and `next_page` is unset (vs. explicitly set\n   to nil), it will default to `current_page+1`.\n\n * `last_page` is deterministic: always `total_pages` if `total_pages`\n   is known, `current_page` if `total_pages` is unknown and `next_page`\n   is nil, nil otherwise (indicating the page sequence continues until\n   `next_page` is nil).\n\nSimilarly, mixing `Folio::Ordinal` into a source provides the same\nmethods as `Folio`, but simplifies your `build_page` and `fill_page`\nmethods by moving some responsibility into the `paginate` method.\n`build_page` also has a default implementation.\n\n * `build_page` no longer needs to configure `ordinal_page?`, `first_page`,\n   or `last_page` on the instantiated page. Instead, just instantiate\n   and return a `Folio::Page` or `Folio::Ordinal::Page`. Then\n   `ordinal_page?`, `first_page`, and `last_page` are handled for you,\n   as described above. If not provided, the default implementation just\n   returns a subclass of `Array` setup to be a `Folio::Ordinal::Page`.\n\n * `fill_page` no longer needs to configure `next_page` and\n   `previous_page`; the ordinal page will handle them. (Note that if\n   necessary, you can still set `next_page` explicitly to nil.) Also,\n   `paginate` will now perform ordinal bounds checking for you, so you\n   can focus entirely on populating the page.\n\n### `BasicPage`s and `create`\n\nOften times you just want to take the simplest collection possible. One\nway would be to subclass `Array` and mixin `Folio::Page`, then\ninstantiate the subclass. When you want to add more, or `Array` isn't the\nproper superclass, you can still do this.\n\nFor the common case we've already done it. This is the\n`Folio::BasicPage` class. We've also provided a shortcut for\ninstantiating one: `Folio::Page.create`. So, for example, a simple\n`build_page` method could just be:\n\n```\n  def build_page\n    page = Folio::Page.create\n    # setup ordinal_pages?, first_page, etc.\n    page\n  end\n```\n\n`Folio::Ordinal::BasicPage` and `Folio::Ordinal::Page.create` are also\navailable, respectively, for the ordinal case.\n\n### Enumerable Extension\n\nIf you require `folio/core_ext/enumerable`, all `Enumerable`s will be\nextended with `Folio::Ordinal` and naive `build_page` and `fill_page`\nmethods.\n\n`build_page` will simply return a basic ordinal page as from\n`Folio::Page::Ordinal.create`. `fill_page` then selects an appropriate\nrange of items from the folio using standard `Enumerable` methods, then\ncalls `replace` on the page (it's a `Folio::Ordinal::BasicPage`) with\nthis subset.\n\nThis lets you do things like:\n\n```\nrequire 'folio/core_ext/enumerable'\n\nnatural_numbers = Enumerator.new do |enum|\n  n = 0\n  loop{ enum.yield(n += 1) }\nend\npage = natural_numbers.paginate(page: 3, per_page: 5, total_entries: nil)\n\npage.ordinal_pages?  #=\u003e true\npage.per_page        #=\u003e 5\npage.first_page      #=\u003e 1\npage.previous_page   #=\u003e 2\npage.current_page    #=\u003e 3\npage.next_page       #=\u003e 4\npage.last_page       #=\u003e nil\npage.total_entries   #=\u003e nil\npage.total_pages     #=\u003e nil\npage                 #=\u003e [11, 12, 13, 14, 15]\n```\n\n## Contributing\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finstructure%2Ffolio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finstructure%2Ffolio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finstructure%2Ffolio/lists"}