Shorturl_routes: Rails Multiple-model Url Aliases
Earlier this week I wrote about my frustration with setting up short urls for rails routing to two models based on attributes, something that I have seen on a bunch of websites, but haven’t found a good solution to in rails. Let me describe it in pseudo-cucumber:
As a site user
In order to have easily-rememberable, and easier twitterable URLs
I want to have my user and group models accessible by /name
Scenario: Users can access by /name
Given there is a User with name "foo"
When I visit the page '/foo'
Then I should be on the page for the User with name "foo"
Scenario: Groups can access by /name
Given there is a Group with name "bar"
When I visit the page '/bar'
Then I should be on the page for the Group with name "bar"
In addition to the two scenarios above, I would like all of my other routes to continue to work as expected, with the priority as normal falling from the top of the routes to the bottom.
My previous solution externalized the disambiguation of these routes which are dependent on the models in the database to a controller. I really wanted this to be in the routes, because that is where I feel like it naturally should be.
So I’m introducing today a solution. shorturl_routes is a gem which will give me what I want. Here’s the routes that will accomplish the functionality that I explained before.
SampleApp::Application.routes.draw do
resources :groups
resources :users
shorturl ':name', :to => 'users#show', :model => "User", :attribute => :name
shorturl ':name', :to => 'groups#show', :model => "Group", :attribute => :name
end
It’s that simple. With the routes above, if you visit the location
/foo, then it is checked against both resources first, but since it’s not
/groups or /users or any of their generated routes, it falls to the shorturl
rule, which checks User.where(:name => params[:name]).first
for existence,
and if it doesn’t exist, then falls to the next rule which checks for
Group.where(:name => params[:name]).first
. If either matches, it sets
params[:id]
to the id of the model found, and then routes to the given
parameter.
It’s more what I was looking for. I’ve packaged it up as a gem for your convenience, so you can just add this to your Gemfile:
gem 'shorturl_routes', :git => git://github.com/jamuraa/shorturl_routes.git
You might notice the distinct lack of working tests in the gem if you have a chance to try to run them. The method that I’m using for making it work in the Routing model works for the real thing, but I can’t figure out how to make it work in the tests. For now, the functionality is simple enough to test by myself.
It also doesn’t solve the inverse problem - user_path(@user)
will still
resolve to /users/:id
. I’ll be looking at improving the DSL syntax a
bit and tackling the no tests and this problem as well in the near future.
Let me know if you find it useful, or want to ream me for bastardizing the routing infrastructure.