My First Monkey Patch for a Rails routes.rb bug

For the past five days I have been struggling with a bug in the route code in rails 1.2.3. I finally monkey patched url_for to essentially force it to do the obvious.
In this blog post, I describe the bug and how I reproduced it and then the patch to work around the bug.
There is a 10% chance that the routes is actually working and I just do not understand it. I read in the Agile book something about routes trying to use the shortest URL where possible – but the problem is that I have these long URLs to pass bits of session-like information around between my ajax calls – particularly when I want to go cross domain with my Ajax (i.e. I want to use a Rails tool from one server included into a web page from another server where cookies and session information will not be usable).
The bug comes down to when I call something with a long url with lots of stuff defined and then I generate a new url adding an id which is always at the end of the routes, url_for decides to “forget” the stuff that was to the left of it in the incoming request URL. Changing action only generates the *right* url while changing the action and id generates the wrong URL.
My Urls have three basic forms
The first two are for use in divs and never shown to the user. The imsti one both establishes a session and sets up the context and div name when the tool is being used cross-domain. The second is used when you are not going cross-domain and cookies are available to establish session because the ajax and the enclosing container are coming from the same server – so incoming ajax requests get the cookies.

Here is my routes.rb – the three routes in question are the ones that freak Rails url generation out – even though I never use not generate Urls with the imsti prefix – other URLs are incorrectly generated as you will see in this short sad story.
map.connect ”, :controller => “portal”
# Problem causing routes
map.connect ‘imsti.:imssession.:context.:div/:controller/:action/:id.:format’
map.connect ‘imsti.:imssession.:context.:div/:controller/:action/:id’
map.connect ‘imsti.:imssession.:context.:div/:controller/:action’
map.connect ‘ajax.:context.:div/:controller/:action/:id.:format’
map.connect ‘ajax.:context.:div/:controller/:action/:id’
map.connect ‘ajax.:context.:div/:controller/:action’
map.connect ‘:controller/:action/:id.:format’
map.connect ‘:controller/:action/:id’
map.connect ‘:controller/:action’
The bug happens when I go to a page with a Url like this
And then in the view for this page I do this:
<%= link_to_remote ‘view’, :update=> portal_get_div,
:url => { :action => ‘view’, :id => } %>
Simple enough – I am changing the action and adding an ID – it should inherit the rest since in all my routes id and action are at the end.
Here is the problem – when I do not have the three imsti routes in my routes.rb all works well – the link_to_remote generates this url:
If I add the three imsti prefixed routes to my routes.rb the same code when called from the same url:
Makes this URL:
Aargh! And on the same page in the same view, this code:
<%= link_to_remote ‘Manage’, :update=> portal_get_div,
:url => { :action => ‘manage’ } %>
Makes beautiful URLs like this regardless of what version of the routes.rb I use:
So what the heck is happening – it works for action and fails when you do action+id – clearly the addition of an :id is triggering some deep stupid behavior in the url_for.
A key here is that I am never calling nor using a route that starts with imsti – the mere adding of the routes causes things to break.
I have played for hours and at one point removed my three routes and had a single route that would cause the evil behavior. I lost the exact single route that broke it – but it looked something like this:
map.connect ‘routesbiteme/:controller/:action’
It had an action but no id – and yet it caused url generation to blow its brains out. I need to look at the Rails source code. One day I will download my own copy and start hacking on Rails – but I will save that for when I have more time.
So here is the monkey patch to fix it:

module ApplicationHelper
include Osids
include Portal
# This is inspired by another monkey patch for support in browsers
# without cookies - but since cookie_only >= false seems broken
# that patch while useful to read is not much help
# Funny - sometimes options is a string - in this view code
# <% form_remote_for :umap, @user, :update=> portal_get_div,
#            :url => { :action => "add" } do |f| %>
# options comes in as a string like this:
#   /ajax.1.main-content/users/add
# I am sure this is yet another bug.  But we need to simply
# not patch the Hash unless it is really a hash
def url_for(options = {}, *parameters_for_method_reference)
if options.class == Hash
if options[:div] == nil && params[:div] != nil
options[:div] = params[:div]
if options[:context] == nil && params[:context] != nil
options[:context] = params[:context]
if options[:imssession] == nil && params[:imssession] != nil
options[:imssession] = params[:imssession]

Effectively if the parameters are on the request, and not in options, I force them into options. Somehow this convinces the recalcitrant url generation to really see the parameters.
I wasted about 15 hours on this one – I guess I learned something from it. I cannot wait to see the ugly code in url generation or secret undocumented back-door feature that broke this.
P.S. I am still struggling with how to set the Rails session id from a URL – my other reason to be peeved at the Rails folks – don’t get me started on cookie_only and Rails developers rushing to put in security fixes without thinking about real impact. For now I have a hack to do IMS TI sessions in my own framework without using Rails sessions – to get on to cross-domain Ajax testing.