STI Problems

Hi,   I've been trying to figure out STI and I'm having some trouble getting things to work smoothly. I can generate the STI hierarchy with no problem but then things get strange. I ran an experiment to try and figure it out. Here's what I did:

I try to create 2 completely scaffolded models, create a simple STI hierarchy between them (Son < Parent) and see how Rails handles it.

At the command line:

    ruby script\generate scaffold Parent name:string description:text     rake db:migrate     ruby script\generate scaffold Son member:string

Make 2 changes to the generated code:

   1. Change the migration from creating a new sons table to adding 2 new columns to the parents table:           * add_column :parents, :member, :string           * add_column :parents, :type, :string # needed for STI    2. Make Son (in app/models/son.rb) derive from Parent (instead of ActiveRecord::Base)

rake db:migrate again, and add a few Parents and a Son (through the console).

Result: At URL /parents all Parents and Sons are listed, since they are all saved in the same table. This is the desired behavior which would be expected with an is-a relationship between Sons and Parents. (Note that I use parent and son names in the parent i.e. base class and son i.e. derived class meaning and not in the genealogical sense) This view is generated by the scaffold generator script, and is actually app\views\parents\index.html.erb.

Since Sons are derived but different types from Parents it might be expected that the Show, Edit and Destroy links for actual concrete Sons will link to the (automatically generated) Son controller and the not to the Parent controller. This would essentially be equivalent to calling virtual methods.

This actually happens for the Show and Destroy links. However, the Edit link still redirects to the Parent edit page. The problem apparently stems from the Parent index view:

<%= link_to 'Show', parent %> <%= link_to 'Edit', edit_parent_path(parent) %> <%= link_to 'Destroy', parent, :confirm => 'Are you sure?', :method => :delete %>

the edit_parent_path(parent) seems to be is not as smart as link_to in detecting the STI hierarchy on the parent object.

Can anyone shed some light on this? What does link_to do right that edit_parent_path does wrong?

There is a problem of consistency, either all actions (Show, Edit, Destroy) should point to Parent actions or they should all point to Son actions (this is arguable smarter and more in the spirit of Polymorphism). Having a mixed behavior is at best confusing.

Is it possible that the new RESTful helpers are "dumber" than link_to or url_for?

Interestingly enough, when I edit a Son object via the Parent edit view, the submit button actually calls Son's update method!

Is there a away to simulate edit_parent_path() with link_to in such a way to get consistent behavior?

Thanks, Adi

Hi Adish,

I've actually faced the same problem this morning. Basically my models look like this

User < ActiveRecord::Base then Administrator < User, Lecturer < User Student < User.

I am using the same STI pattern with Rails 2.0.2.

In order to get the problems you've mentioned solved, for the "show" function, I used the following:

<%= link_to user.full_name, user_path(user) %>

This would make sure that the link always refers to the parent.

Now about the destroy, I actually did not have a destroy link because I just needed the User to be suspended / unsuspended depending on the case. So I created those two methods in the UsersController (def suspend ... end / def unsuspend ... end).

And I tried the following:

<%= link_to 'Suspend', suspend_user_path(user), :confirm => 'Are you sure?', :method => :put %>

Then I added the following in my "routes.rb":

map.resources :users, :member => { :suspend => :put, :unsuspend => :put }

in your case, it'd probably be the following for the "routes.rb":

map.resources :users, :member => { :destroy => :delete }

although I suspect Rails already has that implicitly defined.

The point is to note the difference between what you'd normally do for a destroy as in:

<%= link_to 'Destroy', parent, :confirm => 'Are you sure?', :method => :delete %>

versus what was done for the "suspend/unsuspend" above.

This creates a consistency between the "show/edit/destroy/ other_methods" as I believe the edit_parent_path(parent) is the way it's supposed to work.

I may be wrong on this but I hope this helps somehow.