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 doesn't like HashWithIndifferentAccess

#4391

url_for doesn't seem to like ActiveSupport::HashWithIndifferentAccess. When used in a view via, say, link_to, the URL produced doesn't seem to be correct, or at least what you'd expect. This is noticeable when trying to replicate the old overwrite_params functionality by using the params Hash and Hash#merge, as params is a HWIA.

For instance, given a set of params where :controller is 'hello' and :action is 'world', then using Hash#merge to overwrite these like so:

url_for(params.merge(:controller => 'foo', :action => 'bar'))

You end up with a URL that looks like

http://example.com/hello/world?action=bar&controller=foo

whereas you'd probably be expecting

http://example.com/foo/bar

The following however produces the desired results:

url_for(params.merge(:controller => 'foo', :action => 'bar').to_hash.symbolize_keys)

Stringifying the keys does not produce the desired effect, however.

The attached patch appears to work and doesn't break any existing tests in ActionPack, but being as I'm not really familiar with the Rails 3 code yet compared to 2.x I hope that someone could run with it and create a proper patch, assuming that this is indeed a bug...

Reported by jay · April 14th, 2010 @ 06:38 AM

State: committed
Milestone: 3.0.2
Assigned to: José Valim José Valim
Importance: Low

Activity

  1. Francesc Esplugas
    Francesc Esplugas

    +1

    I'm currently using symbolize_keys to skip this problem.

    April 16th, 2010 @ 08:21 AM

  2. Lenary
    Lenary

    Protip: you're going to need some tests to show exactly what it fixes.

    But other than that, it does indeed work and fix a few other bugs that people have been experiencing - like the following: #4419 current_page? in a partial template does not work.

    Add a few test cases and then it'll get accepted.

    April 16th, 2010 @ 10:47 PM

  3. Lenary
    Lenary
    • Tag set to hashwithindifferentaccess, url_for

    April 16th, 2010 @ 11:35 PM

  4. Lenary
    Lenary

    I'll take more of a look at this tomorrow, and can guide you a little if you want it. Sorry for being condescending earlier.

    April 17th, 2010 @ 12:12 AM

  5. jay
    jay

    No prob. If there's any tests that need writing I can give it a shot... I just figured that this patch, although working, probably wasn't the best way of doing things as it seems to duplicate code by calling doing to_hash.symbolize_keys in two different places. I was just kind of hoping that someone more familiar with the code would know of a better fix, but if this patch is acceptable then all the better. I can write a few tests if it will help, although I don't really know how extensive the coverage should be if this patch affects other tickets.

    April 21st, 2010 @ 12:26 AM

  6. Ryan Bigg
    Ryan Bigg

    Lenary, did you take a look at this ticket yet? What did you think of it?

    April 21st, 2010 @ 01:40 AM

  7. Neeraj Singh
    Neeraj Singh

    It was not clear to me if this problem is only in rails3 branch or in 2-3-stable branch too. It turns out that 2-3-stable branch works only. This is an issue only with rails3 branch. Adding this comment so that it to clear to others who look at this ticket.

    Someone should put right tag. There are so many of them related to rails3 that I am not sure which one I should apply.

    April 21st, 2010 @ 04:11 AM

  8. Ryan Bigg
    Ryan Bigg
    • Tag changed from hashwithindifferentaccess, url_for to hashwithindifferentaccess, rails3, url_for

    April 21st, 2010 @ 04:28 AM

  9. Santiago Pastorino
    Santiago Pastorino
    • Milestone cleared.
    • Tag changed from hashwithindifferentaccess, rails3, url_for to hashwithindifferentaccess, patch, url_for
    • State changed from new to verified
    • Assigned user set to Carl Lerche

    Patch with tests

    April 21st, 2010 @ 05:35 AM

  10. Lenary
    Lenary

    This patch still doesn't feel right IMHO. using hashwithindifferent access should mean that we don't get these issues and don't have to call to_hash.symbolize_keys on the hash to get this to work. I'm going to have a much closer look later today

    Can we not apply this patch until then. This to me kind-of feels like were barking up the wrong tree, but I need time to look at it properly.

    April 21st, 2010 @ 06:41 AM

  11. jay
    jay
    • Assigned user changed from Carl Lerche to Cezary Baginski

    A couple of things I've noticed:

    • Using Strings as options keys to url_for seems to be hit and miss. For instance, if I put the following in a view:
    <%= url_for('controller' => 'c', 'action' => 'a' %>
    

    Produces '/c/a' as expected. However, doing the same thing in a controller produces http://example.com/?action=a&amp;controller=c.

    • The following results in an ActionController::RoutingError exception when done in an irb console:
    irb(main):001:0> app.url_for('controller' => 'c', 'action' => 'a')
    ActionController::RoutingError: No route matches {"action"=>"a", "controller"=>"c"}
    

    This while using a very simple routes.rb set up that should produce a valid route:

    TestApp::Application.routes.draw do
      match ':controller(/:action(/:id(.:format)))' => '#index'
    end
    

    The same route works for the example in the view, however.

    • In all cases, using Symbols instead of Strings for keys produces the expected results.

    • HashWithIndifferentAccess appears to always output its keys as Strings, i.e. when using the hash as an Enumerable or calling #keys or whatever.

    So it appears the problem isn't necessarily with HWIA, but rather with using Strings as keys versus Symbols, at least in some cases...?

    April 21st, 2010 @ 07:13 AM

  12. jay
    jay
    • Assigned user changed from Cezary Baginski to Carl Lerche

    Oh my, sorry for reassigning the person responsible, that was a slip of the mouse apparently...

    April 21st, 2010 @ 07:13 AM

  13. Lenary
    Lenary

    Jay, thanks for all the legwork. I'll take a proper look over what's happening and propose a few solutions in this thread later today.

    April 21st, 2010 @ 07:21 AM

  14. Santiago Pastorino
    Santiago Pastorino

    I think this is a better way to solve it.
    symbolize_keys on hash with indifferent access for me should return a hash with symbolized keys.

    Deeper on the framework things doesn't expect a HWIA so we need to convert on the helper and that's what symbolize_keys should do

    April 21st, 2010 @ 03:59 PM

  15. jay
    jay

    Yeah, this appears to be the behaviour in 2.3. In 2.3, calling symbolize_keys or stringify_keys returns an actual Hash with the appropriate keys, whereas in 3.0 they return a HWIA:

    # in 2.3:
    >> HashWithIndifferentAccess.new({ :one => :two }).stringify_keys.class
    => Hash
    >> HashWithIndifferentAccess.new({ :one => :two }).symbolize_keys.class
    => Hash
    
    # in 3:
    irb(main):005:0> HashWithIndifferentAccess.new({ :one => :two }).stringify_keys.class
    => ActiveSupport::HashWithIndifferentAccess
    irb(main):006:0> HashWithIndifferentAccess.new({ :one => :two }).symbolize_keys.class
    => ActiveSupport::HashWithIndifferentAccess
    

    That being the case and if symbolize_keys is to return a Hash with symbolized keys, then I suppose stringify_keys should also return a Hash with stringified keys, yeah?

    April 21st, 2010 @ 04:18 PM

  16. Santiago Pastorino
    Santiago Pastorino

    I've added a test for the symbolize_keys method and re upload the patch.
    Now looks good.

    April 21st, 2010 @ 05:13 PM

  17. Santiago Pastorino
    Santiago Pastorino

    For me HashWithIndifferentAccess.new({ :one => :two }).stringify_keys.class should return ActiveSupport::HashWithIndifferentAccess
    but we need to do the same on both versions i think.

    April 21st, 2010 @ 05:26 PM

  18. José Valim
    José Valim
    • Assigned user changed from Carl Lerche to José Valim

    April 21st, 2010 @ 05:26 PM

  19. Lenary
    Lenary

    I am just about to take a good look at this now. Sorry i haven't got around to it earlier.

    April 21st, 2010 @ 08:25 PM

  20. Lenary
    Lenary

    I am no clearer on what is going on with this. I took a look at some raw output from url_for, and got some of the way with that, but then got to where you had all got to.

    I have now tried santiago's patch, and it hasn't really worked for me (still getting controller and action params in querystring when in controller). is the latest one ( https://rails.lighthouseapp.com/projects/8994/tickets/4391/a/491493... ) the entire patch, or do i have to apply more than that?

    April 21st, 2010 @ 10:54 PM

  21. Santiago Pastorino
  22. Repository
  23. jay
    jay

    The applied patch in http://github.com/rails/rails/commit/5c9c30ac65d25bd5a83d773739c2cd... is partially there, but url_for still fails with stringified keys in a controller.

    I think that ActionDispatch::Routing::Generator#normalize_options! and #normalize_controller_action_id! are be the culprits. They both assume symbolized keys when they do their options merging, and end up creating options Hashes that sort of duplicate their entries because of it. In the cases where HWIAs are used this merging obviously goes a lot smoother, but in the case of regular Hashes with stringified keys things start to go awry.

    For instance, in the controller given following:

    url_for('controller' => 'c', 'action' => 'a')
    

    This will pass a Hash with stringified keys to ActionDispatch::Routing::RouteSet.url_for. When normalize_options! gets a hold of it, it won't do much of anything because it specifically looks for symbolized keys and we aren't dealing with a HWIA at all.

    In my set up, I'm calling url_for from MainController#index. normalize_controller_action_id! tries to combine the original options Hash with @recall, which is now set to {:controller => 'main', :action => 'index'}, thus using symbolized keys. The use_recall_for method uses Symbols to try and fill in any missing parameters, but it ends up duplicating the parameters since we're not using a HWIA. The options Hash now looks like the following:

    {:controller=>"main", "action"=>"a", :action=>"index", "controller"=>"c"}
    

    This later causes all sorts of problems when other methods like handle_nil_action! try to delete entries from the options Hash because while they may delete :action they'll be leaving behind 'action'. Eventually these duplicate keys seem to cause problems later on down the line in rack-mount and the like.

    Part of the reason url_for is working with both stringified keys in views is because the keys are being symbolized long before they ever make it to normalize_options! and the like, whereas in ActionDispatch they are still being untouched, and thus you could slip HWIAs or Hashes with stringified keys in there. My original assumption that only HWIAs were causing the problem then is a little off -- it's actually more of a case of stringified keys.

    Anyways, that's what I've managed to figure out. So, that being the case, another quick patch seems to be needed -- the controller version of url_for needs to make sure that it's passing along a symbolized Hash.

    I've attached a patch with a couple of tests and it seems to be working now for me. Note that in this case I don't believe we'd need to call to_hash as we're actually calling symbolize_keys on url_options after the merging, and url_options is already a Hash.

    April 22nd, 2010 @ 02:46 AM

  24. Repository
  25. Santiago Pastorino
  26. Ryan Bigg
    Ryan Bigg
    • Tag cleared.

    Automatic cleanup of spam.

    October 9th, 2010 @ 09:54 PM

  27. Jeremy Kemper