Single Table Inheritance and RESTful Routes
March 17, 2009I'm converting an old, controller/action/id style Rails application to a more RESTful way of doing things, and ran into a brief roadblock: one of my main tables uses single table inheritance to generate three subclasses of items. I never actually use the superclass "task", I only use the three subclasses "action item", "work order", and "problem report".
So, I ran into this little challenge: all three STI subclasses use the same controller, "tasks", because they all have essentially the same behavior and differ only in minor details. But, when I do a resources map:
map.resources :tasks
Then I get errors in much of my code when I say things like redirect_to @task, because if that task happens to be an ActionItem, it's trying to call action_item_path(@task), which doesn't exist.
I googled around a bit to no result. Striking out on my own, it turns out the answer is as simple as mapping each resource independently, and just overriding the controller in map.resources:
In config/routes.rb
map.resources :tasks
map.resources :action_items, :controller => 'tasks'
map.resources :work_orders, :controller => 'tasks'
map.resources :problem_reports, :controller => 'tasks'
Now, redirect_to @task works just fine regardless of which subclass @task happens to be.
I was having this exact same problem with routing for a single-table inheritance-based hierarchy of models and this worked like a charm.
Thanks!
Josh: glad to have helped.
Brilliant. Great post, that was really bugging me. Thanks Evan.
Great post! Was about to start pulling my hair. Thanks Evan.
Thanks for this one. Exact same situation for me. Knew there had to be something better than what I was originally up to.
One more question, – how to distinguish actionitems from others (work_orders, problems_reports) in your controller (new, create, edit, update) and how to create the views? Should I create a seperate folder for every subclass with coresponding views (new, edit, etc.) ?Thanks.
sorry for the typo in my mail address
Javix: The primary reason I do things this way is because, for most purposes, I don’t want to distinguish the different types of tasks in the controller. They’re nearly identical and I want to use the same code. The whole point of the exercise is that ActionItems, WorkOrders etc. all use the same controller action and views so I don’t have to write them twice.
For the few things that need to be different — for example, ProblemReports have one fewer column in the table view than AIs or WOs — I can have my views or controllers check @task.type. For example, in app/views/tasks/show.html.erb I might have this line:
< %- unless @task.type == 'problem_report' %>
< % end %>
Another thank you – I ran into the same situation converting an old app, and you saved me a lot of time!
Thank you Evan your solution is very helpful for me!
Thank you, this saved me some time.
I achieved this globally by overriding the following :
module ActionController
module RecordIdentifier
private
def model_name_from_record_or_class(record_or_class)
(record_or_class.is_a?(Class) ? record_or_class.base_class : record_or_class.class.base_class).model_name
end
end
end
The same can be achieved only for specific STI cases with :
@subclassRecord.becomes(BaseClassName)
This saves having extra routes to trawl through and so forth.
Wish i found this post about two hours ago, thanks!
[...] the blog post Single Table Inheritance and RESTful Routes at Logical Reality Design, I see the method that Alex Reisner advised against, viz. adding [...]
Add A Comment