Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

url_for <model> generates a <model>_url route instead of <model>_path

#5874

Rails 3.0.0

for example link_to @user will generate the equivalent of user_url. shouldn't the default behavior be user_path?

This is because url_for line 134 is calling polymorphic_url instead of polymorphic_path

Reported by Nader Akhnoukh · October 28th, 2010 @ 12:19 AM

State: invalid
Milestone: none
Assigned to: Andrew White Andrew White
Importance: Low

Activity

  1. Denis Odorcic
    Denis Odorcic

    This was fixed with commit 6c95e0f8 in master. I guess it just wasn't pushed to 3-0-stable?

    October 28th, 2010 @ 04:17 AM

  2. Andrew White
    Andrew White
    • State changed from new to needs-more-info
    • Assigned user changed from Ryan Bigg to Andrew White
    • Importance changed from to Low

    Nader, can you confirm in what context you are using url_for? In a view the :only_path option is passed to url_for so the fact that url_for is calling polymorphic_url shouldn't make a difference. In fact polymorphic_path calls polymorphic_url itself.

    Denis, I can't see anything in that commit that would change this behaviour - how are you comparing things?

    October 28th, 2010 @ 08:09 AM

  3. Denis Odorcic
    Denis Odorcic

    You're right, I misread something. Woops ;)

    October 28th, 2010 @ 03:13 PM

  4. Nader Akhnoukh
    Nader Akhnoukh

    I'm using link_to @user from a view.

    The code path goes like this:
    -routing/url_for.rb:url_for (options is just the user record) # i think this is the bug, shouldn't the call to polymorphic_url be polymorphic_path (so that the default is _path) -routing/polymorphic_routes.rb:polymorphic_url which calls.. -routing/polymorphic_routes.rb:build_named_route_call which calls.. -routing/polymorphic_routes.rb:routing_type which spits out user_url because there is no routing_type in options (routing_type gets set in polymorphic_path)

    October 28th, 2010 @ 04:30 PM

  5. Andrew White
    Andrew White

    It should be first going through url_helper.rb:

    http://github.com/rails/rails/blob/master/actionpack/lib/action_vie...

    Can you provide a backtrace?

    October 28th, 2010 @ 04:55 PM

  6. Nader Akhnoukh
    Nader Akhnoukh

    OK, I think I see what's happening. Here's the backtrace from where the routing type is set to url:

    --> #0 ActionDispatch::Routing::PolymorphicRoutes.routing_type(options#Hash) 
           at line /Library/Ruby/Gems/1.8/gems/actionpack-3.0.0/lib/action_dispatch/routing/polymorphic_routes.rb:147
        #1 ActionDispatch::Routing::PolymorphicRoutes.build_named_route_call(records#User,...) 
           at line /Library/Ruby/Gems/1.8/gems/actionpack-3.0.0/lib/action_dispatch/routing/polymorphic_routes.rb:173
        #2 ActionDispatch::Routing::PolymorphicRoutes.polymorphic_url(record_or_hash_or_array#User,...) 
           at line /Library/Ruby/Gems/1.8/gems/actionpack-3.0.0/lib/action_dispatch/routing/polymorphic_routes.rb:108
        #3 ActionDispatch::Routing::UrlFor.url_for(options#User) 
           at line /Library/Ruby/Gems/1.8/gems/actionpack-3.0.0/lib/action_dispatch/routing/url_for.rb:135
        #4 ApplicationController.url_for(options#User) 
           at line /Users/nader/repo/napa/app/controllers/application_controller.rb:122
        #5 Kernel.send(args#Array, blk#NilClass) 
           at line /Library/Ruby/Gems/1.8/gems/actionpack-3.0.0/lib/abstract_controller/helpers.rb:55
        #6 #<Module:0x1071fcb08>.url_for 
           at line /Library/Ruby/Gems/1.8/gems/actionpack-3.0.0/lib/abstract_controller/helpers.rb:55
        #7 ActionView::Helpers::UrlHelper.link_to 
           at line /Library/Ruby/Gems/1.8/gems/actionpack-3.0.0/lib/action_view/helpers/url_helper.rb:236
        #8 ActionView::Helpers::UrlHelper.link_to 
           at line /Library/Ruby/Gems/1.8/gems/actionpack-3.0.0/lib/action_view/helpers/url_helper.rb:229
        #9 ActionView::CompiledTemplates._app_views_menus__login_items_html_haml___947485747_2208807400_3573310(local_assigns#Hash,...) 
           at line /Users/nader/repo/napa/app/views/menus/_login_items.html.haml:5
    

    As you can see in Step 4 I have a url_for defined in my application_controller. It's not doing anything besides calling super though (it was, but i commented it out to test):

    def url_for(options = nil)
      super
    end
    

    It looks like my call to super is going to the url_for in routing/url_for.rb instead of the url_for in helpers/url_helper right?

    October 28th, 2010 @ 05:39 PM

  7. Nader Akhnoukh
    Nader Akhnoukh

    Also, the reason this is a big issue for me is because my clients use CNAMEs to get to their site. I have a piece of rack middleware that looks up the CNAME and sets the request headers to the proper subdomain so the app knows what to do. With the above issue my urls get written out using the subdomain instead of the CNAME and the users are asked to reauthenticate as the session cookie isn't shared across different domains

    October 28th, 2010 @ 08:36 PM

  8. Andrew White
    Andrew White
    • State changed from needs-more-info to invalid

    Actually it shouldn't be hitting the url_for in ApplicationController - it doesn't in a test app. In fact the only way I can get it to go to the url_for in ApplicationController is to use helper_method :url_for in ApplicationController, e.g:

    class ApplicationController < ActionController::Base
      helper_method :url_for
    
      def url_for(options = nil)
        super
      end
    end
    

    Doing this reproduces your error. Is this what you're doing? If so all I can say is don't do this. If you want to override the :host option you want to be looking at default_url_options and/or url_options. A simple before_filter in ApplicationController that sets :host in default_url_options should be all that you need.

    October 29th, 2010 @ 06:26 AM

  9. Nader Akhnoukh
    Nader Akhnoukh

    Wow, that was some impressive sleuthing. That's exactly what I was doing. Thanks for the huge help.

    October 29th, 2010 @ 07:27 AM

  10. Nader Akhnoukh
    Nader Akhnoukh

    Andrew, the only thing I can't figure out is if I'm setting this in a before_filter I don't have access to the options hash. I am passing in a subdomain field with the options and based on that want to set the host. Thus overriding url_for. What's the better way to do that?

    October 29th, 2010 @ 09:30 AM

  11. Andrew White
    Andrew White

    This should work:

    class ApplicationController < ActionController::Base
      before_filter :set_default_host
    
      protected
        def set_default_host
          default_url_options[:host] = subdomain_to_host(request.subdomain)
        end
    end
    

    The subdomain_to_host method is however you map the subdomain to the CNAME.

    October 29th, 2010 @ 09:46 AM

  12. Nader Akhnoukh
    Nader Akhnoukh

    Hi Andrew, sorry I wasn't clear.

    I want to set the host based on some application logic. So this was my old method:

    def url_for(options = nil)
      if options.kind_of?(Hash) && options.has_key?(:subdomain)
        options[:host] = with_subdomain(options.delete(:subdomain))
      end
    
      super
    end
    

    and in my view:

    root_url(:subdomain => "xyz")
    

    How would you recommend doing that? I suppose I could get rid of the convenience "subdomain" key and just set the host directly?

    October 29th, 2010 @ 03:47 PM

  13. Andrew White
    Andrew White

    Firstly, why do you need to use root_url and not root_path - that way you don't have to worry about the host name. If you do need the full url then if you set the host in the controller before filter you don't need to pass in the subdomain key.

    October 29th, 2010 @ 03:55 PM

  14. Nader Akhnoukh
    Nader Akhnoukh

    Let say that for example I'm at my root app domain mydomain.com. And each of my users has their own subdomain. So in the view they each have links like root_url(:subdomain => user.subdomain) which should link to user1.mydomain.com and I also use root_url(:subdomain => false) to take people back to the main domain

    So in this case don't I need root_url vs root_path as the host matters?

    In this case how would I set the host in the before_filter? How would the before_filter have access to which subdomain to set as the host? Pass it as a request param or something?

    October 29th, 2010 @ 04:26 PM

  15. Andrew White
    Andrew White

    So why not set the :host directly?

      root_url :host => user.host
    

    October 29th, 2010 @ 05:26 PM

  16. Nader Akhnoukh
    Nader Akhnoukh

    yep, i think that might be the best solution. seems like messing with url_for just leads to trouble. thanks for all your help. just out of curiosity, do you know why there are two different url_fors?

    October 29th, 2010 @ 05:33 PM