STI functionality, but then with multiple tables


#1

Hello,

I’m creating my own Tumblr [tumblr.com] like rails app.
It’s like a blog, but in this case a blog post could be of a certain
type, i.e. a regular post, a video, a link, a photo, a song(info).
Well now, it’s clear that they share a lot of similar functionality:
title, created_at/updated_at, commentable, probably has an author_id,
etc…
But besides that they are very different: a post has a large text field,
a link has an url and a description and a photo a reference to the file
in a way.

What to choose? Single Table Inheritance? Not really an option since the
post types are very different.
A polymorphic relation mabye, with a Post; has_one :subtype,
:polymorphic => true;
And that’s what I did, but soon enough I saw that it gave me a lot of
problems.
First of all, it’s not really a subtype, in this case :subtype is really
a child model. When I have a post of type link, I would access the title
through @post.title, but the url and description through
@post.subtype.url and @post.subtype.description accordingly.
Not really nice, but with some nifty method_missing handler I made it a
bit easier.
Now when I only want to have posts of type link I could do Link.find and
I would have only links. But that one behaves so much different from
working from Post as in this case I would read the attributes like
@link.url, @link.description, @link.post.title
Not very consistent, at all.
So I use Post.find :all, :conditions => {:subtype_type (bad naming, I
know) => ‘Link’
To ease that I made a custom find method to accept Post.find :all, :type
=> :link and all was happy.

Sorry for the long post, it’s been on my mind for such a long time
already.
Anyways…

I was very proud on how I got it to work. Only instead of a Link.find
:all I had to do a Post.find :all, :type => :link, and most was based
around that. I could accept that, even though it was hard give up to. It
required some more hacking to enable easier Post creation of a certain
type and made my Post class bits by bits uglier to make it ‘easier’ to
work with.

But then after a while I wanted to add a relationship to Artist with
post of type song.
But how!?! I couldn’t just link the relationship between Artist and
Song, as that would mean would have the same effect as before. That I
don’t get posts of type Song, but pure song models instead, with all the
incosistent use as before.
And a relation between Artist and Post where the foreign key lies in
Song is not possible in rails. And I thought by myself “who the fuck
wants to make such a complex database model, the eediot!!”
At this moment I KNEW this is a flawed model too! And I’ve been thinking
and wandering and whining and crying since then. Not knowing how to
elegantly solve this stupid problem of mine.

STI in rails works really nice! It wouldn’t have any of these problems I
just described above, so why can’t I make how STI models in rails work
using multiple tables. That would be really nice and I believe this
would actually be a feasible.
the Post table would be the same as with a STI model (id, title,
created_at, updated_at, title, type) but the subtypes with have their
own table, the links table would look something like (post_id, url,
description) and the songs table something like (post_id, artist_id,
…).
The subtypes don’t need an id column, as they don’t, won’t and shouldn’t
be models themselves.
As with polymorphic relations, eager load is not an option. I can’t load
the url and description info from the links table as I don’t know what
type the post will be at query time. Well, has_many_polymorphs
[blog.evanweaver.com/pages/has_many_polymorphs] has some nice magic on
this as long as you know (define) what subtypes there are. But even
then, without eager loading I would be quite happy.

Quite happy? I will probably jump for joy!!
Thank you for reading, how are your thoughts on this?

Dax H.


#2

Dax,

I am writing something similar (well, its an app using polymorphism
whose objective is to have many different objects be published and
search consistently). I’m not saying I have an answer for you, but I
can describe my approach. In my case the basic objects are Article
which is associated with Story, Map, Gallery and Image. I also have a
design goal to be able to add new objects easily.

I’ve done two things which ease the burden a little:

  1. I created a module I mixin to each sub-type object. In this
    module I have accessor methods for basically all the main object
    attributes. For example, the module defines article_headline,
    article_tags, article_author and so on. These now appear as
    attributes in each sub-type. I order to facilitate publishing i ensure
    each sub-type has a unique “name” attribute and a “description”
    attribute. These are the only two assumptions I make but it lets me
    abstract the publishing model pretty easily.

  2. I created a controller called ArticleController which implements
    the CRUD loop and other common functions. Then I inherit from that
    controller for the controllers for my objects.

It doesn’t solve all problems by any means. But it does mean I can
search for Articles by author, name, tags, posting/update dates
irrespective of what sub-type. I can invoke a show/edit/new function
on any sub-type without knowing its type and get the right behaviour.

And I can add a new object in less than an hour (if its a basic
object). I create the model and controller as usual and create the
views for show and edit. Add the “require” and “include” for the
mixin in the model. Change the inheritance in the controller. All
done.

Probably not general interest to the list, so email me as kipcole9 -
at- gm-ail dot com if you’d like to continue the conversation - i’d
like to find a more elegant solution to this too.

Cheers, --Kip

On Aug 3, 6:10 am, Dax H. removed_email_address@domain.invalid


#3

Hey Kip,

Yes, I am familiar with this approach too.
I use it for a different project and it’s actually the nicest sollution
so far.
And with the power of has_many_polymorphs I have everything I ever need.
One minor detail is that I don’t use a module with the generic
functionality, but I use an abstract base class, which eventually works
exactly the same.

The only one thing I’m missing here is to get the 10 latest posts (like
Tumblr again) of each kind. This way I have to do a find 10 on each
individual post type, ordered by creation date, concatenade them all,
sort them by the same ‘order by’, creatoin_date that is, and use only
the top 10 of that list.
Now imagine this method with pagination and WHAM! It’s not cool anymore.
Now get the 20 latest posts of each kind, concatenade them, sort them,
and take the 2nd 10 from the beginning. Not cool at all!
Not that I want to do pagination all the time, I’d rather make
subselections for each month, but even then, pagination is still an
option.

I don’t have to do this with my other project, so this method works
great, but for my Tumblr like project, it doesn’t solve it either.

Thanks for reading, Dax.

PS: I wonder how 37signals does this with basecamp and backpack, they
sorta do the same thing I’d like to do.

Kip wrote:

Dax,

I am writing something similar (well, its an app using polymorphism
whose objective is to have many different objects be published and
search consistently). I’m not saying I have an answer for you, but I
can describe my approach. In my case the basic objects are Article
which is associated with Story, Map, Gallery and Image. I also have a
design goal to be able to add new objects easily.