diff --git a/.gitignore b/.gitignore index 404ce1a..d5041ae 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ tmp *.o *.a mkmf.log +.byebug_history \ No newline at end of file diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..2c9b4ef --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.7.3 diff --git a/README.md b/README.md index 276b6d3..c7db562 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ rails generate loaf:install * [2.1.1 controller](#211-controller) * [2.1.2 view](#212-view) * [2.1.3 :match](#213-match) + * [2.1.4 :request_methods](#214-request_methods) * [2.2 breadcrumb_trail](#22-breadcrumb_trail) * [3. Configuration](#3-configuration) * [4. Translation](#4-translation) @@ -207,6 +208,33 @@ To make a breadcrumb current based on the query parameters do: breadcrumb "Posts", posts_path(order: :desc), match: {order: :desc} ``` +#### 2.1.4 :request_methods + +**Loaf** allows you to match on multiple HTTP methods in order to make a breadcrumb current with the `:request_methods` option. + +The `:request_methods` key accepts `:all` or an array with following values: + +* `:get` +* `:post` +* `:put` +* `:patch` +* `:delete` +* `:head` +* `:options` +* `:link` +* `:unlink` +* `:trace` + +It's defaults to `%i[get head]` + +Here are some examples: + +```ruby +request_methods: %i[get head] +request_methods: %i[post get head] +request_methods: :all +``` + ### 2.2 breadcrumb_trail 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. diff --git a/lib/loaf/configuration.rb b/lib/loaf/configuration.rb index f5a8c0f..a062f53 100644 --- a/lib/loaf/configuration.rb +++ b/lib/loaf/configuration.rb @@ -4,7 +4,8 @@ module Loaf class Configuration VALID_ATTRIBUTES = [ :locales_path, - :match + :match, + :request_methods ].freeze attr_accessor(*VALID_ATTRIBUTES) @@ -13,6 +14,8 @@ class Configuration DEFAULT_MATCH = :inclusive + DEFAULT_REQUEST_METHODS = %i[get head].freeze + # Setup this configuration # # @api public diff --git a/lib/loaf/crumb.rb b/lib/loaf/crumb.rb index 9fe1de1..d950249 100644 --- a/lib/loaf/crumb.rb +++ b/lib/loaf/crumb.rb @@ -10,10 +10,13 @@ class Crumb attr_reader :match + attr_reader :request_methods + def initialize(name, url, options = {}) @name = name || raise_name_error @url = url || raise_url_error @match = options.fetch(:match, Loaf.configuration.match) + @request_methods = options.fetch(:request_methods, Loaf.configuration.request_methods) freeze end diff --git a/lib/loaf/view_extensions.rb b/lib/loaf/view_extensions.rb index bcd158a..d4aa964 100644 --- a/lib/loaf/view_extensions.rb +++ b/lib/loaf/view_extensions.rb @@ -52,7 +52,11 @@ def breadcrumb_trail(options = {}) _breadcrumbs.each do |crumb| name = title_for(crumb.name) path = url_for(_expand_url(crumb.url)) - current = current_crumb?(path, options.fetch(:match) { crumb.match }) + current = current_crumb?( + path, + options.fetch(:match) { crumb.match }, + request_methods: options.fetch(:request_methods) { crumb.request_methods } + ) yield(Loaf::Breadcrumb[name, path, current]) end @@ -65,8 +69,8 @@ def breadcrumb_trail(options = {}) # the pattern to match on # # @api public - def current_crumb?(path, pattern = :inclusive) - return false unless request.get? || request.head? + def current_crumb?(path, pattern = :inclusive, request_methods: Loaf.configuration.request_methods) + return false unless match_request_methods(request_methods) origin_path = URI::DEFAULT_PARSER.unescape(path).force_encoding(Encoding::BINARY) @@ -128,5 +132,16 @@ def _expand_url(url) url end end + + # Check if the HTTP request methods are allowed + # + # @retun [Boolean] + # + # @api private + def match_request_methods(request_methods) + return true if request_methods == :all + + request_methods.any? { |method| request.try("#{method}?") } + end end # ViewExtensions end # Loaf diff --git a/spec/integration/breadcrumb_trail_spec.rb b/spec/integration/breadcrumb_trail_spec.rb index bda6c72..0b8d248 100644 --- a/spec/integration/breadcrumb_trail_spec.rb +++ b/spec/integration/breadcrumb_trail_spec.rb @@ -8,66 +8,116 @@ it "shows root breadcrumb" do visit root_path - page.within '#breadcrumbs .selected' do - expect(page.html).to include('Home') + within "#breadcrumbs .selected" do + expect(page).to have_link(href: "/", text: "Home") end end it "inherits controller breadcrumb and adds index action breadcrumb" do visit posts_path - page.within '#breadcrumbs' do - expect(page.html).to include('Home') + within "#breadcrumbs" do + expect(page).to have_link(href: "/", text: "Home") end - page.within '#breadcrumbs .selected' do - expect(page.html).to include('All Posts') + within "#breadcrumbs .selected" do + expect(page).to have_link(href: "/posts", text: "All Posts") end end - it 'filters out controller breadcrumb and adds new action breadcrumb' do + it "filters out controller breadcrumb and adds new action breadcrumb" do visit new_post_path - page.within '#breadcrumbs' do - expect(page).to_not have_content('Home') - expect(page).to have_content('New Post') + within "#breadcrumbs" do + expect(page).to_not have_content("Home") + expect(page).to have_content("New Post") end end it "adds breadcrumb in view with path variable" do visit post_path(1) - page.within '#breadcrumbs .selected' do - expect(page.html).to include('Show Post in view') + within "#breadcrumbs .selected" do + expect(page).to have_link(href: "/posts/1", text: "Show Post in view") end end - it 'is current when forced' do + it "is current when forced" do visit new_post_path expect(page.current_path).to eq(new_post_path) - page.within '#breadcrumbs' do - expect(page).to have_selector('li.selected', count: 2) - expect(page.html).to include('All') - expect(page.html).to include('New Post') + within "#breadcrumbs" do + expect(page).to have_selector("li.selected", count: 2) + expect(page).to have_link(href: "/posts", text: "All") + expect(page).to have_link(href: "/posts/new", text: "New Post") end end it "allows for procs in name and url" do visit post_comments_path(1) - page.within '#breadcrumbs .selected' do - expect(page.html).to include('Post comments') + within "#breadcrumbs .selected" do + expect(page).to have_link(href: "/posts/1/comments", text: "Post comments") end end it "allows for procs in name and url without supplying the controller" do - visit post_comments_path(1) + visit post_comments_path(1, no_controller: true) - page.within "#breadcrumbs .selected" do - expect(page.html).to include( - ''\ - "Post comments No Controller" + within "#breadcrumbs .selected" do + expect(page).to have_link( + href: "/posts/1/comments?no_controller=true", + text: "Post comments No Controller" ) end end + + it "match to Non-GET methods" do + visit onboard_path + + expect(page).to have_selector("h1", text: "Onboard") + within "#breadcrumbs" do + expect(page).to have_content("Onboard") + end + + click_link "Step 1" # GET + expect(page).to have_selector("h1", text: "Step 1") + within "#breadcrumbs" do + expect(page).to have_link(href: "/onboard", text: "Onboard") + end + within "#breadcrumbs .selected" do + expect(page).to have_link(href: "/onboard/step/1", text: "Step 1") + end + + click_on "Save & Next" # POST + expect(page).to have_selector("h1", text: "Step 2") + within "#breadcrumbs .selected" do + expect(page).to have_link(href: "/onboard/step/2", text: "Step 2") + end + + click_on "Save & Next" # PUT + expect(page).to have_selector("h1", text: "Step 3") + within "#breadcrumbs .selected" do + expect(page).to have_link(href: "/onboard/step/3", text: "Step 3") + end + + if Rails.version >= "4.0.0" + click_on "Save & Next" # PATCH + expect(page).to have_selector("h1", text: "Step 4") + within "#breadcrumbs .selected" do + expect(page).to have_link(href: "/onboard/step/4", text: "Step 4") + end + end + + click_on "Save & Next" # DELETE + expect(page).to have_selector("h1", text: "Step 5") + within "#breadcrumbs .selected" do + expect(page).to have_link(href: "/onboard/step/5", text: "Step 5") + end + + click_on "Save & Next" # GET + expect(page).to have_selector("h1", text: "Step 6") + within "#breadcrumbs .selected" do + expect(page).to have_link(href: "/onboard/step/6", text: "Step 6") + end + end end diff --git a/spec/integration/configuration_spec.rb b/spec/integration/configuration_spec.rb index 4aae487..b6ff5db 100644 --- a/spec/integration/configuration_spec.rb +++ b/spec/integration/configuration_spec.rb @@ -1,10 +1,10 @@ # encoding: utf-8 -RSpec.describe 'setting configuration options' do +RSpec.describe "setting configuration options" do it "contains 'selected' inside the breadcrumb markup" do visit posts_path - page.within '#breadcrumbs' do - expect(page).to have_selector('.selected') + within "#breadcrumbs" do + expect(page).to have_selector(".selected") end end end diff --git a/spec/rails_app/app/controllers/comments_controller.rb b/spec/rails_app/app/controllers/comments_controller.rb index c97a999..1375565 100644 --- a/spec/rails_app/app/controllers/comments_controller.rb +++ b/spec/rails_app/app/controllers/comments_controller.rb @@ -3,10 +3,12 @@ class Article < Struct.new(:id, :title); end class CommentsController < ApplicationController breadcrumb lambda { |c| c.find_article(c.params[:post_id]).title }, - lambda { |c| c.post_comments_path(c.params[:post_id]) } + lambda { |c| c.post_comments_path(c.params[:post_id]) }, + match: :exact breadcrumb -> { find_article(params[:post_id]).title + " No Controller" }, - -> { post_comments_path(params[:post_id], no_controller: true) } + -> { post_comments_path(params[:post_id], no_controller: true) }, + match: :exact def index end diff --git a/spec/rails_app/app/controllers/onboard_controller.rb b/spec/rails_app/app/controllers/onboard_controller.rb new file mode 100644 index 0000000..f5266bb --- /dev/null +++ b/spec/rails_app/app/controllers/onboard_controller.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class OnboardController < ApplicationController + breadcrumb "Onboard", :onboard_path, match: :exact + + def setup + case params[:step] + when "1" + breadcrumb "Step 1", onboard_step_path(step: 1), + match: :exact, request_methods: %i[get] + render :step1 + when "2" + breadcrumb "Step 2", onboard_step_path(step: 2), + match: :exact, request_methods: %i[get post] + render :step2 + when "3" + breadcrumb "Step 3", onboard_step_path(step: 3), + match: :exact, request_methods: %i[get put] + render :step3 + when "4" + breadcrumb "Step 4", onboard_step_path(step: 4), + match: :exact, request_methods: %i[get patch] + render :step4 + when "5" + breadcrumb "Step 5", onboard_step_path(step: 5), + match: :exact, request_methods: %i[get delete] + render :step5 + when "6" + breadcrumb "Step 6", onboard_step_path(step: 6), + match: :exact, request_methods: :all + render :step6 + else + render :setup + end + end +end \ No newline at end of file diff --git a/spec/rails_app/app/views/onboard/setup.html.erb b/spec/rails_app/app/views/onboard/setup.html.erb new file mode 100644 index 0000000..5b4fea4 --- /dev/null +++ b/spec/rails_app/app/views/onboard/setup.html.erb @@ -0,0 +1,3 @@ +