ruby
54 lines · 8 steps
How polymorphic comments work in Rails
A reusable Comment model attaches to any model through a polymorphic association and a shared concern.
Explained by
highlit
1class Comment < ApplicationRecord
2 belongs_to :commentable, polymorphic: true, counter_cache: true
3 belongs_to :author, class_name: "User"
4
5 validates :body, presence: true, length: { maximum: 10_000 }
6
7 scope :recent, -> { order(created_at: :desc) }
8 scope :approved, -> { where(approved: true) }
9
10 after_create_commit :notify_commentable_owner
11
12 private
13
14 def notify_commentable_owner
15 owner = commentable.try(:owner)
16 return if owner.nil? || owner == author
17
18 CommentMailer.with(comment: self).new_comment.deliver_later
19 end
20end
21
22module Commentable
23 extend ActiveSupport::Concern
24
25 included do
26 has_many :comments, as: :commentable, dependent: :destroy
27 end
28
29 def add_comment(body:, author:)
30 comments.create(body: body, author: author)
31 end
32end
33
34class Post < ApplicationRecord
35 include Commentable
36end
37
38class Photo < ApplicationRecord
39 include Commentable
40end
41
42class CreateComments < ActiveRecord::Migration[7.1]
43 def change
44 create_table :comments do |t|
45 t.text :body, null: false
46 t.boolean :approved, null: false, default: false
47 t.references :author, null: false, foreign_key: { to_table: :users }
48 t.references :commentable, polymorphic: true, null: false
49 t.timestamps
50 end
51
52 add_index :comments, [:commentable_type, :commentable_id, :created_at]
53 end
54end
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A polymorphic association lets one model belong to many different parent types through a type and id pair.
- 2Concerns package the parent-side wiring so any model becomes commentable with a single include.
- 3A composite index matching your query columns keeps polymorphic lookups and ordering fast.
Related explainers
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
ruby
class LogAggregator BUCKET_FORMAT = "%Y-%m-%dT%H:%M" def initialize(entries)
Bucketing log entries by the minute in Ruby
aggregation
hashing
enumerable
Intermediate
5 steps
ruby
class WeeklySignupsReport DEFAULT_WEEKS = 12 def initialize(weeks: DEFAULT_WEEKS, source: User.all)
Building a weekly signups report in Rails
service object
aggregation
group by
Intermediate
7 steps
ruby
class ApplicationController < ActionController::Base EXPERIMENTS = { checkout_button_color: %w[control blue green], onboarding_flow: %w[control streamlined]
How A/B test cohorts are assigned in Rails
a-b-testing
cookies
hashing
Intermediate
8 steps
ruby
class SnippetHighlighter CONTEXT_RADIUS = 60 MAX_TERMS = 8
Building search-result snippets in Ruby
regular-expressions
text-processing
search
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-polymorphic-comments-work-in-rails-explained-ruby-d3a4/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.