Ruby on rails: singular resource and form_for

67
我希望用户只能处理与用户会话相连的一个订单。因此,我为订单设置了单数资源。
routes.rb:
resource :order

views/orders/new.html.erb:

<%= form_for @order do |f| %>
   ...
<% end %>

但是当我打开新订单页面时,出现了一个错误:

undefined method `orders_path`

我知道在form_for中可以设置:url => order_path,但解决这个冲突的真正方式是什么?


我不确定我是否理解为什么您不使用单数形式来命名您的资源。 - marcgg
因为Rails指南建议使用控制器的复数形式。但是在控制器的单数形式中存在相同的错误... - petRUShka
如果您只有一个嵌套资源出现问题,您可以使用 form_for [@user, :subscription, @payment] 生成路径 user_subscription_payment_path(@user, @payment),路径类似于 action="/users/21/subscription/payments/29" - Chloe
2个回答

64

很遗憾,这是一个 bug。你需要像你提到的那样设置 URL。

= form_for @order, :url => order_path do |f|

请注意,这将正确地路由到createupdate,具体取决于是否保存了@order


更新

现在有另一种选择。您可以将此添加到路由配置中:

resolve("Order") { [:order] }

当使用类名为“Order”的对象调用polymorphic_url方法时,它将使用[:order]作为URL组件,而不是像jskol的回答中描述的那样调用model_name.route_key

这种方法的限制在于它不能在作用域或命名空间中使用。您可以在路由配置的顶级路由中路由一个命名空间模型:

resolve('Shop::Order') { [:shop, :order] }

但它不会影响具有额外组件的路由,因此

url_for(@order)           # resolves to shop_order_url(@order)
url_for([:admin, @order]) # resolves to admin_shop_orders_url(@order) 
                          #        note plural 'orders' ↑
                          #        also, 'shop' is from module name, not `resolve` in the second case

5
Rails的问题已经从Lighthouse迁移到Github。这是在Github上的问题链接:https://github.com/rails/rails/issues/1769。 - balexand
2
目前,这是一个解决方案 https://github.com/rails/rails/issues/1769#issuecomment-9556381 - kuboon
1
到了2016年末,这仍然是一个问题。只是每个人都已经更新了.. ;) - ferdynator

57

那个神奇的路径是从哪儿来的?

我追踪了很久,最终发现 url_for 使用 polymorphic_path 方法确定您的模型的路径,该方法定义在 ActionDispatch::Routing::PolymorphicRoutes 中。 polymorphic_path 最终通过调用以下类似的内容获取您的模型的自动路径:

record.class.model_name.route_key

我稍微简化了一下,但基本上就是这样。如果你有一个数组(例如form_for[@order, @item]),那么上述方法将在每个元素上调用,并将结果用_连接起来。


你的类上的model_name方法来自于ActiveRecord::Naming

module ActiveModel
  ...
  module Naming
    ...
    def model_name
      @_model_name ||= begin
        namespace = self.parents.detect do |n|
          n.respond_to?(:use_relative_model_naming?) && 
                                                 n.use_relative_model_naming?
        end
        ActiveModel::Name.new(self, namespace)
      end
    end
  end
end


我该如何改变它?

幸运的是,ActiveModel::Name 预先计算了包括 route_key 在内的所有值,因此要覆盖该值,我们只需更改实例变量的值。

对于您问题中的 :order 资源:

class Order < ActiveRecord::Base
  model_name.instance_variable_set(:@route_key, 'order')
  ...
end

# new.html.erb
<%= form_for @order do |f| # Works with action="/order" %>
    ...
<% end %>

试一下吧!


1
@aidan 谢谢!当我自己遇到这个问题时,另一个答案已经被接受了一段时间。 - jshkol
2
这需要大量的调查才能得出一个超级酷的答案。干得好! - courtsimas
这是一个忍者回答。 - RubyRedGrapefruit
1
这是唯一的简单解决方案,适用于多态单一资源,其中您无法显式设置url选项。 - Michael MacDonald

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