Polymorphic Associations

Episode #98 by Teacher's Avatar David Kimura

Summary

Advancing from Single Table Inheritance, learn how Polymorphic Associations differ and tricks to simplify their usage.
rails model 10:31

Resources

Summary

# Terminal
rails g scaffold company name website
rails g scaffold employees first_name last_name email birth_date:date
rails g model note notable:references{polymorphic} content:text

# database migrations
class CreateNotes < ActiveRecord::Migration[5.1]
  def change
    create_table :notes do |t|
      t.references :notable, polymorphic: true
      t.text :content

      t.timestamps
    end
  end
end

create_table "notes", force: :cascade do |t|
  t.string "notable_type"
  t.integer "notable_id"
  t.text "content"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["notable_type", "notable_id"], name: "index_notes_on_notable_type_and_notable_id"
end

# note.rb
class Note < ApplicationRecord
  belongs_to :notable, polymorphic: true
end

# employee.rb
class Employee < ApplicationRecord
  has_many :notes, as: :notable
end

# company.rb
class Company < ApplicationRecord
  has_many :notes, as: :notable
end

# routes.rb
Rails.application.routes.draw do
  resources :companies do
    resources :notes, module: :companies
  end

  resources :employees do
    resources :notes, module: :employees
  end

  ...
end

# notes_controller.rb
class NotesController < ApplicationController
  def new
    @note = @notable.notes.new
  end

  def create
    @note = @notable.notes.new note_params
    @notable.save
    redirect_to @notable, notice: "Your note was successfully posted."
  end

  private

    def note_params
      params.require(:note).permit(:content)
    end
end

# employees/notes_controller.rb
class Employees::NotesController < NotesController
  before_action :set_notable

  private

    def set_notable
      @notable = Employee.find(params[:employee_id])
    end
end

# companies/notes_controller.rb
class Companies::NotesController < NotesController
  before_action :set_notable
  
  def create
    # NOTIFY
    super
  end

  private

    def set_notable
      @notable = Company.find(params[:company_id])
    end
end

# employees/show.html.erb
<%= render partial: "notes/notes", locals: {notable: @employee} %>
<%= render partial: "notes/form", locals: {notable: @employee} %>

# companies/show.html.erb
<%= render partial: "notes/notes", locals: {notable: @company} %>
<%= render partial: "notes/form", locals: {notable: @company} %>

# notes/_form.html.erb
<%= form_with(model: [notable, Note.new], local: true) do |form| %>
  <div class="field">
    <%= form.label :content %><br/>
    <%= form.text_area :content %>
  </div>
  <%= form.submit class: "btn btn-primary" %>
<% end %>

# notes/_notes.html.erb
<h3>Notes</h3>

<% notable.notes.each do |note| %>
  <p>
    <hr>
    <%= note.content %>
    <em><%= time_ago_in_words note.created_at %></em>
  </p>
<% end %>