Thursday, September 23, 2010

Easy Way Out of The STI Hell

I like the idea of STI (single table inheritance), it is cheap, dodgy but it works and allows you to play with all sorts of sub-types in a civilized way. For example you can redefine things in subclasses, use strategies and so one.

But when you try STI with Rails, you immediately fall into the polymorphic routes hell. Because you have new types, Rails tries to find routes for them, and it doesn't want to use the base model as a fallback. And here, people start to do all kinds of things (I did), define controller level helpers, generators, modules and so one. But then, someone adds a new custom method or a new subtype and it all falls down like a house of cards.

Well, you know what they say, the exit from a hell lays at the very bottom of it. And in Rails it will be routes. Say I want to handle all sorts of User model subclasses, like User::Admin, User::Manager, User::Blocked.

Trololo::Application.routes.draw do
# here I collect all the User subtypes
user_types = Dir["#{Rails.root}/app/models/user/*.rb"].map do |name|
"user_" + File.basename(name).gsub('.rb', '').pluralize
end

# and then I put all of them one by one
(['users'] + user_types).each do |name|
resources name, :controller => 'users', :path => '/users' do
member do
get 'stats'
end

resources :comments # we can define nested resources too
end
end
end

I use the Rails 3 notation in this case, but I suppose you can figure how to make it work under Rails 2 as well.

It is still a bit dodgy, but it is better and more stable than defining those type-specific routes manually in controllers, you also can automatically handle all the nested routes as well.

That's it, hope that will help

No comments: