Ruby on Rails - form_for自定义PATCH操作

3
我正在处理一个基于Ruby on Rails的Spree电子商务店,并希望创建一个自定义操作,让用户可以直接在结账页面标记他们的订单为已完成,而无需经历交付等步骤。我已经覆盖了所有的结账步骤,但是无法通过将订单发送到Orders控制器中的自定义操作来完成“结账”按钮。
我想我已经勾选了所有的框:在routes.rb中创建了一个补丁操作并检查了rake路由以确保路由存在。但仍然告诉我没有路由。
在提交任何内容之前,购物车页面甚至都无法加载,出现以下错误。我已经花了一整天的时间来解决这个问题,所以任何想法都会很好...
错误信息:
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 %>

我还没有创建控制器,但它可能是这样的:
def complete
  # mark order as complete
  # redirect to confirmation page
end

非常感谢您的帮助。谢谢。

编辑:这是更新后的视图(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>

以下是整个控制器的代码:

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

编辑

自从我发布这个问题以来,显然你需要以特定的方式声明路由,尽管rake routes显示它们是正确的。

在routes.rb中添加以下内容:

Spree::Core::Engine.routes.draw do
   # add your custom  routes here, e.g.
   get '/terms-and-conditions' => 'home#terms', as: :terms
end

这将允许您使用<%= link_to("条款", terms_path) %>助手。

有关详细信息,请参见添加路由到Rails的Spree电子商务。 我希望文档能更好,因为我无法在其他地方找到提及它的地方。

1个回答

2
这是因为您没有将对象传递给表单。所以在路由中没有id参数,路由器无法匹配。
您的路由被定义为member action,这意味着它需要一个id参数。而您却传递了一个符号。
<%= form_for :order <-- problem

线索在错误信息中:
No route matches {:action=>"complete", :controller=>"spree/orders", :method=>:patch}

注意一下错误信息哈希中没有“id”参数。为了解决这个问题,请在表单中提供一个对象,例如:
<%= form_for @order, url: complete_order_path(@order) do |f| %>

“@order”实例变量在控制器中设置。
另外,你可以像下面这样定义你的路由:
resources :orders do
  member do
    patch :complete
  end
  # or, since it's only one route...
  patch :complete, on: :member
end

注意,您可以使用符号,而且不必指定控制器,因为它是从资源名称推断出来的。
最后,您不需要告诉表单方法应该是 patch。Rails 从传递的对象中推断出这一点,在本例中是 @order。如果是新的,则方法将是 POST,否则将是 PATCH

非常感谢您的回复,非常感激。昨天我尝试了几种form_for组合,但都没有成功。我尝试了您的代码,但出现了以下错误信息:undefined method complete_order_path'`我还应该提到,如果这有所不同,此form_for代码位于app/views/orders/edit.html.erb文件中。 - Tebbers
@order实例变量已经存在于edit.html.erb视图中 - 我可以通过在页面上打印@order.id来测试这一点,正确显示订单ID。因此,在此页面上,订单对象是有效的 - 我只需要将其传递给自定义操作complete。我已经编辑了我的原始问题,并附上了整个控制器和视图。再次感谢您的帮助Mohamad。 - Tebbers
是的,谢谢。我已经更改了f.submit,但它并没有解决问题。 我只是想知道这是否与Spree有关,而不是Rails问题。我可能会创建一个新的控制器,看看是否可以在那里提交 - 控制器位于app/views/controllers/spree/xxx_controller.rb中,如果这有区别的话? - Tebbers
嗨@Mohamad,我只需更改结帐流程就解决了它。我将“结帐”按钮路由到Orders控制器中的自定义操作,而不是推进订单。从:if params.has_key?(:checkout) @order.next if @order.cart? redirect_to checkout_state_path(@order.checkout_steps.first)到:if params.has_key?(:checkout) complete_order(@order)def complete_order(order) order.state = "complete" end再次感谢您的帮助,很高兴最终能找到一个简单的解决方案。 - Tebbers
似乎你需要使用 Spree::Core::Engine.routes.draw doroutes.rb 中以特殊的方式声明路由。我已经将这个问题添加到我的问题列表中,但是我另外找到了解决方法。希望官方文档能够更加详细。 - Tebbers
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接