Ruby on rails: singular resource and form_for
I want user to work with only one order connected to user's session. So I set singular resource for order
routes.rb:
resource :order
views/orders/new.html.erb:
<%= form_for @order do |f| %>
...
<% end %>
But when I open the new order page I get an error:
undefined method `orders_path`
I know, that I can set :url => order_path
in form_for
, but what is the true way of resolving this collision?
Unfortunately, this is a bug. You'll have to set the url like you mention.
= form_for @order, :url => order_path do |f|
Note that this will properly route to create
or update
depending on whether @order
is persisted.
Update
There's another option now. You can add this to your routes config:
resolve("Order") { [:order] }
Then when the polymorphic_url
method is given an object with class name "Order" it will use [:order]
as the url component instead of calling model_name.route_key
as described in jskol's answer.
This has the limitation that it cannot be used within scopes or namespaces. You can route a namespaced model at the top level of the routes config:
resolve('Shop::Order') { [:shop, :order] }
But it won't have an effect on routes with extra components, so
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
Where does that magic path come from?
It took me a lot of tracing but I ultimately found that the url_for determines the path for your model using the polymorphic_path
method defined in ActionDispatch::Routing::PolymorphicRoutes. polymorphic_path
ultimately gets the automagic path for your model by calling something along the lines of:
record.class.model_name.route_key
I'm simplifying slightly but this is basically it. If you have an array (e.g. form_for[@order, @item]
) the above is called on each element and the results are joined with _
.
The model_name
method on your Class comes from 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
How can I change it?
Fortunately ActiveModel::Name precalculates all values including route_key, so to override that value all we have to do is change the value of the instance variable.
For the :order
resource in your question:
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 %>
Try it out!