Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #10454 (new enhancement)

Opened 7 months ago

Last modified 3 months ago

STI - Routing uses subclass for URL generation

Reported by: clemensk Assigned to: core
Priority: normal Milestone: 2.x
Component: ActionPack Version: 2.0.1
Severity: minor Keywords: sti routing
Cc: findchris, lukemelia

Description

I have a Project resource and 5 types subclassing it via Single Table Inheritance. When I'm using the new URL generation in Rails 2.0 (i.e. <%= link_to project.name, project %> instead of <%= link_to project.name, project_path(project) %> for a SpecialProject (class SpecialProject < Project), Rails looks for special_project_path - and can't find it, obviously.

I think there are two ways to approach this problem: 1) map.resources :projects should also create routes for subclassed resources. 2) Polymorphic routing should be modified to use only a direct subclass of ActiveRecord::Base by default.

I have been thinking about whether or not this is a bug - and I think it is. I've only used STI a couple of times but when I did, I never defined controllers for every subclass but rather used ProjectsController to manage all types. This rules out option 1) mentioned above because special_project_path would map to SpecialProjectsController.

Option 2) seems to fit - if people are like me, most of the time they will use only one controller for subclassed resources. And if they don't, they still can define routes for their resources and write special_project_path(project) when needed.

I did a quick fix in the ActionController's polymorphic_routes.rb and replaced line 59 (base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", records.pop)}_" with the following code:

record = records.pop.class
        
if record.superclass == ActiveRecord::Base
  base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", record)}_"
else
  base_segment = "#{RecordIdentifier.send!("#{inflection}_class_name", record.superclass)}_"
end

This is a quick and dirty fix because I needed it - I'm not a Ruby geek (yet), so probably someone else could do a better job fixing it permanently.

Change History

12/10/07 18:38:00 changed by clemensk

And yes, I know that the quick fix only goes up one step in the class hierarchy and not to the direct subclass of ActiveRecord::Base - as I said, it's just a quick fix.

03/05/08 08:25:13 changed by findchris

+1 - A big plus one from me as I happened upon this ticket while trying to figure out this exact issue. I can verify that the following code will fail to generate a valid route ("undefined method for 'sub_user_url'" on 1.2.6):

In sub_user.rb:
class SubUser < User

In _form.rhtml (if @user is a SubUser):
<% form_for @user, :url => users_path do |f| -%>

Thanks, Chris

03/05/08 08:42:00 changed by findchris

  • cc set to findchris.

(in reply to: ↑ description ) 03/21/08 20:21:21 changed by wdutcher

Same issue. Rather than changing polymorphic_routes.rb, I was able to work around the problem by (1) explicitly identifying the controller in the :url, and (2) setting a variable in the controller to be equal to the params[:subclass] passed from the view. Using the example provided by Chris,

In _form.html.erb: <% form_for @user, :url => {:controller => :users, :action => :update} do |f| %>

In users.controller.rb:

attributes = (params[:sub_user] params[:sub_user2] params[:sub_user3]) @user.update_attributes(attributes)

Note - I didn't delve into this any further, but in my case I had to specify :action as well (which would preclude, for example, using the _form.html.erb for both 'edit' and 'new' purposes). Not sure if this is generally true as the specific STI scenario I was dealing with was complicated by as has_many relationship.

04/13/08 21:32:47 changed by lukemelia

  • cc changed from findchris to findchris, lukemelia.

I'm working on a patch that would make polymorphic url check for routes up the inheritance chain if a route for the class of the passed in record doesn't exist.

In the example given by the ticket opener:

<%= link_to project.name, project %>

when project is a SpecialProject (subclass of Project) would result in special_project_path if such a route exists, or fall through to project_path if that route exists. Patch forthcoming, but feedback welcome in the meantime.

04/13/08 22:57:23 changed by lukemelia