title: Ruby on Rails - custom PATCH action for form_for
body: <p>I'm working on a Spree e-commerce store built on Ruby on Rails and want a custom action where a user can mark their order as complete straight from the checkout page without going through delivery etc. I've overridden all the checkout steps but cannot get the 'Checkout' button to complete the order by sending the order to a custom action in the Orders controller. </p>
  
  <p>I'd like to think I've ticked off all the boxes: created a patch action in routes.rb and checked rake routes to make sure the route exists. But it's still telling me there is no route.</p>
  
  <p>The cart page won't even load before I submit anything, with the following error. I've spent all day trying to fix this so any ideas would be great....</p>
  
  <p>The error:</p>
  
  <pre><code>No route matches {:action=&gt;"complete", :controller=&gt;"spree/orders", :method=&gt;:patch}
  </code></pre>
  
  <p>Routes.rb:</p>
  
  <pre><code>resources :orders do
     member do
       patch 'complete', to: 'orders#complete'
     end
   end
  </code></pre>
  
  <p>Rake routes:</p>
  
  <pre><code>        Prefix Verb   URI Pattern                    Controller#Action
         spree        /                              Spree::Core::Engine
  complete_order PATCH  /orders/:id/complete(.:format) orders#complete
          orders GET    /orders(.:format)              orders#index
                 POST   /orders(.:format)              orders#create
       new_order GET    /orders/new(.:format)          orders#new
      edit_order GET    /orders/:id/edit(.:format)     orders#edit
           order GET    /orders/:id(.:format)          orders#show
                 PATCH  /orders/:id(.:format)          orders#update
                 PUT    /orders/:id(.:format)          orders#update
                 DELETE /orders/:id(.:format)          orders#destroy
  </code></pre>
  
  <p>HTML:</p>
  
  <pre><code>&lt;%= form_for :order, url: {action: 'complete', method: :patch} do |f| %&gt;
    &lt;% f.submit %&gt;
  &lt;% end %&gt;
  </code></pre>
  
  <p>I haven't created the controller yet but it would be:</p>
  
  <pre><code>def complete
    # mark order as complete
    # redirect to confirmation page
  end
  </code></pre>
  
  <p>Would really appreciate any help. Thanks</p>
  
  <p>EDIT: Here is the updated view (app/views/orders/edit.html.erb):</p>
  
  <pre><code>&lt;% @body_id = 'cart' %&gt;
  &lt;div data-hook="cart_container"&gt;
    &lt;h1&gt;&lt;%= Spree.t(:shopping_cart) %&gt;&lt;/h1&gt;
  
    &lt;% if @order.line_items.empty? %&gt;
      &lt;div data-hook="empty_cart"&gt;
        &lt;div class="alert alert-info"&gt;&lt;%= Spree.t(:your_cart_is_empty) %&gt;&lt;/div&gt;
        &lt;p&gt;&lt;%= link_to Spree.t(:continue_shopping), products_path, class: 'btn btn-default' %&gt;&lt;/p&gt;
      &lt;/div&gt;
    &lt;% else %&gt;
      &lt;div data-hook="outside_cart_form"&gt;
        &lt;%= form_for @order, url: update_cart_path, html: { id: 'update-cart' } do |order_form| %&gt;
          &lt;div data-hook="inside_cart_form"&gt;
  
            &lt;div data-hook="cart_items" class="table-responsive"&gt;
              &lt;%= render partial: 'form', locals: { order_form: order_form } %&gt;
            &lt;/div&gt;
  
          &lt;/div&gt;
        &lt;% end %&gt;
      &lt;/div&gt;
  
      &lt;div id="empty-cart" class="col-md-6" data-hook&gt;
        &lt;%= form_tag empty_cart_path, method: :put do %&gt;
          &lt;p id="clear_cart_link" data-hook&gt;
            &lt;%= submit_tag Spree.t(:empty_cart), class: 'btn btn-default' %&gt;
            &lt;%= Spree.t(:or) %&gt;
            &lt;%= link_to Spree.t(:continue_shopping), products_path, class: 'continue' %&gt;
          &lt;/p&gt;
        &lt;% end %&gt;
      &lt;/div&gt;
  
      &lt;div id="complete-order"&gt;
        complete order here - submit to custom controller
        &lt;%= @order.id  %&gt;
        &lt;%= form_for @order, url: complete_order_path(@order) do |f| %&gt;
          &lt;% f.submit %&gt;
        &lt;% end %&gt;
  
  
      &lt;/div&gt;
  
    &lt;% end %&gt;
  &lt;/div&gt;
  </code></pre>
  
  <p>Here is the whole controller:</p>
  
  <pre><code>module Spree
    class OrdersController &lt; Spree::StoreController
      before_action :check_authorization
      rescue_from ActiveRecord::RecordNotFound, :with =&gt; :render_404
      helper 'spree/products', 'spree/orders'
  
      respond_to :html
  
      before_action :assign_order_with_lock, only: :update
      skip_before_action :verify_authenticity_token, only: [:populate]
  
      def show
        @order = Order.includes(line_items: [variant: [:option_values, :images, :product]], bill_address: :state, ship_address: :state).find_by_number!(params[:id])
      end
  
      def complete
        @order = current_order
      end
  
      def update
        if @order.contents.update_cart(order_params)
          respond_with(@order) do |format|
            format.html do
              if params.has_key?(:checkout)
                @order.next if @order.cart?
                redirect_to checkout_state_path(@order.checkout_steps.first)
              else
                redirect_to cart_path
              end
            end
          end
        else
          respond_with(@order)
        end
      end
  
      # Shows the current incomplete order from the session
      def edit
        @order = current_order || Order.incomplete.
                                    includes(line_items: [variant: [:images, :option_values, :product]]).
                                    find_or_initialize_by(guest_token: cookies.signed[:guest_token])
        associate_user
      end
  
      # Adds a new item to the order (creating a new order if none already exists)
      def populate
        order    = current_order(create_order_if_necessary: true)
        variant  = Spree::Variant.find(params[:variant_id])
        quantity = params[:quantity].to_i
        options  = params[:options] || {}
  
        # 2,147,483,647 is crazy. See issue #2695.
        if quantity.between?(1, 2_147_483_647)
          begin
            order.contents.add(variant, quantity, options)
          rescue ActiveRecord::RecordInvalid =&gt; e
            error = e.record.errors.full_messages.join(", ")
          end
        else
          error = Spree.t(:please_enter_reasonable_quantity)
        end
  
        if error
          flash[:error] = error
          redirect_back_or_default(spree.root_path)
        else
          respond_with(order) do |format|
            format.html { redirect_to cart_path }
          end
        end
      end
  
      def empty
        if @order = current_order
          @order.empty!
        end
  
        redirect_to spree.cart_path
      end
  
      def accurate_title
        if @order &amp;&amp; @order.completed?
          Spree.t(:order_number, :number =&gt; @order.number)
        else
          Spree.t(:shopping_cart)
        end
      end
  
      def check_authorization
        order = Spree::Order.find_by_number(params[:id]) || current_order
  
        if order
          authorize! :edit, order, cookies.signed[:guest_token]
        else
          authorize! :create, Spree::Order
        end
      end
  
      private
  
        def order_params
          if params[:order]
            params[:order].permit(*permitted_order_attributes)
          else
            {}
          end
        end
  
        def assign_order_with_lock
          @order = current_order(lock: true)
          unless @order
            flash[:error] = Spree.t(:order_not_found)
            redirect_to root_path and return
          end
        end
    end
  end
  </code></pre>
  
  <p>EDIT</p>
  
  <p>It has become apparent since I posted this question that you do in fact need to declare your routes in a special way, <strong>despite</strong> rake routes showing them as correct.</p>
  
  <p>In routes.rb, add this:</p>
  
  <pre><code>Spree::Core::Engine.routes.draw do
     # add your custom  routes here, e.g.
     get '/terms-and-conditions' =&gt; 'home#terms', as: :terms
  end
  </code></pre>
  
  <p>This will then allow you to use the &lt;%= link_to("Terms", terms_path) %> helper.</p>
  
  <p>See <a href="https://stackoverflow.com/questions/10064533/adding-routes-to-rails-spree-e-commerce">Adding Routes to Rails&#39; Spree E-Commerce</a> for more details. I wish the documentation was better on this as it is mentioned NOWHERE apart from on SO as far as I can tell.</p>