├── .document
├── .editorconfig
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── ci.yml
├── .gitignore
├── .rspec
├── .travis.yml
├── Appraisals
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── assets
└── loaf_logo.png
├── bin
├── console
└── setup
├── config
└── locales
│ └── loaf.en.yml
├── gemfiles
├── rails3.2.gemfile
├── rails4.0.gemfile
├── rails4.1.gemfile
├── rails4.2.gemfile
├── rails5.0.gemfile
├── rails5.1.gemfile
├── rails5.2.gemfile
├── rails6.0.gemfile
└── rails6.1.gemfile
├── lib
├── generators
│ └── loaf
│ │ └── install_generator.rb
├── loaf.rb
└── loaf
│ ├── breadcrumb.rb
│ ├── configuration.rb
│ ├── controller_extensions.rb
│ ├── crumb.rb
│ ├── errors.rb
│ ├── options_validator.rb
│ ├── railtie.rb
│ ├── translation.rb
│ ├── version.rb
│ └── view_extensions.rb
├── loaf.gemspec
├── spec
├── integration
│ ├── breadcrumb_trail_spec.rb
│ └── configuration_spec.rb
├── rails_app
│ ├── Rakefile
│ ├── app
│ │ ├── assets
│ │ │ └── config
│ │ │ │ └── manifest.js
│ │ ├── controllers
│ │ │ ├── application_controller.rb
│ │ │ ├── comments_controller.rb
│ │ │ ├── home_controller.rb
│ │ │ └── posts_controller.rb
│ │ └── views
│ │ │ ├── comments
│ │ │ └── index.html.erb
│ │ │ ├── home
│ │ │ └── index.html.erb
│ │ │ ├── layouts
│ │ │ ├── _breadcrumbs.html.erb
│ │ │ └── application.html.erb
│ │ │ └── posts
│ │ │ ├── index.html.erb
│ │ │ ├── new.html.erb
│ │ │ └── show.html.erb
│ ├── config.ru
│ ├── config
│ │ ├── application.rb
│ │ ├── boot.rb
│ │ ├── database.yml
│ │ ├── environment.rb
│ │ ├── environments
│ │ │ ├── development.rb
│ │ │ ├── production.rb
│ │ │ └── test.rb
│ │ ├── initializers
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── inflections.rb
│ │ │ ├── mime_types.rb
│ │ │ ├── secret_token.rb
│ │ │ ├── session_store.rb
│ │ │ └── wrap_parameters.rb
│ │ ├── locales
│ │ │ ├── en.yml
│ │ │ └── loaf.en.yml
│ │ ├── routes.rb
│ │ └── secrets.yml
│ ├── db
│ │ └── seeds.rb
│ ├── log
│ │ └── .gitkeep
│ └── public
│ │ ├── 404.html
│ │ ├── 422.html
│ │ ├── 500.html
│ │ ├── favicon.ico
│ │ └── robots.txt
├── spec_helper.rb
├── support
│ ├── capybara.rb
│ ├── dummy_controller.rb
│ ├── dummy_view.rb
│ └── load_routes.rb
└── unit
│ ├── configuration_spec.rb
│ ├── controller_extensions_spec.rb
│ ├── crumb_spec.rb
│ ├── generators
│ └── install_generator_spec.rb
│ ├── options_validator_spec.rb
│ ├── translation_spec.rb
│ └── view_extensions
│ ├── breadcrumb_spec.rb
│ ├── breadcrumb_trail_spec.rb
│ └── has_breadcrumbs_spec.rb
└── tasks
├── console.rake
├── coverage.rake
└── spec.rake
/.document:
--------------------------------------------------------------------------------
1 | lib/**/*.rb
2 | bin/*
3 | -
4 | features/**/*.feature
5 | LICENSE.txt
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.rb]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 2
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: piotrmurach
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Are you in the right place?
2 | * For issues or feature requests file a GitHub issue in this repository
3 | * For general questions or discussion post on StackOverflow
4 |
5 | ### Describe the problem
6 | A brief description of the issue/feature.
7 |
8 | ### Steps to reproduce the problem
9 | ```
10 | Your code here to reproduce the issue
11 | ```
12 |
13 | ### Actual behaviour
14 | What happened? This could be a description, log output, error raised etc...
15 |
16 | ### Expected behaviour
17 | What did you expect to happen?
18 |
19 | ### Describe your environment
20 |
21 | * OS version:
22 | * Ruby version:
23 | * Loaf version:
24 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Describe the change
2 | What does this Pull Request do?
3 |
4 | ### Why are we doing this?
5 | Any related context as to why is this is a desirable change.
6 |
7 | ### Benefits
8 | How will the library improve?
9 |
10 | ### Drawbacks
11 | Possible drawbacks applying this change.
12 |
13 | ### Requirements
14 |
15 | - [ ] Tests written & passing locally?
16 | - [ ] Code style checked?
17 | - [ ] Rebased with `master` branch?
18 | - [ ] Documentation updated?
19 | - [ ] Changelog updated?
20 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: CI
3 | on:
4 | push:
5 | branches:
6 | - master
7 | paths-ignore:
8 | - "bin/**"
9 | - "*.md"
10 | pull_request:
11 | branches:
12 | - master
13 | paths-ignore:
14 | - "bin/**"
15 | - "*.md"
16 | jobs:
17 | tests:
18 | name: Ruby ${{ matrix.ruby }}, ${{ matrix.gemfile }}
19 | runs-on: ${{ matrix.os }}
20 | strategy:
21 | fail-fast: false
22 | matrix:
23 | ruby:
24 | - 2.1
25 | - 2.2
26 | - 2.3
27 | - 2.4
28 | - 2.5
29 | - 2.6
30 | - 2.7
31 | - 3.0
32 | gemfile:
33 | - gemfiles/rails3.2.gemfile
34 | - gemfiles/rails4.0.gemfile
35 | - gemfiles/rails4.1.gemfile
36 | - gemfiles/rails4.2.gemfile
37 | - gemfiles/rails5.0.gemfile
38 | - gemfiles/rails5.1.gemfile
39 | - gemfiles/rails5.2.gemfile
40 | - gemfiles/rails6.0.gemfile
41 | - gemfiles/rails6.1.gemfile
42 | exclude:
43 | - ruby: 2.1
44 | gemfile: gemfiles/rails5.0.gemfile
45 | - ruby: 2.1
46 | gemfile: gemfiles/rails5.1.gemfile
47 | - ruby: 2.1
48 | gemfile: gemfiles/rails5.2.gemfile
49 | - ruby: 2.1
50 | gemfile: gemfiles/rails6.0.gemfile
51 | - ruby: 2.1
52 | gemfile: gemfiles/rails6.1.gemfile
53 | - ruby: 2.2
54 | gemfile: gemfiles/rails5.2.gemfile
55 | - ruby: 2.2
56 | gemfile: gemfiles/rails6.0.gemfile
57 | - ruby: 2.2
58 | gemfile: gemfiles/rails6.1.gemfile
59 | - ruby: 2.3
60 | gemfile: gemfiles/rails6.0.gemfile
61 | - ruby: 2.3
62 | gemfile: gemfiles/rails6.1.gemfile
63 | - ruby: 2.4
64 | gemfile: gemfiles/rails3.2.gemfile
65 | - ruby: 2.4
66 | gemfile: gemfiles/rails4.0.gemfile
67 | - ruby: 2.4
68 | gemfile: gemfiles/rails4.1.gemfile
69 | - ruby: 2.4
70 | gemfile: gemfiles/rails6.0.gemfile
71 | - ruby: 2.4
72 | gemfile: gemfiles/rails6.1.gemfile
73 | - ruby: 2.5
74 | gemfile: gemfiles/rails3.2.gemfile
75 | - ruby: 2.5
76 | gemfile: gemfiles/rails4.0.gemfile
77 | - ruby: 2.5
78 | gemfile: gemfiles/rails4.1.gemfile
79 | - ruby: 2.6
80 | gemfile: gemfiles/rails3.2.gemfile
81 | - ruby: 2.6
82 | gemfile: gemfiles/rails4.0.gemfile
83 | - ruby: 2.6
84 | gemfile: gemfiles/rails4.1.gemfile
85 | - ruby: 2.7
86 | gemfile: gemfiles/rails3.2.gemfile
87 | - ruby: 2.7
88 | gemfile: gemfiles/rails4.0.gemfile
89 | - ruby: 2.7
90 | gemfile: gemfiles/rails4.1.gemfile
91 | - ruby: 2.7
92 | gemfile: gemfiles/rails4.2.gemfile
93 | - ruby: 3.0
94 | gemfile: gemfiles/rails3.2.gemfile
95 | - ruby: 3.0
96 | gemfile: gemfiles/rails4.0.gemfile
97 | - ruby: 3.0
98 | gemfile: gemfiles/rails4.1.gemfile
99 | - ruby: 3.0
100 | gemfile: gemfiles/rails4.2.gemfile
101 | - ruby: 3.0
102 | gemfile: gemfiles/rails5.0.gemfile
103 | - ruby: 3.0
104 | gemfile: gemfiles/rails5.1.gemfile
105 | - ruby: 3.0
106 | gemfile: gemfiles/rails5.2.gemfile
107 | include:
108 | - ruby: 2.7
109 | os: ubuntu-latest
110 | coverage: true
111 | gemfile: gemfiles/rails6.1.gemfile
112 | os:
113 | - ubuntu-latest
114 | continue-on-error: ${{ endsWith(matrix.ruby, 'head') }}
115 | env:
116 | BUNDLE_GEMFILE: ${{ matrix.gemfile }}
117 | COVERAGE: ${{ matrix.coverage }}
118 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
119 | steps:
120 | - uses: actions/checkout@v2
121 | - name: Set up Ruby
122 | uses: ruby/setup-ruby@v1
123 | with:
124 | ruby-version: ${{ matrix.ruby }}
125 | bundler-cache: false
126 | - name: Install bundler
127 | run: gem install bundler -v '< 2.0'
128 | - name: Install dependencies
129 | run: bundle _1.17.3_ install --jobs 4 --retry 3
130 | - name: Run tests
131 | run: bundle exec rake ci
132 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | *.rbc
3 | .bundle
4 | .config
5 | .yardoc
6 | Gemfile.lock
7 | gemfiles/*.lock
8 | InstalledFiles
9 | _yardoc
10 | coverage
11 | doc/
12 | lib/bundler/man
13 | pkg
14 | rdoc
15 | spec/reports
16 | spec/rails_app/tmp
17 | spec/rails_app/db
18 | spec/rails_app/log
19 | tmp
20 | *.bundle
21 | *.so
22 | *.o
23 | *.a
24 | mkmf.log
25 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: ruby
3 | cache: bundler
4 | sudo: false
5 | before_install:
6 | - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
7 | - gem install bundler -v '< 2'
8 | - bundle -v
9 | - echo $TRAVIS_RUBY_VERSION
10 | script: "bundle exec rake ci"
11 | rvm:
12 | - 2.1.10
13 | - 2.2.10
14 | - 2.3.8
15 | - 2.4.5
16 | - 2.5.3
17 | - 2.6.5
18 | - 2.7.0
19 | gemfile:
20 | - gemfiles/rails3.2.gemfile
21 | - gemfiles/rails4.0.gemfile
22 | - gemfiles/rails4.1.gemfile
23 | - gemfiles/rails4.2.gemfile
24 | - gemfiles/rails5.0.gemfile
25 | - gemfiles/rails5.1.gemfile
26 | - gemfiles/rails5.2.gemfile
27 | - gemfiles/rails6.0.gemfile
28 | matrix:
29 | exclude:
30 | - rvm: 2.1.10
31 | gemfile: gemfiles/rails5.0.gemfile
32 | - rvm: 2.1.10
33 | gemfile: gemfiles/rails5.1.gemfile
34 | - rvm: 2.1.10
35 | gemfile: gemfiles/rails5.2.gemfile
36 | - rvm: 2.1.10
37 | gemfile: gemfiles/rails6.0.gemfile
38 | - rvm: 2.2.10
39 | gemfile: gemfiles/rails5.2.gemfile
40 | - rvm: 2.2.10
41 | gemfile: gemfiles/rails6.0.gemfile
42 | - rvm: 2.3.8
43 | gemfile: gemfiles/rails6.0.gemfile
44 | - rvm: 2.4.5
45 | gemfile: gemfiles/rails3.2.gemfile
46 | - rvm: 2.4.5
47 | gemfile: gemfiles/rails4.0.gemfile
48 | - rvm: 2.4.5
49 | gemfile: gemfiles/rails4.1.gemfile
50 | - rvm: 2.4.5
51 | gemfile: gemfiles/rails6.0.gemfile
52 | - rvm: 2.5.3
53 | gemfile: gemfiles/rails3.2.gemfile
54 | - rvm: 2.5.3
55 | gemfile: gemfiles/rails4.0.gemfile
56 | - rvm: 2.5.3
57 | gemfile: gemfiles/rails4.1.gemfile
58 | - rvm: 2.6.5
59 | gemfile: gemfiles/rails3.2.gemfile
60 | - rvm: 2.6.5
61 | gemfile: gemfiles/rails4.0.gemfile
62 | - rvm: 2.6.5
63 | gemfile: gemfiles/rails4.1.gemfile
64 | - rvm: 2.7.0
65 | gemfile: gemfiles/rails3.2.gemfile
66 | - rvm: 2.7.0
67 | gemfile: gemfiles/rails4.0.gemfile
68 | - rvm: 2.7.0
69 | gemfile: gemfiles/rails4.1.gemfile
70 | - rvm: 2.7.0
71 | gemfile: gemfiles/rails4.2.gemfile
72 | fast_finish: true
73 | branches:
74 | only: master
75 | notifications:
76 | email: false
77 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | rails_versions = [
2 | %w[3.2 3.2.22.5],
3 | %w[4.0 4.0.13],
4 | %w[4.1 4.1.16],
5 | %w[4.2 4.2.11],
6 | %w[5.0 5.0.7],
7 | %w[5.1 5.1.7],
8 | %w[5.2 5.2.4],
9 | %w[6.0 6.0.3],
10 | %w[6.1 6.1.0]
11 | ]
12 |
13 | rails_versions.each do |(version, rails)|
14 | gem_version = Gem::Version.new(version)
15 |
16 | appraise "rails#{version}" do
17 | gem "railties", "~> #{rails}"
18 | gem "capybara", "~> 2.18.0"
19 |
20 | if gem_version == Gem::Version.new("3.2")
21 | gem "tzinfo", "~> 0.3"
22 | end
23 |
24 | if gem_version <= Gem::Version.new("4.0")
25 | gem "test-unit", "~> 3.0"
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change log
2 |
3 | ## [v0.10.0] - 2020-11-21
4 |
5 | ### Changed
6 | * Reduce gem dependencies to `railties` by Christian Sutter (@csutter)
7 | * Use `URI::DEFAULT_PARSER` instead of deprecated `URI.parser` by (@dsazup)
8 | * Support Rails 6.1 in tests
9 |
10 | ### Fixed
11 | * Fix #breadcrumb_trail to allow overriding the match option
12 | * Fix #breadcrumb_trail to return enumerator that includes passed in match option
13 |
14 | ## [v0.9.0] - 2020-01-19
15 |
16 | ### Changed
17 | * Change gemspec to include metadata, license info and remove test artifacts
18 | * Change to update testing to include Ruby 2.7
19 | * Change to limit Ruby to 1.9.3 or greater
20 |
21 | ### Fixed
22 | * Fix Ruby 2.7 warnings
23 |
24 | ## [v0.8.1] - 2019-02-04
25 |
26 | ### Added
27 | * Add console binary
28 |
29 | ### Changed
30 | * Remove rake & appraisal binaries
31 | * Change setup binary to load correct env
32 | * Change gemspec to load files directly in without using git
33 |
34 | ## [v0.8.0] - 2018-08-07
35 |
36 | ### Changed
37 | * Change Translation to skip translating nil and empty string
38 | * Change view extension to only lookup breadcrumb name translation
39 | * Remove Configuration #crumb_length and #capitalize options
40 | * Remove CrumbFormatter to skip truncating and formatting crumb names
41 |
42 | ## Fix
43 | * Fix issue with breadcrumb names being modified
44 |
45 | ## [v0.7.0] - 2018-06-20
46 |
47 | ### Added
48 | * Add test setup for Rails 5.2 by Brendon Muir(@brendon)
49 |
50 | ### Changed
51 | * Change controller level #breadcrumb helper to accept Proc as name without controller parameter by Brendon Muir(@brendon)
52 |
53 | ## [v0.6.2] - 2018-03-30
54 |
55 | ### Added
56 | * Add :match to Configuration by Johan Kim(@hiphapis)
57 |
58 | ## [v0.6.1] - 2018-03-26
59 |
60 | ### Added
61 | * Add nil guard and clear error messages to Loaf::Crumb initialization by Dan Matthews(@dmvt)
62 |
63 | ### Fixed
64 | * Fix Loaf::Crumb to stop modifying options hash by Marcel Müller(@TheNeikos)
65 |
66 | ## [v0.6.0] - 2017-10-19
67 |
68 | ### Added
69 | * Add new :match option to allow customisation of breadcrumb matching behaviour
70 | * Add #current_crumb? for checking if breadcrumb is current in view
71 | * Add tests setup for Rails 5.0, 5.1
72 |
73 | ### Changed
74 | * Change view helper name from #breadcrumbs to #breadcrumb_trail
75 | * Change Configuration to accept attributes at initilization
76 | * Change Loaf::Railtie to load for both old and new rails versions
77 | * Remove Builder class
78 | * Remove configuration options :root, :last_crumb_linked, :style_classes
79 | * Remove #add_breadcrumbs from controller api
80 |
81 | ### Fixed
82 | * Fix current page matching logic to allow for inclusive paths
83 | * Fix controller filter to work with new Rails action semantics
84 |
85 | ## [v0.5.0] - 2015-01-31
86 |
87 | ### Added
88 | * Add generator for locales file
89 | * Add breadcrumbs scope for translations
90 | * Add ability to pass proc as title and/or url for breadcrumb helper inside controller
91 |
92 | ### Changed
93 | * Change breadcrumb formatter to use translations for title formatting
94 |
95 | ## [v0.4.0] - 2015-01-10
96 |
97 | ### Added
98 | * Add ability to force current path through :force option
99 |
100 | ### Changed
101 | * Change breadcrumbs view method to return enumerator without block
102 | * Change Crumb to ruby class and add force option
103 | * Change Configuration to ruby class and scope config options
104 | * Change format_name to only take name argument
105 | * Change to expose config settings on configuration
106 | * Update test suite to work against different rubies 1.9.x, 2.x
107 | * Test Rails 3.2.x, 4.0, 4.1, 4.2
108 |
109 | ### Fixed
110 | * Fix bug with url parameter to allow for regular rails path variables
111 |
112 | ## [v0.3.0] - 2012-02-25
113 |
114 | ### Added
115 | * Add loaf gem errors
116 | * Add custom options validator for filtering invalid breadcrumbs params
117 | * Add specs for isolated view testing.
118 |
119 | ### Changed
120 | * Renamed main gem helpers for adding breadcrumbs from `add_breadcrumb` to
121 | `breadcrumb`, both inside controllers and views.
122 |
123 | ## [v0.2.1] - 2012-02-22
124 |
125 | ### Added
126 | * Add more integration tests and fixed bug with adding breadcrumbs inside view
127 | * Add specs for controller extensions
128 |
129 | ## [v0.2.0] - 2012-02-18
130 |
131 | ### Added
132 | * Add integration tests for breadcrumbs view rendering
133 | * Add translation module for breadcrumbs title lookup
134 | * Add breadcrumb formatting module with tests
135 |
136 | ### Changed
137 | * Change gemspec with new gem dependencies, use git
138 | * Setup testing environment with dummy rails app
139 | * Refactor names for controller and view extensions
140 |
141 | ## [v0.1.0] - 2011-10-22
142 |
143 | * Initial implementation and release
144 |
145 | [v0.10.0]: https://github.com/piotrmurach/loaf/compare/v0.9.0...v0.10.0
146 | [v0.9.0]: https://github.com/piotrmurach/loaf/compare/v0.8.1...v0.9.0
147 | [v0.8.1]: https://github.com/piotrmurach/loaf/compare/v0.8.0...v0.8.1
148 | [v0.8.0]: https://github.com/piotrmurach/loaf/compare/v0.7.0...v0.8.0
149 | [v0.7.0]: https://github.com/piotrmurach/loaf/compare/v0.6.2...v0.7.0
150 | [v0.6.2]: https://github.com/piotrmurach/loaf/compare/v0.6.1...v0.6.2
151 | [v0.6.1]: https://github.com/piotrmurach/loaf/compare/v0.6.0...v0.6.1
152 | [v0.6.0]: https://github.com/piotrmurach/loaf/compare/v0.5.0...v0.6.0
153 | [v0.5.0]: https://github.com/piotrmurach/loaf/compare/v0.4.0...v0.5.0
154 | [v0.4.0]: https://github.com/piotrmurach/loaf/compare/v0.3.0...v0.4.0
155 | [v0.3.0]: https://github.com/piotrmurach/loaf/compare/v0.2.1...v0.3.0
156 | [v0.2.1]: https://github.com/piotrmurach/loaf/compare/v0.2.0...v0.2.1
157 | [v0.2.0]: https://github.com/piotrmurach/loaf/compare/v0.1.0...v0.2.0
158 | [v0.1.0]: https://github.com/piotrmurach/loaf/compare/v0.1.0...HEAD
159 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at piotr@piotrmurach.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gemspec
4 |
5 | gem 'ammeter', '~> 1.1.4'
6 | gem 'appraisal', '~> 2.2.0'
7 | gem 'yard', '~> 0.9.24'
8 | gem 'capybara', '~> 3.30.0'
9 | gem 'rspec-rails', '~> 3.9.0'
10 | gem 'public_suffix', '~> 2.0.5'
11 |
12 | group :metrics do
13 | gem 'coveralls', '0.8.23'
14 | gem 'simplecov', '~> 0.16.1'
15 | gem 'yardstick', '~> 0.9.9'
16 | end
17 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Piotr Murach (https://piotrmurach.com)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # Loaf
6 |
7 | [][gem]
8 | [][gh_actions_ci]
9 | [][codeclimate]
10 | [][coveralls]
11 | [][inchpages]
12 |
13 | [gem]: http://badge.fury.io/rb/loaf
14 | [gh_actions_ci]: https://github.com/piotrmurach/loaf/actions?query=workflow%3ACI
15 | [codeclimate]: https://codeclimate.com/github/piotrmurach/loaf/maintainability
16 | [coveralls]: https://coveralls.io/github/piotrmurach/loaf
17 | [inchpages]: http://inch-ci.org/github/piotrmurach/loaf
18 |
19 | > **Loaf** manages and displays breadcrumb trails in your Rails application.
20 |
21 | ## Features
22 |
23 | * Use controllers and/or views to specify breadcrumb trails
24 | * Specify urls using Rails conventions
25 | * No markup assumptions for breadcrumbs trails rendering
26 | * Use locales file for breadcrumb names
27 | * Tested with Rails `>= 3.2` and Ruby `>= 2.0.0`
28 |
29 | ## Installation
30 |
31 | Add this line to your application's Gemfile:
32 |
33 | ```ruby
34 | gem "loaf"
35 | ```
36 |
37 | And then execute:
38 |
39 | ```ruby
40 | $ bundle
41 | ```
42 |
43 | Or install it yourself as:
44 |
45 | ```ruby
46 | gem install loaf
47 | ```
48 |
49 | Then run the generator:
50 |
51 | ```ruby
52 | rails generate loaf:install
53 | ```
54 |
55 | ## Contents
56 |
57 | * [1. Usage](#1-usage)
58 | * [2. API](#2-api)
59 | * [2.1 breadcrumb](#21-breadcrumb)
60 | * [2.1.1 controller](#211-controller)
61 | * [2.1.2 view](#212-view)
62 | * [2.1.3 :match](#213-match)
63 | * [2.2 breadcrumb_trail](#22-breadcrumb_trail)
64 | * [3. Configuration](#3-configuration)
65 | * [4. Translation](#4-translation)
66 |
67 | ## 1. Usage
68 |
69 | **Loaf** allows you to add breadcrumbs in controllers and views.
70 |
71 | In order to add breadcrumbs in controller use `breadcrumb` method ([see 2.1](#21-breadcrumb)).
72 |
73 | ```ruby
74 | class Blog::CategoriesController < ApplicationController
75 |
76 | breadcrumb "Article Categories", :blog_categories_path, only: [:show]
77 |
78 | def show
79 | breadcrumb @category.title, blog_category_path(@category)
80 | end
81 | end
82 | ```
83 |
84 | Then in your view render the breadcrumbs trail using [breadcrumb_trail](#22-breadcrumb_trail)
85 |
86 | ## 2. API
87 |
88 | ### 2.1 breadcrumb
89 |
90 | Creation of breadcrumb in Rails is achieved by the `breadcrumb` helper.
91 |
92 | The `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.
93 |
94 | When using path variable `blog_categories_path`:
95 |
96 | ```ruby
97 | breadcrumb "Categories", blog_categories_path
98 | ```
99 |
100 | When using an instance `@category`:
101 |
102 | ```ruby
103 | breadcrumb @category.title, blog_category_path(@category)
104 | ```
105 |
106 | You can also use set of objects:
107 |
108 | ```ruby
109 | breadcrumb @category.title, [:blog, @category]
110 | ```
111 |
112 | You can specify segments of the url:
113 |
114 | ```ruby
115 | breadcrumb @category.title, {controller: "categories", action: "show", id: @category.id}
116 | ```
117 |
118 | #### 2.1.1 controller
119 |
120 | Breadcrumbs 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:
121 |
122 | ```ruby
123 | class ApplicationController < ActionController::Base
124 | breadcrumb "Home", :root_path
125 | end
126 | ```
127 |
128 | Outside 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.
129 |
130 | ```ruby
131 | class ArticlesController < ApplicationController
132 | breadcrumb "All Articles", :articles_path, only: [:new, :create]
133 | end
134 | ```
135 |
136 | Each time you call the `breadcrumb` helper, a new element is added to a breadcrumb trial stack:
137 |
138 | ```ruby
139 | class ArticlesController < ApplicationController
140 | breadcrumb "Home", :root_path
141 | breadcrumb "All Articles", :articles_path
142 |
143 | def show
144 | breadcrumb "Article One", article_path(:one)
145 | breadcrumb "Article Two", article_path(:two)
146 | end
147 | end
148 | ```
149 |
150 | **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:
151 |
152 | ```ruby
153 | class CommentsController < ApplicationController
154 | breadcrumb -> { find_article(params[:post_id]).title }, :articles_path
155 | end
156 | ```
157 |
158 | Also, to dynamically evaluate parameters inside the url argument do:
159 |
160 | ```ruby
161 | class CommentsController < ApplicationController
162 | breadcrumb "All Comments", -> { post_comments_path(params[:post_id]) }
163 | end
164 | ```
165 |
166 | You 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:
167 |
168 | ```ruby
169 | before_action do
170 | ancestors.each do |ancestor|
171 | breadcrumb ancestor.name, [:admin, ancestor]
172 | end
173 | end
174 | ```
175 |
176 | Assume `ancestors` method is defined inside the controller.
177 |
178 | #### 2.1.2 view
179 |
180 | **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:
181 |
182 | ```ruby
183 | <% breadcrumb @category.title, blog_category_path(@category) %>
184 | ```
185 |
186 | #### 2.1.3 :match
187 |
188 | **Loaf** allows you to define matching conditions in order to make a breadcrumb current with the `:match` option.
189 |
190 | The `:match` key accepts the following values:
191 |
192 | * `:inclusive` - the default value, which matches nested paths
193 | * `:exact` - matches only the exact same path
194 | * `:exclusive` - matches only direct path and its query parameters if present
195 | * `/regex/` - matches based on regular expression
196 | * `{foo: bar}` - match based on query parameters
197 |
198 | For example, to force a breadcrumb to be the current regardless do:
199 |
200 | ```ruby
201 | breadcrumb "New Post", new_post_path, match: :exact
202 | ```
203 |
204 | To make a breadcrumb current based on the query parameters do:
205 |
206 | ```ruby
207 | breadcrumb "Posts", posts_path(order: :desc), match: {order: :desc}
208 | ```
209 |
210 | ### 2.2 breadcrumb_trail
211 |
212 | In order to display breadcrumbs use the `breadcrumb_trail` view helper. It accepts optional argument of configuration options and can be used in two ways.
213 |
214 | One way, given a block it will yield all the breadcrumbs in order of definition:
215 |
216 | ```ruby
217 | breadcrumb_trail do |crumb|
218 | ...
219 | end
220 | ```
221 |
222 | The yielded parameter is an instance of `Loaf::Crumb` object with the following methods:
223 |
224 | ```ruby
225 | crumb.name # => the name as string
226 | crumb.path # => the path as string
227 | crumb.url # => alias for path
228 | crumb.current? # => true or false
229 | ```
230 |
231 | For example, you can add the following semantic markup to show breadcrumbs using the `breadcrumb_trail` helper like so:
232 |
233 | ```erb
234 |
235 |
236 | <% breadcrumb_trail do |crumb| %>
237 | ">
238 | <%= link_to crumb.name, crumb.url, (crumb.current? ? {"aria-current" => "page"} : {}) %>
239 | <% unless crumb.current? %>:: <% end %>
240 |
241 | <% end %>
242 |
243 |
244 | ```
245 |
246 | For Bootstrap 4:
247 |
248 | ```erb
249 | <% #erb %>
250 |
251 |
252 | <% breadcrumb_trail do |crumb| %>
253 | ">
254 | <%= link_to_unless crumb.current?, crumb.name, crumb.url, (crumb.current? ? {"aria-current" => "page"} : {}) %>
255 |
256 | <% end %>
257 |
258 |
259 | ```
260 |
261 | And, if you are using `HAML` do:
262 |
263 | ```haml
264 | - # haml
265 | %ol.breadcrumb
266 | - breadcrumb_trail do |crumb|
267 | %li.breadcrumb-item{class: crumb.current? ? "active" : "" }
268 | = link_to_unless crumb.current?, crumb.name, crumb.url, (crumb.current? ? {"aria-current" => "page"} : {})
269 | ```
270 |
271 | Usually best practice is to put such snippet inside its own `_breadcrumbs.html.erb` partial.
272 |
273 | The 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.
274 |
275 | For example, you can define a `breadcrumbs_to_title` method in `ApplicationHelper` like so:
276 |
277 | ```ruby
278 | module ApplicationHelper
279 | def breadcrumbs_to_title
280 | breadcrumb_trail.map(&:name).join(">")
281 | end
282 | end
283 | ```
284 |
285 | Use whichever of the two ways is more convenient given your application structure and needs.
286 |
287 | ## 3. Configuration
288 |
289 | There is a small set of custom opinionated defaults. The following options are valid parameters:
290 |
291 | ```ruby
292 | :match # set match type, default :inclusive (see [:match](#213-match) for more details)
293 | ```
294 |
295 | You can override them in your views by passing them to the view `breadcrumb` helper
296 |
297 | ```erb
298 | <% breadcrumb_trail(match: :exclusive) do |name, url, styles| %>
299 | ..
300 | <% end %>
301 | ```
302 |
303 | or by configuring an option in `config/initializers/loaf.rb`:
304 |
305 | ```ruby
306 | Loaf.configure do |config|
307 | config.match = :exclusive
308 | end
309 | ```
310 |
311 | ## 4. Translation
312 |
313 | You 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.
314 |
315 | ```ruby
316 | en:
317 | loaf:
318 | breadcrumbs:
319 | name: 'my-breadcrumb-name'
320 | ```
321 |
322 | Therefore, in your controller/view you would do:
323 |
324 | ```ruby
325 | class Blog::CategoriesController < ApplicationController
326 | breadcrumb 'blog.categories', :blog_categories_path
327 | end
328 | ```
329 |
330 | And corresponding entry in locale would be:
331 |
332 | ```ruby
333 | en:
334 | loaf:
335 | breadcrumbs:
336 | blog:
337 | categories: 'Article Categories'
338 | ```
339 |
340 | ## Contributing
341 |
342 | Questions 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`.
343 |
344 | ## License
345 |
346 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
347 |
348 | ## Code of Conduct
349 |
350 | Everyone 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).
351 |
352 | ## Copyright
353 |
354 | Copyright (c) 2011 Piotr Murach. See LICENSE.txt for further details.
355 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require 'rubygems'
4 | require 'bundler/setup'
5 | require 'bundler/gem_tasks'
6 |
7 | desc "Default: run loaf unit & integration tests."
8 | task default: :spec
9 |
10 | FileList['tasks/**/*.rake'].each(&method(:import))
11 |
12 | desc 'Run all specs'
13 | task ci: %w[ spec ]
14 |
--------------------------------------------------------------------------------
/assets/loaf_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/piotrmurach/loaf/27b508c813f0dd32ce15c8b01f5a94550ee1ebc0/assets/loaf_logo.png
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "bundler/setup"
4 | require "loaf"
5 |
6 | # You can add fixtures and/or initialization code here to make experimenting
7 | # with your gem easier. You can also use a different console, if you like.
8 |
9 | # (If you use this, don't forget to add pry to your Gemfile!)
10 | # require "pry"
11 | # Pry.start
12 |
13 | require "irb"
14 | IRB.start(__FILE__)
15 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 | IFS=$'\n\t'
5 | set -vx
6 |
7 | # Install gems required by Appraisal
8 | bundle install
9 | bundle exec appraisal install
10 |
--------------------------------------------------------------------------------
/config/locales/loaf.en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | loaf:
3 | errors:
4 | invalid_options: "Invalid option :%{invalid}. Valid options are: %{valid}, make sure these are the ones you are using."
5 | breadcrumbs:
6 | home: 'Home'
7 |
--------------------------------------------------------------------------------
/gemfiles/rails3.2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "ammeter", "~> 1.1.4"
6 | gem "appraisal", "~> 2.2.0"
7 | gem "yard", "~> 0.9.24"
8 | gem "capybara", "~> 2.18.0"
9 | gem "rspec-rails", "~> 3.9.0"
10 | gem "public_suffix", "~> 2.0.5"
11 | gem "railties", "~> 3.2.22.5"
12 | gem "tzinfo", "~> 0.3"
13 | gem "test-unit", "~> 3.0"
14 |
15 | group :metrics do
16 | gem "coveralls", "0.8.23"
17 | gem "simplecov", "~> 0.16.1"
18 | gem "yardstick", "~> 0.9.9"
19 | end
20 |
21 | gemspec path: "../"
22 |
--------------------------------------------------------------------------------
/gemfiles/rails4.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "ammeter", "~> 1.1.4"
6 | gem "appraisal", "~> 2.2.0"
7 | gem "yard", "~> 0.9.24"
8 | gem "capybara", "~> 2.18.0"
9 | gem "rspec-rails", "~> 3.9.0"
10 | gem "public_suffix", "~> 2.0.5"
11 | gem "railties", "~> 4.0.13"
12 | gem "test-unit", "~> 3.0"
13 |
14 | group :metrics do
15 | gem "coveralls", "0.8.23"
16 | gem "simplecov", "~> 0.16.1"
17 | gem "yardstick", "~> 0.9.9"
18 | end
19 |
20 | gemspec path: "../"
21 |
--------------------------------------------------------------------------------
/gemfiles/rails4.1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "ammeter", "~> 1.1.4"
6 | gem "appraisal", "~> 2.2.0"
7 | gem "yard", "~> 0.9.24"
8 | gem "capybara", "~> 2.18.0"
9 | gem "rspec-rails", "~> 3.9.0"
10 | gem "public_suffix", "~> 2.0.5"
11 | gem "railties", "~> 4.1.16"
12 |
13 | group :metrics do
14 | gem "coveralls", "0.8.23"
15 | gem "simplecov", "~> 0.16.1"
16 | gem "yardstick", "~> 0.9.9"
17 | end
18 |
19 | gemspec path: "../"
20 |
--------------------------------------------------------------------------------
/gemfiles/rails4.2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "ammeter", "~> 1.1.4"
6 | gem "appraisal", "~> 2.2.0"
7 | gem "yard", "~> 0.9.24"
8 | gem "capybara", "~> 2.18.0"
9 | gem "rspec-rails", "~> 3.9.0"
10 | gem "public_suffix", "~> 2.0.5"
11 | gem "railties", "~> 4.2.11"
12 |
13 | group :metrics do
14 | gem "coveralls", "0.8.23"
15 | gem "simplecov", "~> 0.16.1"
16 | gem "yardstick", "~> 0.9.9"
17 | end
18 |
19 | gemspec path: "../"
20 |
--------------------------------------------------------------------------------
/gemfiles/rails5.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "ammeter", "~> 1.1.4"
6 | gem "appraisal", "~> 2.2.0"
7 | gem "yard", "~> 0.9.24"
8 | gem "capybara", "~> 2.18.0"
9 | gem "rspec-rails", "~> 3.9.0"
10 | gem "public_suffix", "~> 2.0.5"
11 | gem "railties", "~> 5.0.7"
12 |
13 | group :metrics do
14 | gem "coveralls", "0.8.23"
15 | gem "simplecov", "~> 0.16.1"
16 | gem "yardstick", "~> 0.9.9"
17 | end
18 |
19 | gemspec path: "../"
20 |
--------------------------------------------------------------------------------
/gemfiles/rails5.1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "ammeter", "~> 1.1.4"
6 | gem "appraisal", "~> 2.2.0"
7 | gem "yard", "~> 0.9.24"
8 | gem "capybara", "~> 2.18.0"
9 | gem "rspec-rails", "~> 3.9.0"
10 | gem "public_suffix", "~> 2.0.5"
11 | gem "railties", "~> 5.1.7"
12 |
13 | group :metrics do
14 | gem "coveralls", "0.8.23"
15 | gem "simplecov", "~> 0.16.1"
16 | gem "yardstick", "~> 0.9.9"
17 | end
18 |
19 | gemspec path: "../"
20 |
--------------------------------------------------------------------------------
/gemfiles/rails5.2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "ammeter", "~> 1.1.4"
6 | gem "appraisal", "~> 2.2.0"
7 | gem "yard", "~> 0.9.24"
8 | gem "capybara", "~> 2.18.0"
9 | gem "rspec-rails", "~> 3.9.0"
10 | gem "public_suffix", "~> 2.0.5"
11 | gem "railties", "~> 5.2.4"
12 |
13 | group :metrics do
14 | gem "coveralls", "0.8.23"
15 | gem "simplecov", "~> 0.16.1"
16 | gem "yardstick", "~> 0.9.9"
17 | end
18 |
19 | gemspec path: "../"
20 |
--------------------------------------------------------------------------------
/gemfiles/rails6.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "ammeter", "~> 1.1.4"
6 | gem "appraisal", "~> 2.2.0"
7 | gem "yard", "~> 0.9.24"
8 | gem "capybara", "~> 2.18.0"
9 | gem "rspec-rails", "~> 3.9.0"
10 | gem "public_suffix", "~> 2.0.5"
11 | gem "railties", "~> 6.0.3"
12 |
13 | group :metrics do
14 | gem "coveralls", "0.8.23"
15 | gem "simplecov", "~> 0.16.1"
16 | gem "yardstick", "~> 0.9.9"
17 | end
18 |
19 | gemspec path: "../"
20 |
--------------------------------------------------------------------------------
/gemfiles/rails6.1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "ammeter", "~> 1.1.4"
6 | gem "appraisal", "~> 2.2.0"
7 | gem "yard", "~> 0.9.24"
8 | gem "capybara", "~> 2.18.0"
9 | gem "rspec-rails", "~> 3.9.0"
10 | gem "public_suffix", "~> 2.0.5"
11 | gem "railties", "~> 6.1.0"
12 |
13 | group :metrics do
14 | gem "coveralls", "0.8.23"
15 | gem "simplecov", "~> 0.16.1"
16 | gem "yardstick", "~> 0.9.9"
17 | end
18 |
19 | gemspec path: "../"
20 |
--------------------------------------------------------------------------------
/lib/generators/loaf/install_generator.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require 'rails/generators'
4 |
5 | module Loaf
6 | module Generators
7 | class InstallGenerator < Rails::Generators::Base
8 | source_root File.expand_path("../../../..", __FILE__)
9 |
10 | desc 'Copy locale file to your application'
11 |
12 | def copy_locale
13 | copy_file "#{self.class.source_root}/config/locales/loaf.en.yml", "config/locales/loaf.en.yml"
14 | end
15 | end # InstallGenerator
16 | end # Generators
17 | end # Loaf
18 |
--------------------------------------------------------------------------------
/lib/loaf.rb:
--------------------------------------------------------------------------------
1 | require_relative 'loaf/configuration'
2 | require_relative 'loaf/railtie'
3 | require_relative 'loaf/version'
4 |
5 | module Loaf
6 | # Set global configuration
7 | #
8 | # @api public
9 | def self.configuration=(config)
10 | @configuration = config
11 | end
12 |
13 | # Get global configuration
14 | #
15 | # @api public
16 | def self.configuration
17 | @configuration ||= Configuration.new
18 | end
19 |
20 | # Sets the Loaf configuration options. Best used by passing a block.
21 | #
22 | # Loaf.configure do |config|
23 | # config.capitalize = true
24 | # end
25 | def self.configure
26 | yield configuration
27 | end
28 | end # Loaf
29 |
--------------------------------------------------------------------------------
/lib/loaf/breadcrumb.rb:
--------------------------------------------------------------------------------
1 | module Loaf
2 | # A container for breadcrumb values
3 | # @api public
4 | class Breadcrumb
5 | attr_reader :name
6 |
7 | attr_reader :path
8 | alias url path
9 |
10 | def self.[](*args)
11 | new(*args)
12 | end
13 |
14 | def initialize(name, path, current)
15 | @name = name
16 | @path = path
17 | @current = current
18 | freeze
19 | end
20 |
21 | def current?
22 | @current
23 | end
24 |
25 | def to_ary
26 | [@name, @path, @current]
27 | end
28 | alias to_a to_ary
29 | end # Breadcrumb
30 | end # Loaf
31 |
--------------------------------------------------------------------------------
/lib/loaf/configuration.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Loaf
4 | class Configuration
5 | VALID_ATTRIBUTES = [
6 | :locales_path,
7 | :match
8 | ].freeze
9 |
10 | attr_accessor(*VALID_ATTRIBUTES)
11 |
12 | DEFAULT_LOCALES_PATH = '/'
13 |
14 | DEFAULT_MATCH = :inclusive
15 |
16 | # Setup this configuration
17 | #
18 | # @api public
19 | def initialize(attributes = {})
20 | VALID_ATTRIBUTES.each do |attr|
21 | default = self.class.const_get("DEFAULT_#{attr.to_s.upcase}")
22 | attr_value = attributes.fetch(attr) { default }
23 | send("#{attr}=", attr_value)
24 | end
25 | end
26 |
27 | # Convert all properties into hash
28 | #
29 | # @return [Hash]
30 | #
31 | # @api public
32 | def to_hash
33 | VALID_ATTRIBUTES.reduce({}) { |acc, k| acc[k] = send(k); acc }
34 | end
35 | end # Configuration
36 | end # Loaf
37 |
--------------------------------------------------------------------------------
/lib/loaf/controller_extensions.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'crumb'
4 |
5 | module Loaf
6 | module ControllerExtensions
7 | # Module injection
8 | #
9 | # @api private
10 | def self.included(base)
11 | base.extend ClassMethods
12 | base.send :include, InstanceMethods
13 | base.send :helper_method, :_breadcrumbs
14 | end
15 |
16 | module ClassMethods
17 | # Add breacrumb to the trail in controller as class method
18 | #
19 | # @param [String]
20 | #
21 | # @api public
22 | def breadcrumb(name, url, options = {})
23 | normalizer = method(:_normalize_name)
24 | send(_filter_name, options) do |instance|
25 | normalized_name = normalizer.call(name, instance)
26 | normalized_url = normalizer.call(url, instance)
27 | instance.send(:breadcrumb, normalized_name, normalized_url, options)
28 | end
29 | end
30 | alias add_breadcrumb breadcrumb
31 |
32 | private
33 |
34 | # Choose available filter name
35 | #
36 | # @api private
37 | def _filter_name
38 | respond_to?(:before_action) ? :before_action : :before_filter
39 | end
40 |
41 | # Convert breadcrumb name to string
42 | #
43 | # @return [String]
44 | #
45 | # @api private
46 | def _normalize_name(name, instance)
47 | case name
48 | when NilClass
49 | when Proc
50 | if name.arity == 1
51 | instance.instance_exec(instance, &name)
52 | else
53 | instance.instance_exec(&name)
54 | end
55 | else
56 | name
57 | end
58 | end
59 | end # ClassMethods
60 |
61 | module InstanceMethods
62 | # Add breadcrumb in controller as instance method
63 | #
64 | # @param [String] name
65 | #
66 | # @param [Object] url
67 | #
68 | # @api public
69 | def breadcrumb(name, url, options = {})
70 | _breadcrumbs << Loaf::Crumb.new(name, url, options)
71 | end
72 | alias add_breadcrumb breadcrumb
73 |
74 | # Collection of breadcrumbs
75 | #
76 | # @api private
77 | def _breadcrumbs
78 | @_breadcrumbs ||= []
79 | end
80 |
81 | # Remove all current breadcrumbs
82 | #
83 | # @api public
84 | def clear_breadcrumbs
85 | _breadcrumbs.clear
86 | end
87 | end # InstanceMethods
88 | end # ControllerExtensions
89 | end # Loaf
90 |
--------------------------------------------------------------------------------
/lib/loaf/crumb.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Loaf
4 | # Basic crumb container for internal use
5 | # @api private
6 | class Crumb
7 | attr_reader :name
8 |
9 | attr_reader :url
10 |
11 | attr_reader :match
12 |
13 | def initialize(name, url, options = {})
14 | @name = name || raise_name_error
15 | @url = url || raise_url_error
16 | @match = options.fetch(:match, Loaf.configuration.match)
17 | freeze
18 | end
19 |
20 | def raise_name_error
21 | raise ArgumentError, 'breadcrumb first argument, `name`, cannot be nil'
22 | end
23 |
24 | def raise_url_error
25 | raise ArgumentError, 'breadcrumb second argument, `url`, cannot be nil'
26 | end
27 | end # Crumb
28 | end # Loaf
29 |
--------------------------------------------------------------------------------
/lib/loaf/errors.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Loaf #:nodoc:
4 | # Default Loaf error for all custom errors.
5 | #
6 | class LoafError < StandardError
7 | BASE_KEY = "loaf.errors"
8 |
9 | def error_message(key, attributes)
10 | translate(key, attributes)
11 | end
12 |
13 | def translate(key, options)
14 | ::I18n.translate("#{BASE_KEY}.#{key}", **{ :locale => :en }.merge(options))
15 | end
16 | end
17 |
18 | # Raised when invalid options are passed to breadcrumbs view renderer.
19 | # InvalidOptions.new :name, :crumber, [:crumb]
20 | #
21 | class InvalidOptions < LoafError
22 | def initialize(invalid, valid)
23 | super(
24 | error_message("invalid_options",
25 | { :invalid => invalid, :valid => valid.join(', ') }
26 | )
27 | )
28 | end
29 | end
30 | end # Loaf
31 |
--------------------------------------------------------------------------------
/lib/loaf/options_validator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'errors'
4 |
5 | module Loaf
6 | # A mixin to validate configuration options
7 | module OptionsValidator
8 | # Check if options are valid or not
9 | #
10 | # @param [Hash] options
11 | #
12 | # @return [Boolean]
13 | #
14 | # @api public
15 | def valid?(options)
16 | valid_options = Loaf::Configuration::VALID_ATTRIBUTES
17 | options.each_key do |key|
18 | unless valid_options.include?(key)
19 | fail Loaf::InvalidOptions.new(key, valid_options)
20 | end
21 | end
22 | true
23 | end
24 | end # OptionsValidator
25 | end # Loaf
26 |
--------------------------------------------------------------------------------
/lib/loaf/railtie.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require 'action_controller'
4 | require 'action_view'
5 |
6 | require_relative 'controller_extensions'
7 | require_relative 'view_extensions'
8 |
9 | module Loaf
10 | class RailtieHelpers
11 | class << self
12 | def insert_view
13 | ActionController::Base.helper Loaf::ViewExtensions
14 | end
15 |
16 | def insert_controller
17 | ActionController::Base.send :include, Loaf::ControllerExtensions
18 | end
19 | end
20 | end # RailtieHelpers
21 |
22 | if defined?(Rails::Railtie)
23 | class Railtie < Rails::Railtie
24 | initializer "loaf.extend_action_controller_base" do |app|
25 | ActiveSupport.on_load :action_controller do
26 | Loaf::RailtieHelpers.insert_controller
27 | Loaf::RailtieHelpers.insert_view
28 | end
29 | end
30 | end
31 | else
32 | Loaf::RailtieHelpers.insert_controller
33 | Loaf::RailtieHelpers.insert_view
34 | end
35 | end # Loaf
36 |
--------------------------------------------------------------------------------
/lib/loaf/translation.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Loaf
4 | module Translation
5 | # Returns translation lookup
6 | #
7 | # @return [String]
8 | #
9 | # @api private
10 | def translation_scope
11 | 'loaf.breadcrumbs'
12 | end
13 | module_function :translation_scope
14 |
15 | # Translate breadcrumb title
16 | #
17 | # @param [String] :title
18 | # @param [Hash] options
19 | # @option options [String] :scope
20 | # The translation scope
21 | # @option options [String] :default
22 | # The default translation
23 | #
24 | # @return [String]
25 | #
26 | # @api public
27 | def find_title(title, options = {})
28 | return title if title.nil? || title.empty?
29 |
30 | options[:scope] ||= translation_scope
31 | options[:default] = Array(options[:default])
32 | options[:default] << title if options[:default].empty?
33 | I18n.t(title.to_s, **options)
34 | end
35 | module_function :find_title
36 | end # Translation
37 | end # Loaf
38 |
--------------------------------------------------------------------------------
/lib/loaf/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Loaf
4 | VERSION = "0.10.0"
5 | end # Loaf
6 |
--------------------------------------------------------------------------------
/lib/loaf/view_extensions.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'breadcrumb'
4 | require_relative 'crumb'
5 | require_relative 'options_validator'
6 | require_relative 'translation'
7 |
8 | module Loaf
9 | # A mixin to define view extensions
10 | module ViewExtensions
11 | include Loaf::OptionsValidator
12 |
13 | def initialize(*)
14 | @_breadcrumbs ||= []
15 | super
16 | end
17 |
18 | # Checks to see if any breadcrumbs have been added
19 | #
20 | # @return [Boolean]
21 | #
22 | # @api public
23 | def breadcrumbs?
24 | _breadcrumbs.present?
25 | end
26 |
27 | # Adds breadcrumbs inside view.
28 | #
29 | # @param [String] name
30 | # the breadcrumb name
31 | # @param [Object] url
32 | # the breadcrumb url
33 | # @param [Hash] options
34 | # the breadcrumb options
35 | #
36 | # @api public
37 | def breadcrumb(name, url, options = {})
38 | _breadcrumbs << Loaf::Crumb.new(name, url, options)
39 | end
40 | alias add_breadcrumb breadcrumb
41 |
42 | # Renders breadcrumbs inside view.
43 | #
44 | # @param [Hash] options
45 | #
46 | # @api public
47 | def breadcrumb_trail(options = {})
48 | return enum_for(:breadcrumb_trail, options) unless block_given?
49 |
50 | valid?(options)
51 |
52 | _breadcrumbs.each do |crumb|
53 | name = title_for(crumb.name)
54 | path = url_for(_expand_url(crumb.url))
55 | current = current_crumb?(path, options.fetch(:match) { crumb.match })
56 |
57 | yield(Loaf::Breadcrumb[name, path, current])
58 | end
59 | end
60 |
61 | # Check if breadcrumb is current based on the pattern
62 | #
63 | # @param [String] path
64 | # @param [Object] pattern
65 | # the pattern to match on
66 | #
67 | # @api public
68 | def current_crumb?(path, pattern = :inclusive)
69 | return false unless request.get? || request.head?
70 |
71 | origin_path = URI::DEFAULT_PARSER.unescape(path).force_encoding(Encoding::BINARY)
72 |
73 | request_uri = request.fullpath
74 | request_uri = URI::DEFAULT_PARSER.unescape(request_uri)
75 | request_uri.force_encoding(Encoding::BINARY)
76 |
77 | # strip away trailing slash
78 | if origin_path.start_with?('/') && origin_path != '/'
79 | origin_path.chomp!('/')
80 | request_uri.chomp!('/')
81 | end
82 |
83 | if %r{^\w+://} =~ origin_path
84 | origin_path.chomp!('/')
85 | request_uri.insert(0, "#{request.protocol}#{request.host_with_port}")
86 | end
87 |
88 | case pattern
89 | when :inclusive
90 | !request_uri.match(/^#{Regexp.escape(origin_path)}(\/.*|\?.*)?$/).nil?
91 | when :exclusive
92 | !request_uri.match(/^#{Regexp.escape(origin_path)}\/?(\?.*)?$/).nil?
93 | when :exact
94 | request_uri == origin_path
95 | when :force
96 | true
97 | when Regexp
98 | !request_uri.match(pattern).nil?
99 | when Hash
100 | query_params = URI.encode_www_form(pattern)
101 | !request_uri.match(/^#{Regexp.escape(origin_path)}\/?(\?.*)?.*?#{query_params}.*$/).nil?
102 | else
103 | raise ArgumentError, "Unknown `:#{pattern}` match option!"
104 | end
105 | end
106 |
107 | private
108 |
109 | # Find title translation for a crumb name
110 | #
111 | # @return [String]
112 | #
113 | # @api private
114 | def title_for(name)
115 | Translation.find_title(name)
116 | end
117 |
118 | # Expand url in the current context of the view
119 | #
120 | # @api private
121 | def _expand_url(url)
122 | case url
123 | when String, Symbol
124 | respond_to?(url) ? send(url) : url
125 | when Proc
126 | url.call(self)
127 | else
128 | url
129 | end
130 | end
131 | end # ViewExtensions
132 | end # Loaf
133 |
--------------------------------------------------------------------------------
/loaf.gemspec:
--------------------------------------------------------------------------------
1 | require_relative "lib/loaf/version"
2 |
3 | Gem::Specification.new do |spec|
4 | spec.name = "loaf"
5 | spec.version = Loaf::VERSION.dup
6 | spec.authors = ["Piotr Murach"]
7 | spec.email = ["piotr@piotrmurach.com"]
8 | spec.summary = %q{Loaf manages and displays breadcrumb trails in your Rails application.}
9 | spec.description = %q{Loaf manages and displays breadcrumb trails in your Rails app. It aims to handle breadcrumb data through easy dsl and expose it through view helpers without any assumptions about markup.}
10 | spec.homepage = "https://github.com/piotrmurach/loaf"
11 | spec.license = "MIT"
12 | if spec.respond_to?(:metadata=)
13 | spec.metadata = {
14 | "allowed_push_host" => "https://rubygems.org",
15 | "bug_tracker_uri" => "https://github.com/piotrmurach/loaf/issues",
16 | "changelog_uri" => "https://github.com/piotrmurach/loaf/blob/master/CHANGELOG.md",
17 | "documentation_uri" => "https://www.rubydoc.info/gems/loaf",
18 | "homepage_uri" => spec.homepage,
19 | "source_code_uri" => "https://github.com/piotrmurach/loaf",
20 | }
21 | end
22 | spec.files = Dir["{lib,config}/**/*"]
23 | spec.extra_rdoc_files = Dir["README.md", "CHANGELOG.md", "LICENSE.txt"]
24 | spec.bindir = "exe"
25 | spec.require_paths = ["lib"]
26 | spec.required_ruby_version = ">= 1.9.3"
27 |
28 | spec.add_dependency "railties", ">= 3.2"
29 |
30 | spec.add_development_dependency "rake"
31 | end
32 |
--------------------------------------------------------------------------------
/spec/integration/breadcrumb_trail_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | RSpec.describe "breadcrumbs trail" do
4 | before do
5 | include ActionView::TestCase::Behavior
6 | end
7 |
8 | it "shows root breadcrumb" do
9 | visit root_path
10 |
11 | page.within '#breadcrumbs .selected' do
12 | expect(page.html).to include('Home ')
13 | end
14 | end
15 |
16 | it "inherits controller breadcrumb and adds index action breadcrumb" do
17 | visit posts_path
18 |
19 | page.within '#breadcrumbs' do
20 | expect(page.html).to include('Home ')
21 | end
22 | page.within '#breadcrumbs .selected' do
23 | expect(page.html).to include('All Posts ')
24 | end
25 | end
26 |
27 | it 'filters out controller breadcrumb and adds new action breadcrumb' do
28 | visit new_post_path
29 |
30 | page.within '#breadcrumbs' do
31 | expect(page).to_not have_content('Home')
32 | expect(page).to have_content('New Post')
33 | end
34 | end
35 |
36 | it "adds breadcrumb in view with path variable" do
37 | visit post_path(1)
38 |
39 | page.within '#breadcrumbs .selected' do
40 | expect(page.html).to include('Show Post in view ')
41 | end
42 | end
43 |
44 | it 'is current when forced' do
45 | visit new_post_path
46 |
47 | expect(page.current_path).to eq(new_post_path)
48 | page.within '#breadcrumbs' do
49 | expect(page).to have_selector('li.selected', count: 2)
50 | expect(page.html).to include('All ')
51 | expect(page.html).to include('New Post ')
52 | end
53 | end
54 |
55 | it "allows for procs in name and url" do
56 | visit post_comments_path(1)
57 |
58 | page.within '#breadcrumbs .selected' do
59 | expect(page.html).to include('Post comments ')
60 | end
61 | end
62 |
63 | it "allows for procs in name and url without supplying the controller" do
64 | visit post_comments_path(1)
65 |
66 | page.within "#breadcrumbs .selected" do
67 | expect(page.html).to include(
68 | ''\
69 | "Post comments No Controller "
70 | )
71 | end
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/spec/integration/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | RSpec.describe 'setting configuration options' do
4 | it "contains 'selected' inside the breadcrumb markup" do
5 | visit posts_path
6 | page.within '#breadcrumbs' do
7 | expect(page).to have_selector('.selected')
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/spec/rails_app/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | # Add your own tasks in files placed in lib/tasks ending in .rake,
3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
4 |
5 | require File.expand_path('../config/application', __FILE__)
6 | require 'rake'
7 |
8 | RailsApp::Application.load_tasks
9 |
--------------------------------------------------------------------------------
/spec/rails_app/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | /* manifest */
2 |
--------------------------------------------------------------------------------
/spec/rails_app/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery
3 |
4 | breadcrumb 'Home', :root_path, only: :index
5 | end
6 |
--------------------------------------------------------------------------------
/spec/rails_app/app/controllers/comments_controller.rb:
--------------------------------------------------------------------------------
1 | class Article < Struct.new(:id, :title); end
2 |
3 | class CommentsController < ApplicationController
4 |
5 | breadcrumb lambda { |c| c.find_article(c.params[:post_id]).title },
6 | lambda { |c| c.post_comments_path(c.params[:post_id]) }
7 |
8 | breadcrumb -> { find_article(params[:post_id]).title + " No Controller" },
9 | -> { post_comments_path(params[:post_id], no_controller: true) }
10 |
11 | def index
12 | end
13 |
14 | def show
15 | end
16 |
17 | def find_article(id)
18 | ::Article.new(id, 'Post comments')
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/spec/rails_app/app/controllers/home_controller.rb:
--------------------------------------------------------------------------------
1 | class HomeController < ApplicationController
2 | def index
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/spec/rails_app/app/controllers/posts_controller.rb:
--------------------------------------------------------------------------------
1 | class Post < Struct.new(:id); end
2 |
3 | class PostsController < ApplicationController
4 | def index
5 | breadcrumb 'all_posts', posts_path
6 | end
7 |
8 | def show
9 | @post = ::Post.new(1)
10 | end
11 |
12 | def new
13 | breadcrumb 'All', :posts_path, match: :force
14 | breadcrumb 'New Post', new_post_path
15 | end
16 |
17 | def create
18 | render action: :new
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/spec/rails_app/app/views/comments/index.html.erb:
--------------------------------------------------------------------------------
1 | Post comments index
2 |
--------------------------------------------------------------------------------
/spec/rails_app/app/views/home/index.html.erb:
--------------------------------------------------------------------------------
1 | Home page
2 |
--------------------------------------------------------------------------------
/spec/rails_app/app/views/layouts/_breadcrumbs.html.erb:
--------------------------------------------------------------------------------
1 | <% if breadcrumb_trail.any? %>
2 |
3 | <% breadcrumb_trail do |crumb| %>
4 |
5 | <%= link_to crumb.name, crumb.url %>
6 | <% unless crumb.current? %>
7 | ::
8 | <% end %>
9 |
10 | <% end %>
11 |
12 | <% end %>
13 |
--------------------------------------------------------------------------------
/spec/rails_app/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | RailsApp
5 | <%= csrf_meta_tags %>
6 |
7 |
8 |
9 | <%= render 'layouts/breadcrumbs' %>
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/spec/rails_app/app/views/posts/index.html.erb:
--------------------------------------------------------------------------------
1 | Posts:index
2 |
--------------------------------------------------------------------------------
/spec/rails_app/app/views/posts/new.html.erb:
--------------------------------------------------------------------------------
1 | Posts:new
2 | <%= form_tag posts_path, :method => "POST" do %>
3 | <% submit_tag "Create"%>
4 | <% end %>
5 |
--------------------------------------------------------------------------------
/spec/rails_app/app/views/posts/show.html.erb:
--------------------------------------------------------------------------------
1 | <% breadcrumb 'Show Post in view', post_path(@post.id) %>
2 |
--------------------------------------------------------------------------------
/spec/rails_app/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run RailsApp::Application
5 |
--------------------------------------------------------------------------------
/spec/rails_app/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require "action_controller/railtie"
4 | require "action_view/railtie"
5 | require "rails/test_unit/railtie"
6 |
7 | if defined?(Bundler)
8 | # If you precompile assets before deploying to production, use this line
9 | Bundler.require *Rails.groups(:assets => %w(development test))
10 | # If you want your assets lazily compiled in production, use this line
11 | # Bundler.require(:default, :assets, Rails.env)
12 | end
13 |
14 | module RailsApp
15 | class Application < Rails::Application
16 | # Settings in config/environments/* take precedence over those specified here.
17 | # Application configuration should go into files in config/initializers
18 | # -- all .rb files in that directory are automatically loaded.
19 |
20 | # Custom directories with classes and modules you want to be autoloadable.
21 | # config.autoload_paths += %W(#{config.root}/extras)
22 |
23 | # Only load the plugins named here, in the order given (default is alphabetical).
24 | # :all can be used as a placeholder for all plugins not explicitly named.
25 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
26 |
27 | # Activate observers that should always be running.
28 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
29 |
30 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
31 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
32 | # config.time_zone = 'Central Time (US & Canada)'
33 |
34 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
35 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
36 | # config.i18n.default_locale = :de
37 |
38 | # Configure the default encoding used in templates for Ruby 1.9.
39 | config.encoding = "utf-8"
40 |
41 | # Configure sensitive parameters which will be filtered from the log file.
42 | config.filter_parameters += [:password]
43 |
44 | # Enable the asset pipeline
45 | # config.assets.enabled = true
46 |
47 | # Version of your assets, change this if you want to expire all your assets
48 | # config.assets.version = '1.0'
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/spec/rails_app/config/boot.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | gemfile = File.expand_path('../../../../Gemfile', __FILE__)
3 |
4 | if File.exists?(gemfile)
5 | ENV['BUNDLE_GEMFILE'] ||= gemfile
6 | require 'bundler'
7 | Bundler.setup
8 | end
9 |
10 | $:.unshift File.expand_path('../../../../lib', __FILE__)
11 |
--------------------------------------------------------------------------------
/spec/rails_app/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | development:
7 | adapter: sqlite3
8 | database: db/development.sqlite3
9 | pool: 5
10 | timeout: 5000
11 |
12 | # Warning: The database defined as "test" will be erased and
13 | # re-generated from your development database when you run "rake".
14 | # Do not set this db to the same as development or production.
15 | test:
16 | adapter: sqlite3
17 | database: db/test.sqlite3
18 | pool: 5
19 | timeout: 5000
20 |
21 | production:
22 | adapter: sqlite3
23 | database: db/production.sqlite3
24 | pool: 5
25 | timeout: 5000
26 |
--------------------------------------------------------------------------------
/spec/rails_app/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the rails application
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the rails application
5 | RailsApp::Application.initialize!
6 |
--------------------------------------------------------------------------------
/spec/rails_app/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | RailsApp::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Log error messages when you accidentally call methods on nil.
10 | config.whiny_nils = true
11 |
12 | # Show full error reports and disable caching
13 | config.consider_all_requests_local = true
14 | config.action_controller.perform_caching = false
15 |
16 | # Don't care if the mailer can't send
17 | # config.action_mailer.raise_delivery_errors = false
18 |
19 | # Print deprecation notices to the Rails logger
20 | config.active_support.deprecation = :log
21 |
22 | # Only use best-standards-support built into browsers
23 | config.action_dispatch.best_standards_support = :builtin
24 |
25 | # Do not compress assets
26 | # config.assets.compress = false
27 |
28 | # Expands the lines which load the assets
29 | # config.assets.debug = true
30 |
31 | config.eager_load = false
32 | end
33 |
--------------------------------------------------------------------------------
/spec/rails_app/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | RailsApp::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # Code is not reloaded between requests
5 | config.cache_classes = true
6 |
7 | # Full error reports are disabled and caching is turned on
8 | config.consider_all_requests_local = false
9 | config.action_controller.perform_caching = true
10 |
11 | # Disable Rails's static asset server (Apache or nginx will already do this)
12 | config.serve_static_assets = false
13 |
14 | # Compress JavaScripts and CSS
15 | config.assets.compress = true
16 |
17 | # Don't fallback to assets pipeline if a precompiled asset is missed
18 | config.assets.compile = false
19 |
20 | # Generate digests for assets URLs
21 | config.assets.digest = true
22 |
23 | # Defaults to Rails.root.join("public/assets")
24 | # config.assets.manifest = YOUR_PATH
25 |
26 | # Specifies the header that your server uses for sending files
27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
29 |
30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
31 | # config.force_ssl = true
32 |
33 | # See everything in the log (default is :info)
34 | # config.log_level = :debug
35 |
36 | # Use a different logger for distributed setups
37 | # config.logger = SyslogLogger.new
38 |
39 | # Use a different cache store in production
40 | # config.cache_store = :mem_cache_store
41 |
42 | # Enable serving of images, stylesheets, and JavaScripts from an asset server
43 | # config.action_controller.asset_host = "http://assets.example.com"
44 |
45 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
46 | # config.assets.precompile += %w( search.js )
47 |
48 | # Disable delivery errors, bad email addresses will be ignored
49 | # config.action_mailer.raise_delivery_errors = false
50 |
51 | # Enable threaded mode
52 | # config.threadsafe!
53 |
54 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
55 | # the I18n.default_locale when a translation can not be found)
56 | config.i18n.fallbacks = true
57 |
58 | # Send deprecation notices to registered listeners
59 | config.active_support.deprecation = :notify
60 |
61 | config.eager_load = true
62 | end
63 |
--------------------------------------------------------------------------------
/spec/rails_app/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | RailsApp::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Configure static asset server for tests with Cache-Control for performance
11 | config.serve_static_files = true
12 | config.static_cache_control = "public, max-age=3600"
13 |
14 | # Show full error reports and disable caching
15 | config.consider_all_requests_local = true
16 | config.action_controller.perform_caching = false
17 |
18 | # Raise exceptions instead of rendering exception templates
19 | config.action_dispatch.show_exceptions = false
20 |
21 | # Disable request forgery protection in test environment
22 | config.action_controller.allow_forgery_protection = false
23 |
24 | # Tell Action Mailer not to deliver emails to the real world.
25 | # The :test delivery method accumulates sent emails in the
26 | # ActionMailer::Base.deliveries array.
27 | # config.action_mailer.delivery_method = :test
28 |
29 | # Use SQL instead of Active Record's schema dumper when creating the test database.
30 | # This is necessary if your schema can't be completely dumped by the schema dumper,
31 | # like if you have constraints or database-specific column types
32 | # config.active_record.schema_format = :sql
33 |
34 | # Print deprecation notices to the stderr
35 | config.active_support.deprecation = :stderr
36 |
37 | # Allow pass debug_assets=true as a query parameter to load pages with unpackaged assets
38 | # config.assets.allow_debugging = true
39 |
40 | config.eager_load = false
41 | end
42 |
--------------------------------------------------------------------------------
/spec/rails_app/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/rails_app/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format
4 | # (all these examples are active by default):
5 | # ActiveSupport::Inflector.inflections do |inflect|
6 | # inflect.plural /^(ox)$/i, '\1en'
7 | # inflect.singular /^(ox)en/i, '\1'
8 | # inflect.irregular 'person', 'people'
9 | # inflect.uncountable %w( fish sheep )
10 | # end
11 |
--------------------------------------------------------------------------------
/spec/rails_app/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 | # Mime::Type.register_alias "text/html", :iphone
6 |
--------------------------------------------------------------------------------
/spec/rails_app/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 | # Make sure the secret is at least 30 characters and all random,
6 | # no regular words or you'll be exposed to dictionary attacks.
7 | RailsApp::Application.config.secret_token = '2bc2743124978fc50d054e09cc457b9a6de3cb8c1b7000d78a9af674e93245bf292f2f57f56ad782dc6f7da1695206251d7c9541ec3870467275e4c65447db48'
8 |
--------------------------------------------------------------------------------
/spec/rails_app/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | RailsApp::Application.config.session_store :cookie_store, key: '_rails_app_session'
4 |
5 | # Use the database for sessions instead of the cookie-based default,
6 | # which shouldn't be used to store highly confidential information
7 | # (create the session table with "rails generate session_migration")
8 | # RailsApp::Application.config.session_store :active_record_store
9 |
--------------------------------------------------------------------------------
/spec/rails_app/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 | #
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # Disable root element in JSON by default.
12 | ActiveSupport.on_load(:active_record) do
13 | self.include_root_in_json = false
14 | end
15 |
--------------------------------------------------------------------------------
/spec/rails_app/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Sample localization file for English. Add more files in this directory for other locales.
2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 |
4 | en:
5 | hello: "Hello world"
6 |
--------------------------------------------------------------------------------
/spec/rails_app/config/locales/loaf.en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | loaf:
3 | errors:
4 | invalid_options: "Invalid option :%{invalid}. Valid options are: %{valid}, make sure these are the ones you are using."
5 | breadcrumbs:
6 | all_posts: 'All Posts'
7 |
--------------------------------------------------------------------------------
/spec/rails_app/config/routes.rb:
--------------------------------------------------------------------------------
1 | RailsApp::Application.routes.draw do
2 | root :to => 'home#index'
3 |
4 | resources :posts do
5 | resources :comments
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/spec/rails_app/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | secret_key_base: c392df63ec28b42b36b92a5b9e8603fa62d7807024d07801ab162ec7a2a165a87d27a4a8535ce09cd2bf4a82afe45e3080fb8cc56613d608e5b18335594782b3
15 |
16 | test:
17 | secret_key_base: 4b1fba0d12c53b8b21880fda47b603b3dcca117ccf41fcc70091351b88ede5d4c5c91371f407cd056efd66fa5f306c23add65a89d4304e06a4243289ee97be1d
18 |
19 | # Do not keep production secrets in the repository,
20 | # instead read values from the environment.
21 | production:
22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
23 |
--------------------------------------------------------------------------------
/spec/rails_app/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
7 | # Mayor.create(name: 'Emanuel', city: cities.first)
8 |
--------------------------------------------------------------------------------
/spec/rails_app/log/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/piotrmurach/loaf/27b508c813f0dd32ce15c8b01f5a94550ee1ebc0/spec/rails_app/log/.gitkeep
--------------------------------------------------------------------------------
/spec/rails_app/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The page you were looking for doesn't exist.
23 |
You may have mistyped the address or the page may have moved.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/spec/rails_app/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The change you wanted was rejected.
23 |
Maybe you tried to change something you didn't have access to.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/spec/rails_app/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
We're sorry, but something went wrong.
23 |
We've been notified about this issue and we'll take a look at it shortly.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/spec/rails_app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/piotrmurach/loaf/27b508c813f0dd32ce15c8b01f5a94550ee1ebc0/spec/rails_app/public/favicon.ico
--------------------------------------------------------------------------------
/spec/rails_app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-Agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | if ENV["COVERAGE"] == "true"
4 | require "simplecov"
5 | require "coveralls"
6 |
7 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
8 | SimpleCov::Formatter::HTMLFormatter,
9 | Coveralls::SimpleCov::Formatter
10 | ])
11 |
12 | SimpleCov.start do
13 | command_name "spec"
14 | add_filter "spec"
15 | end
16 | end
17 |
18 | # Configure Rails Environment
19 | ENV["RAILS_ENV"] = "test"
20 |
21 | require "rails_app/config/environment.rb"
22 | require "rspec/rails"
23 | require "loaf"
24 |
25 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
26 |
27 | RSpec.configure do |config|
28 | config.expect_with :rspec do |expectations|
29 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
30 | end
31 |
32 | config.mock_with :rspec do |mocks|
33 | mocks.verify_partial_doubles = true
34 | end
35 |
36 | # Limits the available syntax to the non-monkey patched syntax that is recommended.
37 | config.disable_monkey_patching!
38 |
39 | # This setting enables warnings. It's recommended, but in some cases may
40 | # be too noisy due to issues in dependencies.
41 | config.warnings = true
42 |
43 | if config.files_to_run.one?
44 | config.default_formatter = "doc"
45 | end
46 |
47 | config.profile_examples = 2
48 |
49 | config.order = :random
50 |
51 | Kernel.srand config.seed
52 | end
53 |
--------------------------------------------------------------------------------
/spec/support/capybara.rb:
--------------------------------------------------------------------------------
1 | require 'capybara/rails'
2 | require 'capybara/dsl'
3 |
4 | RSpec.configure do |c|
5 | c.include Capybara::DSL, :file_path => /\bspec\/integration\//
6 | end
7 | Capybara.default_driver = :rack_test
8 | Capybara.default_selector = :css
9 |
--------------------------------------------------------------------------------
/spec/support/dummy_controller.rb:
--------------------------------------------------------------------------------
1 | class DummyController < ActionController::Base
2 | def self.before_filter(options, &block)
3 | yield self.new
4 | end
5 | class << self
6 | alias before_action before_filter
7 | end
8 | include Loaf::ControllerExtensions
9 | end
10 |
--------------------------------------------------------------------------------
/spec/support/dummy_view.rb:
--------------------------------------------------------------------------------
1 | require "action_view"
2 |
3 | class DummyView < ActionView::Base
4 | module FakeRequest
5 | class Request
6 | attr_accessor :path, :fullpath, :protocol, :host_with_port
7 | def get?
8 | true
9 | end
10 | end
11 | def request
12 | @request ||= Request.new
13 | end
14 | def params
15 | @params ||= {}
16 | end
17 | end
18 |
19 | include FakeRequest
20 | include ActionView::Helpers::UrlHelper
21 | include Loaf::ViewExtensions
22 |
23 | def initialize
24 | context = ActionView::LookupContext.new([])
25 | assigns = {}
26 | controller = nil
27 | super(context, assigns, controller)
28 | end
29 |
30 | attr_reader :_breadcrumbs
31 |
32 | routes = ActionDispatch::Routing::RouteSet.new
33 | routes.draw do
34 | get "/" => "foo#bar", :as => :home
35 | get "/posts" => "foo#posts"
36 | get "/posts/:title" => "foo#posts"
37 | get "/post/:id" => "foo#post", :as => :post
38 | get "/post/:title" => "foo#title"
39 | get "/post/:id/comments" => "foo#comments"
40 |
41 | namespace :blog do
42 | get "/" => "foo#bar"
43 | end
44 | end
45 |
46 | include routes.url_helpers
47 |
48 | def set_path(path)
49 | request.path = path
50 | request.fullpath = path
51 | request.protocol = "http://"
52 | request.host_with_port = "www.example.com"
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/spec/support/load_routes.rb:
--------------------------------------------------------------------------------
1 | RSpec.configure do |c|
2 | c.include Rails.application.routes.url_helpers,
3 | :file_path => /\bspec\/integration\//
4 | end
5 |
--------------------------------------------------------------------------------
/spec/unit/configuration_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | RSpec.describe Loaf::Configuration do
4 | it "allows to set and read attributes" do
5 | config = Loaf::Configuration.new
6 | config.match = :exact
7 | expect(config.match).to eq(:exact)
8 | end
9 |
10 | it "accepts attributes at initialization" do
11 | options = { locales_path: "/lib", match: :exact }
12 | config = Loaf::Configuration.new(options)
13 |
14 | expect(config.locales_path).to eq("/lib")
15 | expect(config.match).to eq(:exact)
16 | end
17 |
18 | it "exports configuration as hash" do
19 | config = Loaf::Configuration.new
20 | expect(config.to_hash).to eq({
21 | locales_path: "/",
22 | match: :inclusive
23 | })
24 | end
25 |
26 | it "yields configuration" do
27 | conf = double(:conf)
28 | allow(Loaf).to receive(:configuration).and_return(conf)
29 | expect { |b|
30 | Loaf.configure(&b)
31 | }.to yield_with_args(conf)
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/spec/unit/controller_extensions_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | RSpec.describe Loaf::ControllerExtensions do
4 |
5 | context "when classes extend controller_extensions" do
6 | it { expect(DummyController).to respond_to(:add_breadcrumb) }
7 | it { expect(DummyController).to respond_to(:breadcrumb) }
8 | it { expect(DummyController.new).to respond_to(:add_breadcrumb) }
9 | it { expect(DummyController.new).to respond_to(:breadcrumb) }
10 | it { expect(DummyController.new).to respond_to(:clear_breadcrumbs) }
11 | end
12 |
13 | context "class methods" do
14 | it "invokes before_action" do
15 | allow(DummyController).to receive(:before_action)
16 | allow(DummyController).to receive(:respond_to?).and_return(true)
17 | DummyController.breadcrumb("name", "url_path")
18 | expect(DummyController).to have_received(:before_action)
19 | end
20 |
21 | it "delegates breadcrumb registration to controller instance" do
22 | name = "List objects"
23 | url = :object_path
24 | options = {force: true}
25 | instance = double(:controller_instance).as_null_object
26 |
27 | allow(DummyController).to receive(:new).and_return(instance)
28 | DummyController.breadcrumb(name, url, options)
29 | expect(instance).to have_received(:breadcrumb).with(name, url, options)
30 | end
31 | end
32 |
33 | context "instance methods" do
34 | it "instantiates breadcrumbs container" do
35 | name = "List objects"
36 | url = :object_path
37 | instance = DummyController.new
38 |
39 | allow(Loaf::Crumb).to receive(:new)
40 | instance.breadcrumb(name, url)
41 | expect(Loaf::Crumb).to have_received(:new).with(name, url, {})
42 | end
43 |
44 | it "adds breadcrumb to collection" do
45 | name = "List objects"
46 | url = :object_path
47 | instance = DummyController.new
48 |
49 | expect {
50 | instance.breadcrumb(name, url)
51 | }.to change { instance._breadcrumbs.size }.by(1)
52 | end
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/spec/unit/crumb_spec.rb:
--------------------------------------------------------------------------------
1 | RSpec.describe Loaf::Crumb do
2 | it "fails when name is nil" do
3 | expect {
4 | Loaf::Crumb.new(nil, "path")
5 | }.to raise_error(ArgumentError,
6 | /breadcrumb first argument, `name`, cannot be nil/)
7 | end
8 |
9 | it "fails when url is nil" do
10 | expect {
11 | Loaf::Crumb.new("name", nil)
12 | }.to raise_error(ArgumentError,
13 | /breadcrumb second argument, `url`, cannot be nil/)
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/unit/generators/install_generator_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require 'fileutils'
4 | require 'generators/loaf/install_generator'
5 |
6 | RSpec.describe Loaf::Generators::InstallGenerator, type: :generator do
7 | destination File.expand_path("../../../tmp", __FILE__)
8 |
9 | before { prepare_destination }
10 |
11 | it "copies loaf locales to the host application" do
12 | run_generator
13 | locale = file("config/locales/loaf.en.yml")
14 | expect(locale).to exist
15 | FileUtils.rm_rf(File.expand_path("../../../tmp", __FILE__))
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/unit/options_validator_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | RSpec.describe Loaf::OptionsValidator, ".valid?" do
4 | let(:klass) { Class.extend Loaf::OptionsValidator }
5 |
6 | it "validates succesfully known option" do
7 | expect(klass.valid?(match: :exact)).to eq(true)
8 | end
9 |
10 | it "validates unknown option with an error" do
11 | expect {
12 | klass.valid?(invalid_param: true)
13 | }.to raise_error(Loaf::InvalidOptions)
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/spec/unit/translation_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | RSpec.describe Loaf::Translation do
4 |
5 | before { I18n.backend = I18n::Backend::Simple.new }
6 |
7 | after { I18n.backend.reload! }
8 |
9 | it "doesn't translate empty title" do
10 | expect(described_class.find_title("")).to eql("")
11 | end
12 |
13 | it "skips translation if doesn't find a matching scope" do
14 | expect(described_class.find_title("unknown")).to eql("unknown")
15 | end
16 |
17 | it "translates breadcrumb title" do
18 | I18n.backend.store_translations "en", loaf: { breadcrumbs: { home: "Home"}}
19 | expect(described_class.find_title("home")).to eql("Home")
20 | end
21 |
22 | it "does not translates breadcrumb name with missing scope" do
23 | I18n.backend.store_translations "en", breadcrumbs: {home: "Home"}
24 | expect(described_class.find_title("home")).to eql("home")
25 | end
26 |
27 | it "translates breadcrumb name using default option" do
28 | expect(described_class.find_title("home", default: "breadcrumb default name")).to eql("breadcrumb default name")
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/spec/unit/view_extensions/breadcrumb_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | RSpec.describe Loaf::ViewExtensions, "#breadcrumb" do
4 | it "exposes add_breadcrumb alias" do
5 | expect(DummyView.new).to respond_to(:add_breadcrumb)
6 | end
7 |
8 | it "creates crumb instance" do
9 | instance = DummyView.new
10 | name = "Home"
11 | url = :home_path
12 | allow(Loaf::Crumb).to receive(:new).with(name, url, {})
13 | instance.breadcrumb name, url
14 | expect(Loaf::Crumb).to have_received(:new).with(name, url, {})
15 | end
16 |
17 | it "adds crumb to breadcrumbs storage" do
18 | instance = DummyView.new
19 | expect {
20 | instance.breadcrumb "Home", :home_path
21 | }.to change { instance._breadcrumbs.size }.by(1)
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/spec/unit/view_extensions/breadcrumb_trail_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | RSpec.describe Loaf::ViewExtensions, "#breadcrumb_trail" do
4 | it "doesn't configure any breadcrumbs by default" do
5 | view = DummyView.new
6 |
7 | expect(view.breadcrumb_trail.to_a).to be_empty
8 | end
9 |
10 | it "resolves breadcrumb paths" do
11 | view = DummyView.new
12 | view.breadcrumb("home", :home_path)
13 | view.breadcrumb("posts", :posts_path)
14 | view.set_path("/posts")
15 |
16 | yielded = []
17 | block = -> (crumb) { yielded << crumb.to_a }
18 | view.breadcrumb_trail(&block)
19 |
20 | expect(yielded).to eq([
21 | ["home", "/", false],
22 | ["posts", "/posts", true]
23 | ])
24 | end
25 |
26 | it "matches current path with :inclusive option as default" do
27 | view = DummyView.new
28 | view.breadcrumb("home", :home_path)
29 | view.breadcrumb("posts", :posts_path)
30 | view.set_path("/posts?id=73-title")
31 |
32 | yielded = []
33 | block = -> (crumb) { yielded << crumb.to_a }
34 | view.breadcrumb_trail(&block)
35 |
36 | expect(yielded).to eq([
37 | ["home", "/", false],
38 | ["posts", "/posts", true]
39 | ])
40 | end
41 |
42 | it "matches current path with :inclusive when query params" do
43 | view = DummyView.new
44 | view.breadcrumb("posts", "/posts", match: :inclusive)
45 | view.set_path("/posts?foo=bar")
46 |
47 | expect(view.breadcrumb_trail.map(&:to_a)).to eq([["posts", "/posts", true]])
48 | end
49 |
50 | it "matches current path with :inclusive when nested" do
51 | view = DummyView.new
52 | view.breadcrumb("posts", "/posts", match: :inclusive)
53 | view.set_path("/posts/1/comment")
54 |
55 | expect(view.breadcrumb_trail.map(&:to_a)).to eq([["posts", "/posts", true]])
56 | end
57 |
58 | it "doesn't match with :inclusive when unrelated path" do
59 | view = DummyView.new
60 | view.breadcrumb("posts", "/posts", match: :inclusive)
61 | view.set_path("/post/1")
62 |
63 | expect(view.breadcrumb_trail.map(&:to_a)).to eq([["posts", "/posts", false]])
64 | end
65 |
66 | it "matches current path with :inclusive when extra trailing slash" do
67 | view = DummyView.new
68 | view.breadcrumb("posts", "/posts/", match: :inclusive)
69 | view.set_path("/posts")
70 |
71 | expect(view.breadcrumb_trail.map(&:to_a)).to eq([["posts", "/posts/", true]])
72 | end
73 |
74 | it "matches current path with :inclusive when absolute path" do
75 | view = DummyView.new
76 | view.breadcrumb("posts", "http://www.example.com/posts/", match: :inclusive)
77 | view.set_path("/posts")
78 |
79 | expect(view.breadcrumb_trail.map(&:to_a)).to eq([
80 | ["posts", "http://www.example.com/posts/", true]
81 | ])
82 | end
83 |
84 | it "matches current path with :exact when trailing slash" do
85 | view = DummyView.new
86 | view.breadcrumb("posts", "/posts/", match: :exact)
87 | view.set_path("/posts")
88 |
89 | expect(view.breadcrumb_trail.map(&:to_a)).to eq([["posts", "/posts/", true]])
90 | end
91 |
92 | it "fails to match current path with :exact when nested" do
93 | view = DummyView.new
94 | view.breadcrumb("posts", "/posts", match: :exact)
95 | view.set_path("/posts/1/comment")
96 |
97 | expect(view.breadcrumb_trail.map(&:to_a)).to eq([["posts", "/posts", false]])
98 | end
99 |
100 | it "fails to match current path with :exact when query params" do
101 | view = DummyView.new
102 | view.breadcrumb("posts", "/posts", match: :exact)
103 | view.set_path("/posts?foo=bar")
104 |
105 | expect(view.breadcrumb_trail.map(&:to_a)).to eq([["posts", "/posts", false]])
106 | end
107 |
108 | it "matches current path with :exclusive option when query params" do
109 | view = DummyView.new
110 | view.breadcrumb("posts", "/posts", match: :exclusive)
111 | view.set_path("/posts?foo=bar")
112 |
113 | expect(view.breadcrumb_trail.map(&:to_a)).to eq([["posts", "/posts", true]])
114 | end
115 |
116 | it "fails to match current path with :exclusive option when nested" do
117 | view = DummyView.new
118 | view.breadcrumb("posts", "/posts", match: :exclusive)
119 | view.set_path("/posts/1/comment")
120 |
121 | expect(view.breadcrumb_trail.map(&:to_a)).to eq([["posts", "/posts", false]])
122 | end
123 |
124 | it "matches current path with regex option when query params" do
125 | view = DummyView.new
126 | view.breadcrumb("posts", "/posts", match: %r{/po})
127 | view.set_path("/posts?foo=bar")
128 |
129 | expect(view.breadcrumb_trail.map(&:to_a)).to eq([["posts", "/posts", true]])
130 | end
131 |
132 | it "matches current path with query params option" do
133 | view = DummyView.new
134 | view.breadcrumb("posts", "/posts", match: {foo: :bar})
135 | view.set_path("/posts?foo=bar&baz=boo")
136 |
137 | expect(view.breadcrumb_trail.map(&:to_a)).to eq([["posts", "/posts", true]])
138 | end
139 |
140 | it "fails to match current path with query params option" do
141 | view = DummyView.new
142 | view.breadcrumb("posts", "/posts", match: {foo: :bar})
143 | view.set_path("/posts?foo=2&baz=boo")
144 |
145 | expect(view.breadcrumb_trail.map(&:to_a)).to eq([["posts", "/posts", false]])
146 | end
147 |
148 | it "overrides breadcrumb :inclusive match option with :exclusive" do
149 | view = DummyView.new
150 | view.breadcrumb("posts", "/posts", match: :inclusive)
151 | view.set_path("/posts/1/comment")
152 |
153 | yielded = []
154 | block = ->(crumb) { yielded << crumb.to_a }
155 | view.breadcrumb_trail(match: :exclusive, &block)
156 |
157 | expect(yielded).to eq([["posts", "/posts", false]])
158 | end
159 |
160 | it "overrides default :inclusive match option with :exact" do
161 | view = DummyView.new
162 | view.breadcrumb("posts", :posts_path)
163 | view.set_path("/posts/1/comment")
164 |
165 | yielded = []
166 | block = ->(crumb) { yielded << crumb.to_a }
167 | view.breadcrumb_trail(match: :exact, &block)
168 |
169 | expect(yielded).to eq([["posts", "/posts", false]])
170 | end
171 |
172 | it "fails to recognize the match option" do
173 | view = DummyView.new
174 | view.breadcrumb("posts", "http://www.example.com/posts/", match: :boom)
175 | view.set_path("/posts")
176 | block = -> (*args) { }
177 | expect {
178 | view.breadcrumb_trail(&block)
179 | }.to raise_error(ArgumentError, "Unknown `:boom` match option!")
180 | end
181 |
182 | it "forces current path" do
183 | view = DummyView.new
184 | view.breadcrumb("home", :home_path)
185 | view.breadcrumb("posts", :posts_path, match: :force)
186 | view.set_path("/")
187 |
188 | yielded = []
189 | block = -> (crumb) { yielded << crumb.to_a }
190 | view.breadcrumb_trail(&block)
191 |
192 | expect(yielded).to eq([
193 | ["home", "/", true],
194 | ["posts", "/posts", true]
195 | ])
196 | end
197 |
198 | it "returns enumerator without block" do
199 | view = DummyView.new
200 | view.breadcrumb("home", :home_path)
201 | view.breadcrumb("posts", :posts_path)
202 | view.set_path("/posts")
203 |
204 | result = view.breadcrumb_trail
205 |
206 | expect(result).to be_a(Enumerable)
207 | expect(result.take(2).map(&:to_a)).to eq([
208 | ["home", "/", false],
209 | ["posts", "/posts", true]
210 | ])
211 | end
212 |
213 | it "validates passed options" do
214 | view = DummyView.new
215 | block = -> (name, url, styles) { }
216 | expect {
217 | view.breadcrumb_trail(unknown: true, &block)
218 | }.to raise_error(Loaf::InvalidOptions)
219 | end
220 |
221 | it "permits arbitrary length crumb names" do
222 | view = DummyView.new
223 | view.breadcrumb(" ", :home_path)
224 | view.set_path("/posts")
225 |
226 | yielded = []
227 | block = -> (crumb) { yielded << crumb.to_a }
228 | view.breadcrumb_trail(&block)
229 |
230 | expect(yielded).to eq([
231 | [" ", "/", false]
232 | ])
233 | end
234 |
235 | it "uses global configuration for crumb formatting" do
236 | view = DummyView.new
237 | view.breadcrumb("home-sweet-home", :home_path)
238 | view.breadcrumb("posts-for-everybody", :posts_path)
239 | view.set_path("/posts")
240 |
241 | yielded = []
242 | block = -> (crumb) { yielded << crumb.to_a }
243 | view.breadcrumb_trail(&block)
244 |
245 | expect(yielded).to eq([
246 | ["home-sweet-home", "/", false],
247 | ["posts-for-everybody", "/posts", true]
248 | ])
249 | end
250 |
251 | it "overwrites global configuration" do
252 | view = DummyView.new
253 | view.breadcrumb("home-sweet-home", :home_path)
254 | view.breadcrumb("posts-for-everybody", :posts_path)
255 | view.set_path("/posts/1")
256 |
257 | yielded = []
258 | block = -> (crumb) { yielded << crumb.to_a }
259 | view.breadcrumb_trail(match: :exact, &block)
260 |
261 | expect(yielded).to eq([
262 | ["home-sweet-home", "/", false],
263 | ["posts-for-everybody", "/posts", false]
264 | ])
265 | end
266 |
267 | it "allows to enumerate all breadcrumb properties individually" do
268 | view = DummyView.new
269 | links = {
270 | "Home" => :home_path,
271 | "Posts" => :posts_path,
272 | "Edit post" => "/posts/1"
273 | }
274 | links.each do |name, url|
275 | view.breadcrumb(name, url)
276 | end
277 | view.set_path("/posts/1")
278 |
279 | expect(view.breadcrumb_trail.map(&:name)).to eq(links.keys)
280 | expect(view.breadcrumb_trail.map(&:url)).to eq(%w[/ /posts /posts/1])
281 | expect(view.breadcrumb_trail(match: :exact).map(&:current?)).to eq([false, false, true])
282 | end
283 | end
284 |
--------------------------------------------------------------------------------
/spec/unit/view_extensions/has_breadcrumbs_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | RSpec.describe Loaf::ViewExtensions, "#breadcrumbs?" do
4 | it "checks for breadcrumbs existance" do
5 | instance = DummyView.new
6 | expect(instance.breadcrumbs?).to eq(false)
7 | instance.breadcrumb "Home", :home_path
8 | expect(instance.breadcrumbs?).to eq(true)
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/tasks/console.rake:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | desc 'Load gem inside irb console'
4 | task :console do
5 | require 'irb'
6 | require 'irb/completion'
7 | require File.join(__FILE__, '../../lib/loaf')
8 | ARGV.clear
9 | IRB.start
10 | end
11 |
--------------------------------------------------------------------------------
/tasks/coverage.rake:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | desc 'Measure code coverage'
4 | task :coverage do
5 | begin
6 | original, ENV['COVERAGE'] = ENV['COVERAGE'], 'true'
7 | Rake::Task['spec'].invoke
8 | ensure
9 | ENV['COVERAGE'] = original
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/tasks/spec.rake:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | begin
4 | require 'rspec/core/rake_task'
5 |
6 | desc 'Run all specs'
7 | RSpec::Core::RakeTask.new(:spec) do |task|
8 | task.pattern = 'spec/{unit,integration}{,/*/**}/*_spec.rb'
9 | end
10 |
11 | namespace :spec do
12 | desc 'Run unit specs'
13 | RSpec::Core::RakeTask.new(:unit) do |task|
14 | task.pattern = 'spec/unit{,/*/**}/*_spec.rb'
15 | end
16 |
17 | desc 'Run integration specs'
18 | RSpec::Core::RakeTask.new(:integration) do |task|
19 | task.pattern = 'spec/integration{,/*/**}/*_spec.rb'
20 | end
21 | end
22 |
23 | rescue LoadError
24 | %w[spec spec:unit spec:integration].each do |name|
25 | task name do
26 | $stderr.puts "In order to run #{name}, do `gem install rspec`"
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------