Previously I wrote about why you might want to use Single Table Inheritance in Rails. Let’s take a look at STI in action in a simple Rails app.
(note: You can download the source code for this post from github)
Assume we’re building a micro-blogging app, I’m sure there is room for one more blog app after all. We want users to be able to post a blog entry that is literal text — something they wrote, or post an image alone, or post a link to something else. All three types of posts will have a title, author and timestamps, but the details are specific to each type, as it the behavior. Our application domain might look something like this:
Simple enough, an abstract base class with three concrete specializations, one for each type of post. Now, how can we stuff these into our relational database without having to jump through hoops?
First, we define a migration that creates a Posts table with a superset of these attributes. The “image” attribute is actually expanded to several fields because we’re using attachment_fu to handle storing and re-sizing the image. The “magic” is the definition of the type field, this will be used by Rails to store the post type so that the proper type of object is associated with each row.
class CreatePostings < ActiveRecord::Migration
def self.up
create_table :posts do |t|
# if this thing is a LinkPosting, we use the URL
t.string :url
# if this thing is a LiteralPosting (text/embeded player) we use literal
t.text :literal
# if this thing is an AttachmentPosting we use attachment_fu
t.column :parent_id, :integer
t.column :content_type, :string
t.column :filename, :string
t.column :thumbnail, :string
t.column :size, :integer
t.column :width, :integer
t.column :height, :integer
# common attributes
t.timestamps
t.column :title, :string
t.column :author, :string
# required for STI
t.string :type
end
end
def self.down
drop_table :posts
end
end
We’ll need an ActiveRecord class for Post of course:
class Post < ActiveRecord::Base validates_presence_of :author, :title end
Notice that we only validate the common attributes on the Post model. As far as post is concerned, everything else is optional.
Now we need ActiveRecord objects for the descendant classes, the ones our application will actually manipulate. A few things to notice here: First, each one of these extends Post not ActiveRecord. We only have the one posts table in the database after all. Second, each model implements the validations specific to that type of post. Any behavior that varies by the type of object (and that belongs in the model) can be handled like this.
First, a LiteralPost - nothing too surprising here:
class LiteralPost < Post validates_presence_of :literal end
Next, the LinkPost:
class LinkPost < Post # this functionality is provided by a plug-in # it appears that some sites block this kind of validation at the root # eg. yahoo.com will fail, but a permalink to a news story at yahoo.com will work OK validates_http_url :url end
Finally, the ImagePost. The has_attachment and validates_as_attachment elements come from attachment_fu.
class ImagePost < Post
has_attachment :content_type => ['application/pdf', 'application/msword', 'text/plain', :image],
:storage => :file_system,
:resize_to => "800x800>",
:max_size => 5.megabytes
validates_as_attachment
end
If we were to draw a picture of our domain model as it exists in the database and in our application code we would see something like this:
It might strike you as odd (or perhaps “wrong”) to have all the attributes on all of the entities. A LinkPost is never expected to have an image associated with it, right? This is a design trade off. We’re not really wasting any space because the database is smart enough that it only uses a few bytes to keep track of null data. A lot of things that are “best practices” in Rails tend to irritate experienced DBAs. Such is life.
If we pop into script/console we can verify that this all works as expected:
As you can see, we can create concrete post types, and retrieve all heterogenous posts using Post.find. This is great because we want to be able to display all posts in the same view regardless of the type of post. In essence, we want to treat all of the concrete subclasses the same.
We’ll use a smidgen of Ruby dynamic programming to simplify things. If we define partials for the editing and rendering of the details specific to each subclass then we can call the correct partial using something like this:
<% for post in @posts %>
<div class="postbox">
<h2><%= post.title %></h2>
<p>created by <b> <%= h post.author %> </b> on <b><%= post.created_at %> </b></p>
<!-- use the post type to determine the correct partial to render ->
<%= render :partial => post.type.downcase, :locals=>{:post=>post} %>
</div>
<% end %>
The “magic” is on line 8 in the above code. It’s just a little tiny sprinkling of Ruby Dust, but it completely eliminates the need for conditionals to produce the desired output. We’ve defined two partials for each concrete subclass, one for viewing and one for editing, and they are named using the name of the object itself. For more details you can grab the code and look at it yourself.
The index view in the finished app show off our display of different post types nicely, one LinkPost, one LiteralPost and one ImagePost.
What if we need to have relationships between the various concrete subclasses? What if if we really, really don’t want to store everything in a single table? We’ll take a look at those issues in part 3.




[...] Rails Single Table Inheritance, Part III In the two previous posts I talked about Single Table Inheritance (STI) in Rails, first looking at the concept and why we might choose to represent our domain model like this, then we looked at an actual implementation where the goal was to be able to treat a heterogeneous co…. [...]