How to make Rails routing case-insensitive

Update 2013-01-06: I have now converted this into a gem: Read more

Update 2011-03-10: Brian Marquis pointed out, that I’ve missed showing a good way to actually load the .rb file containing the middleware. This has now been added to the article.

Update 2010-11-06: Rails 3 has changed its routing mechanism. One of the changes involves which environment variable is used as the source for routing. In Rails 2.3.x it was REQUEST_URI, in Rails 3 it is PATH_INFO. I have changed the middleware code to take care of both versions.

At our dog-site www.gipote.dk we have a shop selling dog-tags. The URL for this shop is www.gipote.dk/hundetegn (hundetegn = dog-tag in danish). However one of our marketing-guys have a tendency to capitalize the word “hundetegn”, so that URL reads www.gipote.dk/Hundetegn (with a capital H).

Rails will yield a 404 NOT FOUND on this, simply because it is case sensitive. The Rails core team doesn’t seem to want this changed, but I found a neat way of doing it myself: Using the new Rack Middleware framework.

First the solution:

Create a file called downcase_route_middleware.rb and put it in RAILS_ROOT/lib or wherever you think middleware files ought to go. Fill it with this piece of code:

class DowncaseRouteMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    # Rails 2.3.x routing use REQUEST_URI
    uri_items = env['REQUEST_URI'].split('?')
    uri_items[0].downcase!
    env['REQUEST_URI'] = uri_items.join('?')

    # Rails 3.x routing use PATH_INFO
    env['PATH_INFO'] = env['PATH_INFO'].downcase

    @app.call(env)
  end
end

Now to telling Rails to use this new middleware class. This differs a bit in Rails 2 and 3.

Adding the middleware in Rails 3.x:

Open config/application.rb and find the lines

module YourAppName
  class Application < Rails::Application
    ..bunch of code..
  end
end

Add the middleware code like this:

module YourAppName
  class Application < Rails::Application
    config.autoload_paths << "#{config.root}/app/middleware"
    config.middleware.use "DowncaseRouteMiddleware"

    ..bunch of code..
  end
end

Adding the middleware in Rails 2.3.x:

Open up config/environment.rb, and find the line

Rails::Initializer.run do |config|

  ...a lot of config code...
end

Add the middleware code, so it looks like this afterwards

Rails::Initializer.run do |config|
  config.autoload_paths << "#{config.root}/app/middleware"
  config.middleware.use "DowncaseRouteMiddleware"

  ...a lot of config code...
end

Update: The line:
config.autoload_paths << "#{config.root}/app/middleware"
is my general way of using middleware. I always places all my middleware classes in a folder called app/middleware. The line above enables Rails to automatically search the autoload-path for a file that matches the middleware class used (i.e. "downcase_route_middleware.rb").

If this is the only middleware class that you use, you can simply do a
require "downcase_route_middleware.rb""
somewhere in your config initializers. I just find the above solution more elegant.

Restart your rails application, and it will now accept all kinds of casing on the routing part.

Note: This code only downcases the part of the URI containing namespace, controller, action, and id. It does not touch the querystring parameters, and for a good reason too: The parameter values could contain some context in their casing.

Now for the explanation:

Rails 2.3 introduced Rack middleware. A lot of people have already explained this concept, so instead of going into details, I suggest that you read Pratik's explanation on Rails Guides and Ryan's Railscast on the subject.

Basically, my middleware class gets access to the environment hash and passes it on to the next middleware class on the stack. In between I get to modify the nessecary data. In this case it's the REQUEST_URI (In Rails 3 it's PATH_INFO), since ActionController::Dispatcher uses the URI from here to determine the correct route. By converting the URI to lowercase, ActionController::Dispatcher gets the correct path no matter the case entered by the user.

Bookmark and Share

14 tanker om "How to make Rails routing case-insensitive"

  1. Carsten Forfatter

    Good question Kate. But do you actually get a deprecation warning on the above? The reason I ask is, that as far as I know, “env” is still the way to pass information around in the middleware stack. So I think that env['REQUEST_URI'] should still be valid – as long as you’re only using it in the middleware.

    I will look more into it, and let you know here, if this is not the case.

    - Carsten

  2. kate

    It actually just isn’t working. I moved – config.middleware.use “DowncaseRouteMiddleware” – into my config/application.rb file, and am autoloading the path to my “lib” files, but uppercase and lowercase urls are just bringing me to completely different views

  3. Carsten Forfatter

    I’ve just started to migrate an application to Rails 3, so I will try to find a solution. I’ll let you know when I’ve got it.

    -Carsten

  4. kate

    Awesome, thanks. This is the best solution I’ve found out there for the downcasing-routes issue.

  5. Carsten Forfatter

    Kate: Good news, I finally found the solition. It turned out, that Rails 3 changed its router to use the environment variable PATH_INFO instead of REQUEST_URI. Go figure.

    Anyway, I’ve updated the examples above to take care of both cases, as well as shown the correct way to add the middleware in Rails 3 (which is also the way you are doing it).

    I hope this solves your problem. Thanks for pointing it out. :-)

    - Carsten

  6. kate

    Thanks!!!!!
    I’d read about PATH_INFO and had just tried replacing REQUEST_URI in your code with that, but didn’t think to try it this way.
    So glad I don’t have to go chasing after another way to downcase routes…

  7. Brian Marquis

    I’d like to make two comments on this:

    First, thanks for the middleware. I’ve inherited a Rails app and haven’t worked with the routing mechanism much, having a personal preference to stick to rails default controller/action/id mechanism. This new app I’m working on makes extensive use of routes, so I was looking for a quick solution to the case insensitive issue.

    Second, I noticed that your Initializer modifications don’t require ‘downcase_route_middleware’. This might seem a slight oversight, but for those of us yet unfamiliar with Rack, it was troublesome to find the problem.

    Thanks!

  8. Carsten Forfatter

    Hi Brian,

    Thank you for noticing the missing “require” part of my examples. Actually I don’t use an explicit require statement for this. I’ve placed all my middleware files in a dedicated folder, and then added this folder to Rails’ autoload_path. I’ve updated the article with this solution, but also explained how to do the simple “require” stuff.

    I am glad that this has helped you. I actually chose middleware to solving the casing issue for exactly the same reason, that you mention: I did not want my routing code or any other part of the rails application to have special hacks, for case-difference to work.

    - Carste

  9. Kevin

    hi,

    Incase you got “undefined method autoload_paths”, though you’re using Rails 2.3.x just like me.
    I find a way to solve it by replace require ‘downcase_route_middleware.rb’

    with this one:

    require File.join(File.dirname(__FILE__), ‘../app/middleware/downcase_route_middleware.rb’)

    *I placed downcase_route_middleware.rb on app/middleware folder.

  10. Carsten Forfatter

    Yes good point. Although I tend to use Rails.root or RAILS_ROOT (depending on Rails version) instead for File.dirname(__FILE__)

    / Carsten

  11. David Weiss

    For some reason, the server wouldn’t start with the next line:
    config.autoload_paths << "#{config.root}/app/middleware"
    but I replaced it with: require_relative '../app/middleware/DowncaseRouteMiddleware',
    and it worked fine. Thanks.

Skriv et svar

Din e-mail-adresse vil ikke blive offentliggjort. Krævede felter er markeret med *

Disse HTML koder og attributter er tilladte: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>