{"id":13484283,"url":"https://github.com/piotrmurach/loaf","last_synced_at":"2025-11-11T18:39:35.974Z","repository":{"id":1709949,"uuid":"2439372","full_name":"piotrmurach/loaf","owner":"piotrmurach","description":"Manages and displays breadcrumb trails in Rails app - lean \u0026 mean.","archived":false,"fork":false,"pushed_at":"2022-03-11T00:43:42.000Z","size":334,"stargazers_count":405,"open_issues_count":4,"forks_count":21,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-10-25T15:42:56.636Z","etag":null,"topics":["breadcrumb-trail","breadcrumbs","rails","ruby-gem","ruby-on-rails"],"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/piotrmurach.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"piotrmurach"}},"created_at":"2011-09-22T19:08:34.000Z","updated_at":"2025-08-23T11:37:46.000Z","dependencies_parsed_at":"2022-08-28T13:01:51.156Z","dependency_job_id":null,"html_url":"https://github.com/piotrmurach/loaf","commit_stats":null,"previous_names":["peter-murach/loaf"],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/piotrmurach/loaf","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Floaf","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Floaf/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Floaf/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Floaf/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/piotrmurach","download_url":"https://codeload.github.com/piotrmurach/loaf/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrmurach%2Floaf/sbom","scorecard":{"id":734811,"data":{"date":"2025-08-11","repo":{"name":"github.com/piotrmurach/loaf","commit":"27b508c813f0dd32ce15c8b01f5a94550ee1ebc0"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 1/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:120: update your workflow using https://app.stepsecurity.io/secureworkflow/piotrmurach/loaf/ci.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:122: update your workflow using https://app.stepsecurity.io/secureworkflow/piotrmurach/loaf/ci.yml/master?enable=pin","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 1 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-22T15:28:16.730Z","repository_id":1709949,"created_at":"2025-08-22T15:28:16.730Z","updated_at":"2025-08-22T15:28:16.730Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":281896595,"owners_count":26580138,"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","status":"online","status_checked_at":"2025-10-30T02:00:06.501Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["breadcrumb-trail","breadcrumbs","rails","ruby-gem","ruby-on-rails"],"created_at":"2024-07-31T17:01:21.854Z","updated_at":"2025-11-11T18:39:35.945Z","avatar_url":"https://github.com/piotrmurach.png","language":"Ruby","readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg width=\"237\" src=\"https://github.com/piotrmurach/loaf/blob/master/assets/loaf_logo.png\" alt=\"Loaf gem logo\" /\u003e\n\u003c/div\u003e\n\n# Loaf\n\n[![Gem Version](https://badge.fury.io/rb/loaf.svg)][gem]\n[![Actions CI](https://github.com/piotrmurach/loaf/workflows/CI/badge.svg?branch=master)][gh_actions_ci]\n[![Maintainability](https://api.codeclimate.com/v1/badges/966193dafa3895766977/maintainability)][codeclimate]\n[![Coverage Status](https://coveralls.io/repos/github/piotrmurach/loaf/badge.svg?branch=master)][coveralls]\n[![Inline docs](http://inch-ci.org/github/piotrmurach/loaf.svg?branch=master)][inchpages]\n\n[gem]: http://badge.fury.io/rb/loaf\n[gh_actions_ci]: https://github.com/piotrmurach/loaf/actions?query=workflow%3ACI\n[codeclimate]: https://codeclimate.com/github/piotrmurach/loaf/maintainability\n[coveralls]: https://coveralls.io/github/piotrmurach/loaf\n[inchpages]: http://inch-ci.org/github/piotrmurach/loaf\n\n\u003e **Loaf** manages and displays breadcrumb trails in your Rails application.\n\n## Features\n\n* Use controllers and/or views to specify breadcrumb trails\n* Specify urls using Rails conventions\n* No markup assumptions for breadcrumbs trails rendering\n* Use locales file for breadcrumb names\n* Tested with Rails `\u003e= 3.2` and Ruby `\u003e= 2.0.0`\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem \"loaf\"\n```\n\nAnd then execute:\n\n```ruby\n$ bundle\n```\n\nOr install it yourself as:\n\n```ruby\ngem install loaf\n```\n\nThen run the generator:\n\n```ruby\nrails generate loaf:install\n```\n\n## Contents\n\n* [1. Usage](#1-usage)\n* [2. API](#2-api)\n  * [2.1 breadcrumb](#21-breadcrumb)\n    * [2.1.1 controller](#211-controller)\n    * [2.1.2 view](#212-view)\n    * [2.1.3 :match](#213-match)\n  * [2.2 breadcrumb_trail](#22-breadcrumb_trail)\n* [3. Configuration](#3-configuration)\n* [4. Translation](#4-translation)\n\n## 1. Usage\n\n**Loaf** allows you to add breadcrumbs in controllers and views.\n\nIn order to add breadcrumbs in controller use `breadcrumb` method ([see 2.1](#21-breadcrumb)).\n\n```ruby\nclass Blog::CategoriesController \u003c ApplicationController\n\n  breadcrumb \"Article Categories\", :blog_categories_path, only: [:show]\n\n  def show\n    breadcrumb @category.title, blog_category_path(@category)\n  end\nend\n```\n\nThen in your view render the breadcrumbs trail using [breadcrumb_trail](#22-breadcrumb_trail)\n\n## 2. API\n\n### 2.1 breadcrumb\n\nCreation of breadcrumb in Rails is achieved by the `breadcrumb` helper.\n\nThe `breadcrumb` method takes at minimum two arguments: the first is a name for the crumb that will be displayed and the second is a url that the name points to. The url parameter uses the familiar Rails conventions.\n\nWhen using path variable `blog_categories_path`:\n\n```ruby\nbreadcrumb \"Categories\", blog_categories_path\n```\n\nWhen using an instance `@category`:\n\n```ruby\nbreadcrumb @category.title, blog_category_path(@category)\n```\n\nYou can also use set of objects:\n\n```ruby\nbreadcrumb @category.title, [:blog, @category]\n```\n\nYou can specify segments of the url:\n\n```ruby\nbreadcrumb @category.title, {controller: \"categories\", action: \"show\", id: @category.id}\n```\n\n#### 2.1.1 controller\n\nBreadcrumbs are inherited, so if you set a breadcrumb in `ApplicationController`, it will be inserted as a first element inside every breadcrumb trail. It is customary to set root breadcrumb like so:\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  breadcrumb \"Home\", :root_path\nend\n```\n\nOutside of controller actions the `breadcrumb` helper behaviour is similar to filters/actions and as such you can limit breadcrumb scope with familiar options `:only`, `:except`. Any breadcrumb specified inside actions creates another level in breadcrumbs trail.\n\n```ruby\nclass ArticlesController \u003c ApplicationController\n  breadcrumb \"All Articles\", :articles_path, only: [:new, :create]\nend\n```\n\nEach time you call the `breadcrumb` helper, a new element is added to a breadcrumb trial stack:\n\n```ruby\nclass ArticlesController \u003c ApplicationController\n  breadcrumb \"Home\", :root_path\n  breadcrumb \"All Articles\", :articles_path\n\n  def show\n    breadcrumb \"Article One\", article_path(:one)\n    breadcrumb \"Article Two\", article_path(:two)\n  end\nend\n```\n\n**Loaf** allows you to call controller instance methods inside the `breadcrumb` helper outside of any action. This is useful if your breadcrumb has parameterized behaviour. For example, to dynamically evaluate parameters for breadcrumb title do:\n\n```ruby\nclass CommentsController \u003c ApplicationController\n  breadcrumb -\u003e { find_article(params[:post_id]).title }, :articles_path\nend\n```\n\nAlso, to dynamically evaluate parameters inside the url argument do:\n\n```ruby\nclass CommentsController \u003c ApplicationController\n  breadcrumb \"All Comments\", -\u003e { post_comments_path(params[:post_id]) }\nend\n```\n\nYou may wish to define breadcrumbs over a collection. This is easy within views, and controller actions (just loop your collection), but if you want to do this in the controller class you can use the `before_action` approach:\n\n```ruby\nbefore_action do\n  ancestors.each do |ancestor|\n    breadcrumb ancestor.name, [:admin, ancestor]\n  end\nend\n```\n\nAssume `ancestors` method is defined inside the controller.\n\n#### 2.1.2 view\n\n**Loaf** adds `breadcrumb` helper also to the views. Together with controller breadcrumbs, the view breadcrumbs are appended as the last in breadcrumb trail. For instance, to specify view breadcrumb do:\n\n```ruby\n\u003c% breadcrumb @category.title, blog_category_path(@category) %\u003e\n```\n\n#### 2.1.3 :match\n\n**Loaf** allows you to define matching conditions in order to make a breadcrumb current with the `:match` option.\n\nThe `:match` key accepts the following values:\n\n* `:inclusive` - the default value, which matches nested paths\n* `:exact` - matches only the exact same path\n* `:exclusive` - matches only direct path and its query parameters if present\n* `/regex/` - matches based on regular expression\n* `{foo: bar}` - match based on query parameters\n\nFor example, to force a breadcrumb to be the current regardless do:\n\n```ruby\nbreadcrumb \"New Post\", new_post_path, match: :exact\n```\n\nTo make a breadcrumb current based on the query parameters do:\n\n```ruby\nbreadcrumb \"Posts\", posts_path(order: :desc), match: {order: :desc}\n```\n\n### 2.2 breadcrumb_trail\n\nIn order to display breadcrumbs use the `breadcrumb_trail` view helper. It accepts optional argument of configuration options and can be used in two ways.\n\nOne way, given a block it will yield all the breadcrumbs in order of definition:\n\n```ruby\nbreadcrumb_trail do |crumb|\n  ...\nend\n```\n\nThe yielded parameter is an instance of `Loaf::Crumb` object with the following methods:\n\n```ruby\ncrumb.name     # =\u003e the name as string\ncrumb.path     # =\u003e the path as string\ncrumb.url      # =\u003e alias for path\ncrumb.current? # =\u003e true or false\n```\n\nFor example, you can add the following semantic markup to show breadcrumbs using the `breadcrumb_trail` helper like so:\n\n```erb\n\u003cnav aria-label=\"breadcrumb\"\u003e\n  \u003col class=\"breadcrumbs\"\u003e\n    \u003c% breadcrumb_trail do |crumb| %\u003e\n      \u003cli class=\"\u003c%= crumb.current? ? \"current\" : \"\" %\u003e\"\u003e\n        \u003c%= link_to crumb.name, crumb.url, (crumb.current? ? {\"aria-current\" =\u003e \"page\"} : {}) %\u003e\n        \u003c% unless crumb.current? %\u003e\u003cspan\u003e::\u003c/span\u003e\u003c% end %\u003e\n      \u003c/li\u003e\n    \u003c% end %\u003e\n  \u003c/ol\u003e\n\u003c/nav\u003e\n```\n\nFor Bootstrap 4:\n\n```erb\n\u003c% #erb %\u003e\n\u003cnav aria-label=\"breadcrumb\"\u003e\n  \u003col class=\"breadcrumb\"\u003e\n    \u003c% breadcrumb_trail do |crumb| %\u003e\n      \u003cli class=\"breadcrumb-item \u003c%= crumb.current? ? \"active\" : \"\" %\u003e\"\u003e\n        \u003c%= link_to_unless crumb.current?, crumb.name, crumb.url, (crumb.current? ? {\"aria-current\" =\u003e \"page\"} : {}) %\u003e\n      \u003c/li\u003e\n    \u003c% end %\u003e\n  \u003c/ol\u003e\n\u003c/nav\u003e\n```\n\nAnd, if you are using `HAML` do:\n\n```haml\n  - # haml\n  %ol.breadcrumb\n    - breadcrumb_trail do |crumb|\n      %li.breadcrumb-item{class: crumb.current? ? \"active\" : \"\" }\n        = link_to_unless crumb.current?, crumb.name, crumb.url, (crumb.current? ? {\"aria-current\" =\u003e \"page\"} : {})\n```\n\nUsually best practice is to put such snippet inside its own `_breadcrumbs.html.erb` partial.\n\nThe second way is to use the `breadcrumb_trail` without passing a block. In this case, the helper will return an enumerator that you can use to, for example, access an array of names pushed into the breadcrumb trail in order of addition. This can be handy for generating page titles from breadcrumb data.\n\nFor example, you can define a `breadcrumbs_to_title` method in `ApplicationHelper` like so:\n\n```ruby\nmodule ApplicationHelper\n  def breadcrumbs_to_title\n    breadcrumb_trail.map(\u0026:name).join(\"\u003e\")\n  end\nend\n```\n\nUse whichever of the two ways is more convenient given your application structure and needs.\n\n## 3. Configuration\n\nThere is a small set of custom opinionated defaults. The following options are valid parameters:\n\n```ruby\n:match # set match type, default :inclusive (see [:match](#213-match) for more details)\n```\n\nYou can override them in your views by passing them to the view `breadcrumb` helper\n\n```erb\n\u003c% breadcrumb_trail(match: :exclusive) do |name, url, styles| %\u003e\n  ..\n\u003c% end %\u003e\n```\n\nor by configuring an option in `config/initializers/loaf.rb`:\n\n```ruby\nLoaf.configure do |config|\n  config.match = :exclusive\nend\n```\n\n## 4. Translation\n\nYou can use locales files for breadcrumbs' titles. **Loaf** assumes that all breadcrumb names are scoped inside `breadcrumbs` namespace inside `loaf` scope. However, this can be easily changed by passing `scope: 'new_scope_name'` configuration option.\n\n```ruby\nen:\n  loaf:\n    breadcrumbs:\n      name: 'my-breadcrumb-name'\n```\n\nTherefore, in your controller/view you would do:\n\n```ruby\nclass Blog::CategoriesController \u003c ApplicationController\n  breadcrumb 'blog.categories', :blog_categories_path\nend\n```\n\nAnd corresponding entry in locale would be:\n\n```ruby\nen:\n  loaf:\n    breadcrumbs:\n      blog:\n        categories: 'Article Categories'\n```\n\n## Contributing\n\nQuestions or problems? Please post them on the [issue tracker](https://github.com/piotrmurach/loaf/issues). You can contribute changes by forking the project and submitting a pull request. You can ensure the tests are passing by running `bundle` and `rake`.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Loaf project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/piotrmurach/loaf/blob/master/CODE_OF_CONDUCT.md).\n\n## Copyright\n\nCopyright (c) 2011 Piotr Murach. See LICENSE.txt for further details.\n","funding_links":["https://github.com/sponsors/piotrmurach"],"categories":["Navigation","Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiotrmurach%2Floaf","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpiotrmurach%2Floaf","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiotrmurach%2Floaf/lists"}