Recently, DHH published this tweet about adding the delegated type to Rails. I wrote a thread explaining what it did, but I wanted to write a blog post to explain it better.

First, you may use this without knowing. This is just the use of a polymorphic relation to certain specific cases. The example are going to be in in some pseudo-Rails, as all I need is for you to understand the concept.

The problem

Imagine your web application has two important entities: Posts and Comments. A Post has a title and a content columns. A Comment only has content.

The relations are pretty simple here: a Comment belongs to a Post and a Post has many Comments. Here’s some code:

1class Post
2 has_many :comments
3end
4 
5class Comment
6 belongs_to :post
7end

Now, imagine you want to create a timeline, that, for now, shows both posts and comments. How would you do this? Well, you could fetch posts and comments and then write some logic to merge them and show them chronologically. It does work, but it’s a bit messy. Things start to get more complicated once you start designing the UI: first, you will have to check which type of resource you are dealing with: is it a Post or a Comment? Remember they don’t have the same columns, so you will have to write some logic to handle that. You will also have to have some conditional blocks to determine where to link to, etc. Well, if you want to add pagination it becomes even trickier. It’s just not a really good solution.

The solution

The most common solution is creating a super-table and letting your app find out if it’s a Post or a Comment. So, instead of having these two tables, you would a single timeline_items table or something with a type column to determine which type of it is, plus all the columns an item may have. Remember they don’t necessarily have the same columns? That means your supertable would need all of them and only use a few ones at a time.

1timeline_items:
2 id: integer
3 title: text nullable
4 content: text nullable
5 type: string
6 author_id: integer

We’re dealing with only two resources here. If we were to add another timeline item, we would have to add it’s columns there again. It gets messy.

In your template, you would have to check what type of item you’re dealing with, pull certain attributes from it, etc. It’s not a good solution. To ease things up, you could divide those into several models and use single-table inheritance, but then you would stumble on our first problem — you would still have to deal with different models, and if you were to fetch the parent model, you would still have a messy table and a bunch of logic.

The “correct” solution

Rails now has this natively as “delegated types”. For those who have used Laravel or Rails, it’s somewhat a polymorphic relation. The difference is we usually use polymorphic relations in a Parent > Child manner. For instance, if you have a images table in your app that can belong to either a Post or a Comment, the common thing is to fetch the Post or Comment and then get it’s images. Now, we are doing the opposite. The polymorphable model is now a wrapper that we can use to fetch multiple models at once. It would be like fetching all the images and showing it alongside their post or comment in a timeline.

You create a parent table which holds information shared amongst all children, and the children only include data particular do them. For instance, using our previous example, we would end up with these 3 tables:

1timeline_items:
2 id: integer
3 timelineable_type: string
4 timelineable_id: integer
5 
6posts:
7 id: integer
8 title: string
9 content: text
10 
11comments:
12 id: integer
13 content: text

Notice that we don’t have a giant single-table anymore — we do still have the two tables from the first example but now they all reference a parent.

What we can do now is, instead of querying two different tables to show our time, query the timeline_items table and then access the related model. Imagine we are fetching them through a “middleman”.

1class TimelineItem
2 belongs_to :timelineable, polymorphic: true
3end
4 
5class Post
6 has_one :timeline_item, as: timelineable
7end
8 
9class Comment
10 has_one :timeline_item, as: timelineable
11end

You can even go further and write methods in the TimelineItem so you don’t have to even write conditional logic.

1class TimelineItem
2 belongs_to :timelineable, polymorphic: true
3 
4 def title
5 timelineable.title
6 end
7 
8 def content
9 timelineable.content
10 end
11end
12 
13class Post
14 has_one :timeline_item, as: timelineable
15end
16 
17class Comment
18 has_one :timeline_item, as: timelineable
19 
20 def title
21 content.truncate(20)
22 end
23end

You could the same with only polymorphic relationships, but now you have a way for Rails to handle things to you.

Basically, you’ll retrieve “timeline items” (though you still can retrieve posts and comments individually), and, if you’d like, write one unique way to handle them all. See that while a post title is simply it’s title column, a Post title is a truncated version of it’s content. Timeline items can delegate things like figuring out the title to their related resource instead, etc.

One small, annoying thing of this is that, for instance, when creating a comment, you’d also have to create a timeline_item after it. Want to query timeline items that are Posts? You’ll have to write a scope. Check if a timeline item is a Comment? You will have to write your logic.

What Rails now does is natively implement this. If we were simply to change the belongs_to in the TimelineItem model for delegated_type, we get access to several cool things.

1class TimelineItem
2 delegated_types :timelineable, types: %w[ Post Comment ]
3 
4 def title
5 timelineable.title
6 end
7 
8 def content
9 timelineable.content
10 end
11end
12 
13class Post
14 has_one :timeline_item, as: timelineable
15end
16 
17class Comment
18 has_one :timeline_item, as: timelineable
19 
20 def title
21 content.truncate(20)
22 end
23end

There are lots of added methods, but for instance, you could check if a timeline item is a post like this: timeline_item.post?, or you could limit your query to Comment timelineables like this: TimelineItem.posts. There are lots of cool things and DHH’s PR has all them listed on the documentation. I suggest you check it out!

Hope to have been of any help! :-)