Reading the Source · Card 12
What does delegated_type set up?
DHH's preferred shape for "an Entry can be a Message or a Comment." Less invasive than STI, more structured than raw polymorphic associations. Every Rails 6.1+ codebase has it somewhere.
The familiar line
A Hey-style example: an Entry is either a Message or a Comment.
class Entry < ApplicationRecord
belongs_to :account
delegated_type :entryable, types: %w[Message Comment]
end
class Message < ApplicationRecord
include Entryable
end
class Comment < ApplicationRecord
include Entryable
end
module Entryable
extend ActiveSupport::Concern
included do
has_one :entry, as: :entryable, touch: true
end
end The question
What methods does delegated_type add to Entry, and what schema does it expect?
Take a moment. The shape is "Entry is a wrapper, the actual content lives in entryable." Think about: the polymorphic association, the predicates, the scopes.
What gets installed
The Entry table needs two columns: entryable_type (a string like "Message") and entryable_id (the foreign key to whichever table). Standard polymorphic association columns.
On Entry, delegated_type generates:
entry.entryable # the actual Message or Comment row
entry.entryable = msg # assignment
entry.message? # true if entryable_type == "Message"
entry.comment? # true if entryable_type == "Comment"
entry.message # returns the Message (or nil if it's a Comment)
entry.comment # returns the Comment (or nil if it's a Message)
# On the class:
Entry.messages # scope: where(entryable_type: "Message")
Entry.comments # scope: where(entryable_type: "Comment")
# Plus the .build_entryable_of(... type:, ...) helpers for creation. The point of separating the wrapper (Entry) from the content (Message, Comment) is: shared concerns live on Entry (timestamps, account, indexing, ordering), and type-specific behavior lives on its own table. Two Entry rows pointing at a Message and a Comment can show up together in the same feed, ordered by Entry's created_at, without STI's "one giant table with many null columns" problem.
Where this happens in Rails
Defined in activerecord/lib/active_record/delegated_type.rb. The method declares a regular polymorphic belongs_to, then walks the types: array and defines a predicate, a getter, and a class-level scope for each. No magic beyond what a polymorphic belongs_to already does — it just names the pattern.
Why this is worth knowing
This is the 37signals school's answer to a problem that often gets solved with STI. If you read DHH's "Hey" codebase, you see delegated_type everywhere. Recognizing it on sight means you know what an "entryable" or "linkable" pattern is doing, and you read the rest of the model faster. Same shape as the other declarations in this series: one line, a family of methods, a small schema expectation.