Rails Routes with String IDs and Periods

Well, this has been a pain. I have a case where I need to have the ID of a resource be a string, not an integer. Rails routes don’t like that — if you search a bit, you’ll find a number of arguments about the practice, with the Rails core folks coming down on the side of integer-only IDs. Shame that, and people have come up with various workarounds. For me, I ended up doing this:

  map.resources :workers
  map.inventor 'worker/:id', :controller => 'workers', :action => 'show'

  # In the view, I can then do this, which isn't pretty but is temporary, okay?
  link_to worker_name, worker_path("#{CGI::escape(worker_name)}")

I have my nice RESTful routes created for workers, which is nice for the usual index, update, destroy, etc which I can do using IDs most of the time. But for show, I need to be able to pass in a name rather than an integer. That’s where the unfortunate additional named route comes in, so I can have a URL like /worker/your_name_here. It’s human-readable and SEO-friendly, plus for various reasons I’m working with String keys instead of integers in this case (using Redis as the data store).

So that worked…except that I discovered some cases where the strings contained periods. Which is another Rails no-no! Sigh. So, I could muck around with additional special cases in my routes file, which is not nice. Thankfully, though, my searching stumbled on a couple of bugs filed in the Rails core, most particularly this one, which provided a workaround that’s not too awful. I changed my route to the following:

  map.inventor 'worker/:id', :requirements => { :id => /.*/ }, :controller => 'workers', :action => 'show'

Et voila, there we go. Adding the requirements parameter instructs Rails to allow periods, and it works. But I still hate routes.

Advertisements
7 comments
    • mu said:

      Well, the URL specification says that periods are perfectly acceptable in URLs and that they have no special meaning so there is no room here for belief. If you need a specific format then say “?format=json” and use an HTTP Content-Type header on the return.

      Asking for information on a user with something like “/info/x@example.com” or “/info/miss.piggy” is a perfectly natural thing to do and Rails makes it more work and a hell of a lot more confusing than it should be.

      The rails developer that decided to give “.” a special meaning deserves a smack upside the head for having more attitude than sense. Rails routing should follow the principle of least surprise and if you want “.” to be magical then you should have to explicitly say so in the route.

      And thanks masonoise for giving me a simple work around for some Rails brain damage.

  1. Well, keep in mind that normal URLs always have periods in them, a la “/something.html”! But of course, you’re generally correct, and it’s a pain. My main concern isn’t SEO actually, but simply a way to describe a resource using a name, rather than an ID. It’s an unusual situation that I hope to work around in a more sensible way, but it remains to be seen if that can be done. In that case, using to_param() can work, but it breaks the properly RESTful routing. Which may be the best way to go, as it turns out.

  2. JM said:

    Thanks for this. This got me pointed in the right direction.

    If someone finds this post and needs it to work for Rails 3…. instead of :requirements try :constraints… so for me it was:

    match ‘/this/that/:email/:thing’ => ‘thiscontroller#thisaction’, :constraints =>{:email =>/.*/}

    Thanks again

  3. Brian Hempel said:

    Thank you for this fix, it worked like a charm! Our app is providing a web front-end to another service, and some of that service’s id’s had periods so this was necessary.

    On Rails 3, it’s :constraints instead of :requirements, and it even works with resources.

    resources :things, :constraints => { :id => /.*/ }

  4. gladtocode said:

    This was helpful to me. Thanks!

  5. david said:

    Thanks..
    match ‘tokens/:id’ => ‘tokens#show’, :constraints =>{:id =>/.*/}
    Worked perfectly. Now I can parse the url as I like, as I need.. Most helpful

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: