One Wrong Way for Rails Multiple-model Url Aliases
In a couple of the projects that I’ve been working on in the last few months, I’ve wanted to have a set of aliases that I can’t really set up with the Rails routing DSL. The main problem is that I can’t route a path to two models based on a property of the model.
In order to have a cogent example, let’s say that I have a User model, and a Group model, each
of which have names, that I guarantee is unique based on validations. I want both of them
to be able to access their pages via the url http://host/(name)
.
The pseudocode that I really want in routing is:
if User.where(:name => :id) then
{:controller => UsersController, :action => :show, :id => :id}
elsif Group.where(:name => :id) then
{:controller => GroupsController, :action => :show, :id => :id}
else
404
end
This is, as far as I know, not possible with the Rails routes that are available in either Rails 2.3 or 3.
To complicate matters, I don’t want to redirect the URL becuase I want the browser to show the
http://host/(name)
as the canonical URL. Getting user_path(@user)
and group_path(@group)
to return this shortened url path would also be very nice.
I’ve so far solved this problem in two ways, both of which I’m unhappy with. The first is through routing everything through a controller where I can then check each of the models for existence of the correct property:
# config/routes.rb (at the end)
match ':id' => 'home#disambiguate'
# HomeController
def disambiguate
@user = User.where(:name => params[:id]).first
render(:template => 'users/show') and return if @user
@group = Group.where(:name => params[:id]).first
render(:template => 'groups/show') and return if @group
render(:template => 'common/404', :status => 404)
end
I’m pretty unhappy with this. For one, if I put anything into the actions
for User#show
or Group#show
(counting hits or something) then they need
to be also included in Home#disambiguate
. I haven’t had to do that so far.
Also the 404 is being rendered here, where I would really rather just have it
at the bottom of my rails route just like my other rails apps.
I’ve used another solution that I don’t like for this problem that solves these problems, but I’m still unhappy with. I’m thinking of a third solution, which I might code up and present later this week. Have you solved this problem in a different, better way? I would be interested to hear.