Ruby on rails: singular resource and form_for
Asked Answered
S

2

67

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?

Scalf answered 17/9, 2010 at 15:35 Comment(3)
I'm not sure if I get why you don't use singular for your resourcePopery
Because rails guide advice to use plural form of controller. But in singular form of controller exists same bug...Scalf
If you just have a singular nested resource that is giving you problems, you can use form_for [@user, :subscription, @payment] to generate paths for user_subscription_payment_path(@user, @payment) with paths like action="/users/21/subscription/payments/29".Johnettajohnette
N
64

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
Nw answered 17/9, 2010 at 18:26 Comment(3)
Rails' issues have been moved from Lighthouse to Github. Here's the issue in Github: github.com/rails/rails/issues/1769Tile
currently, this is a solution github.com/rails/rails/issues/1769#issuecomment-9556381Halftruth
Still an issue in late 2016. Just everyone is updated.. ;)Flaminius
A
58

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!

Aeolian answered 26/4, 2013 at 5:9 Comment(5)
@aidan thanks! The other answer had already been accepted for awhile when I encountered this issue myself.Aeolian
That's a lot of investigating for a super rad answer. way to go.Dixon
This is a ninja answer.Misunderstanding
This is the only easy solution for a polymorphic singular resource where you can't explicitly set the url option.Winkler
All attributes on the ActiveName class (route_key, param_key, cache_key, etc) are RW, so you don't need to use instance_variable_set (which is pretty intrusive and not supposed to be used in regular code). This works: class Order < ActiveRecord::Base model_name.param_key = 'order' endContrastive

© 2022 - 2024 — McMap. All rights reserved.