If you're looking for my old blog, it's been moved. Go to old blog →

Understanding the delegated type pattern and multi-table inheritance

Published

help me by sharing on twitter

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:

class Post
	has_many :comments
end

class Comment
	belongs_to :post
end

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.

timeline_items:
	id: integer
	title: text nullable
	content: text nullable
	type: string
	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:

timeline_items:
	id: integer
	timelineable_type: string
	timelineable_id: integer

posts:
	id: integer
	title: string
	content: text

comments:
	id: integer
	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”.

class TimelineItem
	belongs_to :timelineable, polymorphic: true
end

class Post
	has_one :timeline_item, as: timelineable
end

class Comment
	has_one :timeline_item, as: timelineable
end

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

class TimelineItem
	belongs_to :timelineable, polymorphic: true
	
	def title
		timelineable.title
	end

	def content
		timelineable.content
	end
end

class Post
	has_one :timeline_item, as: timelineable
end

class Comment
	has_one :timeline_item, as: timelineable

	def title
		content.truncate(20)
	end
end

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.

class TimelineItem
	delegated_types :timelineable, types: %w[ Post Comment ]
	
	def title
		timelineable.title
	end

	def content
		timelineable.content
	end
end

class Post
	has_one :timeline_item, as: timelineable
end

class Comment
	has_one :timeline_item, as: timelineable

	def title
		content.truncate(20)
	end
end

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! :-)

Keep track of my articles, tutorials and courses and receive valuable information directly in your inbox.

Thanks for subscribing!