If you’ve perused the source of any moderately complicated Rails application you are likely to have come across the Single Table Inheritance (STI) pattern. STI allows you to have polymorphic models all stored in the same table or collection. It may be best to give you an example:
As you can see from the example, we have three models, Meat, Bacon and ChunkyBacon all using the “meats” table for storage. This is really great, especially if there is common model logic (attributes, validations, life-cycle callbacks, etc) needed for all three models, and extra logic on the child models (imagine ChunkyBacon has a season).
Kimono makes extensive use of STI behaviour throughout it’s model graph, although built upon the Mongoid ORM. We kept running into problems where we needed to have routes for every new subclass we created and it didn’t really fit the way we wanted the application to work. The problem is not actually the router (a simple map.resources :meats) works perfectly, but with the route helpers. A call to url_for or link_to when passed in a model instance would throw an exception because there were no routes defined for Bacon or ChunkyBacon. We started with overriding the helpers (link_to, et al) but soon discovered this didn’t work well enough because the assumptions Rails makes about routing your models go deep. How deep? To ActionDispatch::Routing::PolymorphicRoutes as it turns out.
In order to create the behaviour we wanted we needed to add a reusable method to climb up a model’s superclass chain checking if the parent is a Mongoid document until it reaches the top of the inheritance tree, then we needed to patch “build_named_route_call” to use this instead of just using the class of the model being routed. Enter lib/kimono/routing.rb:
Once that’s done, we simply add an initialiser to patch in the behaviour we want:
Now when we call url_for(@bacon) we get “/meats/1” back. Thanks Ruby.