Reputation: 484
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.
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.
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....
The error:
No route matches {:action=>"complete", :controller=>"spree/orders", :method=>:patch}
Routes.rb:
resources :orders do
member do
patch 'complete', to: 'orders#complete'
end
end
Rake routes:
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
HTML:
<%= form_for :order, url: {action: 'complete', method: :patch} do |f| %>
<% f.submit %>
<% end %>
I haven't created the controller yet but it would be:
def complete
# mark order as complete
# redirect to confirmation page
end
Would really appreciate any help. Thanks
EDIT: Here is the updated view (app/views/orders/edit.html.erb):
<% @body_id = 'cart' %>
<div data-hook="cart_container">
<h1><%= Spree.t(:shopping_cart) %></h1>
<% if @order.line_items.empty? %>
<div data-hook="empty_cart">
<div class="alert alert-info"><%= Spree.t(:your_cart_is_empty) %></div>
<p><%= link_to Spree.t(:continue_shopping), products_path, class: 'btn btn-default' %></p>
</div>
<% else %>
<div data-hook="outside_cart_form">
<%= form_for @order, url: update_cart_path, html: { id: 'update-cart' } do |order_form| %>
<div data-hook="inside_cart_form">
<div data-hook="cart_items" class="table-responsive">
<%= render partial: 'form', locals: { order_form: order_form } %>
</div>
</div>
<% end %>
</div>
<div id="empty-cart" class="col-md-6" data-hook>
<%= form_tag empty_cart_path, method: :put do %>
<p id="clear_cart_link" data-hook>
<%= submit_tag Spree.t(:empty_cart), class: 'btn btn-default' %>
<%= Spree.t(:or) %>
<%= link_to Spree.t(:continue_shopping), products_path, class: 'continue' %>
</p>
<% end %>
</div>
<div id="complete-order">
complete order here - submit to custom controller
<%= @order.id %>
<%= form_for @order, url: complete_order_path(@order) do |f| %>
<% f.submit %>
<% end %>
</div>
<% end %>
</div>
Here is the whole controller:
module Spree
class OrdersController < Spree::StoreController
before_action :check_authorization
rescue_from ActiveRecord::RecordNotFound, :with => :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 => 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 && @order.completed?
Spree.t(:order_number, :number => @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
EDIT
It has become apparent since I posted this question that you do in fact need to declare your routes in a special way, despite rake routes showing them as correct.
In routes.rb, add this:
Spree::Core::Engine.routes.draw do
# add your custom routes here, e.g.
get '/terms-and-conditions' => 'home#terms', as: :terms
end
This will then allow you to use the <%= link_to("Terms", terms_path) %> helper.
See Adding Routes to Rails' Spree E-Commerce 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.
Upvotes: 3
Views: 5167
Reputation: 35349
This happens because you are not passing an object to the form. So there's no id
parameter in the route, and the router fails to make a match.
Your route is defined as a member
action, which means it expects an id
parameter. You are passing a symbol instead.
<%= form_for :order <-- problem
The clue is in the error message:
No route matches {:action=>"complete", :controller=>"spree/orders", :method=>:patch}
Notice how there's no id
parameter in the hash in the error message?
To solve this, provide an object to the form. For example:
<%= form_for @order, url: complete_order_path(@order) do |f| %>
Where @order
instance variable is set in the controller.
On a side note, you can define your routes like so:
resources :orders do
member do
patch :complete
end
# or, since it's only one route...
patch :complete, on: :member
end
Notice you can use symbols, and you don't have to specify the controller because it's inferred from the resource name.
Finally, you don't need to tell the form that the method should be patch. Rails infers this from the object passed in, in this case @order
. If it's
new, the method will be POST
, otherwise it will be PATCH
.
Upvotes: 2