Monday, January 21, 2008

STI Views Revisited or Polymorphic Paths

In my previous post I lamented the strange and inconsistent behavior of the the automatically generated view for a simple STI hierarchy. After many searches and several futile attempts to explain myself on the #rubyonrails IRC chat, I posted my question (slightly simplified) to the Ruby on Rails: Core list.

Mislav Marohnić was kind enough to shed light on my problem. He said (edited for context):

When you say link_to(text, record), a polymorphic url is constructed depending on the model. But when you say edit_parent_path, you are explicitly stating that you are dealing with Parent records and that no polymorphism should take place.

I understand that when using STI you have a need for a polymorphic helper, but I don't think changing the behavior of named routes (explicit or automatically generated) is a solution - it would just be the source of more confusion for others.

Your cup of tea would be the edit_polymorphic_path helper...
Essentially, I just need to change:
link_to 'Edit', edit_parent_path(parent)
to
link_to 'Edit', edit_polymorphic_path(parent)
and edit_polymorphic_path() will do the correct thing and generate the path based on the concrete record type.

Problem solved! Thank you Mislav!

On a side note, edit_polymorphic_path() used to be part of the simply_helpful plugin, but had been merged into core Rails more than 8 months ago. However it had not been documented, and my post seems to have sparked the documentation and exposure of this module and its kin. Excellent! My minor and indirect contribution to Rails. ... and, you won't read this in any Rails book!

If you found this post helpful, or you have more thought on this subject, please leave a message in the comments. If you want to read more about my exploration of polymorphism and STI on Rails, subscribe to the blog feed and stay updated.

Friday, January 11, 2008

STI Experiment 1 - Scaffolded STI Sub-Classes

I was having problem listing STI related objects from the same table.
Here are my experiment with trying to work through the problems.

My first experiment will try to create 2 completely scaffolded models, create a simple STI hierarchy between them 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 not as smart as link_to in detecting the STI hierarchy on the parent object.

I don't know how to bypass this at the moment.
I will update this post if and when I have a working solution.

If you found this post helpful, or you can shed some light on this problem, please leave a message in the comments. If you want to read more about my exploration of polymorphism and STI on Rails, subscribe to the blog feed and stay updated.

Wednesday, January 9, 2008

Single Table Inheritance in Rails

I've been experimenting with Single Table Inheritance or STI.
STI documentation is rather sparse, distributed, confusing and lacking so I hope to make some sense out of it.

The Wiki describes Single Table Inheritance like this:

Single Table Inheritance represents an inheritance hierarchy of classes as a single table that has columns for all the fields of the various classes.
Martin Fowler explains:
Relational databases don't support inheritance, so when mapping from objects to databases we have to consider how to represent our nice inheritance structures in relational tables. ... Single Table Inheritance maps all fields of all classes of an inheritance structure into a single table.
What this means, is that at the DB table level all records (objects) from all levels of the type hierarchy (classes) have all the fields (members) of all the other classes (and direct access to them). In other words, the table fields are the union of all the members of all the classes in the hierarchy (plus one special field called :type).
Due to this fact, obviously, two classes in the hierarchy cannot have two differently typed members with the same name.

Interestingly enough, this kind of inheritance actually goes against some of OOP's main tennets such as, data decoupling, data encapsulation/hiding, access control and separation of concerns.

These issues notwithstanding, STI can still be useful.

To build your hierarchy, say by sub-classing from Person, generate a migration script:
>>ruby script/generate migration AddColumnsForPersonHierarchy
then in the resulting migration file add all the additional columns you need for any or all the classes of your hierarchy. Additionally, add the special string column, conventionally called :type. Rails will automatically fill :type with the actual class name.

Then, add your class models by giving each class a separate file, e.g. create a new file app/models/employee.rb:
class Employee < Person 
validates_presence_of :department
end
You could do it all in one model file, as shown in various example on the web, (e.g. in app/models/person.rb) but that creates lookup problems for Rails, so that Employee will not be found before Person is first used.

For improving class data consistency we can, inside our class models, validates_presence_of any fields that are relevant just for this class, as shown in the example above. Other classes can accept nil.

In the next post in this series I will explore the how to add controllers to our STI hierarchy. I will also investigate how far Rails can allow true polymorphism with STI.

If you found this post helpful, please leave a message in the comments. If you want to read more about my exploration of polymorphism and STI on Rails, subscribe to the blog feed and you won't miss a thing.

Tuesday, January 8, 2008

Instant Rails 2.0

Instant Rails 2.0 was recently released.
It contains the following major changes:

  • Upgrades Ruby to version 1.8.6 Patch Level 111
  • Upgrades Rails to 2.0.2
  • Upgrades Mongrel to 1.1.2
  • Upgrades RubyGems to 1.0.1
  • Upgrades Rake to 0.8.1
  • Upgraded Cookbook app to support Rails 2.0.2
  • Added SQLite3 Support
  • Added Capistrano 2.1.0
You can download Instant Rails here and read the complete release notes here.
I have not tried it yet, but will probably do so soon.