Matthew Lang

Icon

A journal on ruby and rails development.

Using Single Table Inheritance Is Okay

This week I noticed an excellent article by Matt Moore where he assembled a check list of code quality items that each developer in his teams should sign off on for each of their projects.  While I agree with most of Matt’s check list as a whole, I don’t agree with his argument that the Single Table Inheritance (STI) feature in the Rails framework shouldn’t be used.

In a project that I am currently working on I am using the STI feature to allow a common set of objects to share similar properties but are of different types.  For example, I have a table of factors but each factor can either be a benefit or a risk.  Each factor has 2 fields at the moment: name and weight.  Using the STI feature I can create a single database table called factors that allows me to store benefits and risks in them.

Making Design Decisions

This is fine for a simple example, but what if one of my types needs an extra field or more?  This is where things can get messy.  At this point the team faces a design decision. Does the team just add the fields to the STI table or do we re-factor out the types into their own tables?

In most cases you’ll want to re-factor out the types into their own tables.  For those other cases where you’ll just add the field to the STI table.  This is fine for one field, but if your adding more I would suggest you re-factor the STI table into seperate tables for each type.

If you have set-up your models correctly and you have a set of unit tests to back them up, then you should have no problems breaking this STI table into two tables.  By using unit tests to ensure your code changes are correct and migrations to make the required changes to your database, you’ll have your STI table re-factored into two or more single tables.

Know The Framework

The Rails framework is a complex thing if your learning, but like most things if you practice and learn what’s involved in its features, you’ll make good design decisions that don’t hamper the progress of your project.  In this case, by learning the correct use of the STI feature and where it can be applied can save hours of bedlam later.

If you want to find out more about the STI feature in the Rails framework, I recommend first familiarising yourself using Martin Fowler’s Single Table Inheritance description and then checking out the Rails documentation for ActiveRecord.

Beware of Using named_scope for Associations

The named_scope feature in the Rails framework has been kicking about for a while now.  Named_scope is a way to of narrowing down your more common find routines in your ActiveRecord models.  Here’s a quick example:

In a task model we have the following:

class Task < ActiveRecord::Base
  named_scope :short, :conditions => ["duration < ?", 2]
end

This will allow us to query for all tasks that have a duration of less than 2.

Task.short

Also we can setup a named scope that uses a parameter that we pass to it.

class Task < ActiveRecord::Base
  belongs_to :project

  named_scope :short, lambda { |hours|
    { :conditions => ["duration < ?", hours] }
  }
end

This will allow us to find tasks that have a duration less than the number of hours we pass to the named_scope.

Task.short(2)

Now while this is all great, I thought about changing some of my associations to use the named scope instead.

class Project < ActiveRecord::Base
  has_many :tasks
end  

class Task < ActiveRecord::Base
  belongs_to :project

  named_scope :for_project, lambda { |project_id|
    { :conditions => ["project_id = ?", project_id] }
  }
end

We can now iterate through a collection of tasks associated with a project.

Task.for_project(id).each do |task|
  # do something here
end

Now while it did give me a chance to use named_scope within my project, something about the code just didn’t sit right. It’s hard to determine at first glance what we’re doing in the above code snippet, as the more conventional method is to find the project, and then access the list of tasks for that project.  After adding a couple of more it seemed that the code didn’t read write in my eyes.  A better way of find tasks for a project is to use the more conventional way in Rails.  We can do this like so:

@project = Project.find(id)
@project.tasks.each do |task|
 # do something here
end

The features in the Rails framework are there to be used, but be careful your not using a feature in your project just for the sake of using it. There are plenty of ways of doing things in Rails, and usually that best way is the more conventional way of doing it rather than exploiting a featureof Rails just becuase you can.

My thanks to Max and Mike over at the RailsForum for their feedback.

About Me

Matthew is a Ruby and Navision developer. Although his Navision skills pay the mortgage, he has decided to ditch his love of programming in .NET and has instead shacked up with the Ruby language.


Matthew is also a keen advocate of mind mapping as a tool for visual thinking and problem solving. Having first started mind mapping in the early 90's, Matthew has produced hundred of mind maps for all aspects of life.

Categories

Twitter

Delicious