#97 Single Table Inheritance
9-10-2017

Summary

Learn to use single table inheritance to allow multiple classes to be stored in the same database table.
14
rails model 8:33 min

Summary

20170910010823_create_contacts.rb# rails g model contact user_id:integer type first_name last_name phone_number address city state zip birthday

class CreateContacts < ActiveRecord::Migration[5.1]
  def change
    create_table :contacts do |t|
      t.integer :user_id
      t.string :type
      t.string :first_name
      t.string :last_name
      t.string :phone_number
      t.string :address
      t.string :city
      t.string :state
      t.string :zip
      t.date :birthday
      t.timestamps
    end
    add_index :contacts, [:type, :user_id]
  end
end
contact.rbclass Contact < ApplicationRecord
  scope :friends, -> { where(type: 'Friend') }
  scope :emergencies, -> { where(type: 'Emergency') }
end
emergency.rbclass Emergency < Contact
  belongs_to :user
  validates :first_name, presence: true
  validates :last_name, presence: true
  validates :phone_number, presence: true
end
friend.rbclass Friend < Contact
  belongs_to :user
  validates :first_name, presence: true
  validates :last_name, presence: true
  validates :birthday, presence: true
end
user.rbclass User < ApplicationRecord
  has_many :emergencies, class_name: 'Emergency'
  has_many :friends, class_name: 'Friend'
end
routes.rbRails.application.routes.draw do
  resources :users do
    resources :emergencies, controller: :contacts, type: 'Emergency'
    resources :friends, controller: :contacts, type: 'Friend'
  end
  root to: 'users#index'
end
show.html.erb<h1>Emergency Contacts</h1>
<%= link_to 'New', new_user_emergency_path(@user) %>
<table class='table'>
  <thead>
    <tr>
      <th></th>
      <th>First Name</th>
      <th>Last Name</th>
      <th>Phone Number</th>
      <th>Birthday</th>
    </tr>
  </thead>
  <tbody>
    <% @user.emergencies.each do |contact| %>
      <tr>
        <td><%= link_to 'delete', [@user, contact], method: :delete %></td>
        <td><%= contact.first_name %></td>
        <td><%= contact.last_name %></td>
        <td><%= contact.phone_number %></td>
        <td><%= contact.birthday %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<h1>Friends Contacts</h1>
<%= link_to 'New', new_user_friend_path(@user) %>
<table class='table'>
  <thead>
    <tr>
      <th></th>
      <th>First Name</th>
      <th>Last Name</th>
      <th>Phone Number</th>
      <th>Birthday</th>
    </tr>
  </thead>
  <tbody>
    <% @user.friends.each do |contact| %>
      <tr>
        <td><%= link_to 'delete', [@user, contact], method: :delete %></td>
        <td><%= contact.first_name %></td>
        <td><%= contact.last_name %></td>
        <td><%= contact.phone_number %></td>
        <td><%= contact.birthday %></td>
      </tr>
    <% end %>
  </tbody>
</table>
_form.html.erb<%= form_with model: [@user, contact], local: contact do |form| %>
  ...
<% end %>
contacts_controller.rbclass ContactsController < ApplicationController
  before_action :set_contact, only: [:show, :edit, :update, :destroy]

  def new
    @user = User.find(params[:user_id])
    @contact = @user.send(set_type.pluralize).new
  end

  def edit
  end

  def create
    @user = User.find(params[:user_id])
    @contact = @user.send(set_type.pluralize).new(contact_params)
    if @contact.save
      redirect_to @user, notice: "#{params[:type]} Contact was successfully created."
    else
      render :new
    end
  end

  def update
    if @contact.update(contact_params)
      redirect_to @user, notice: "#{params[:type]} Contact was successfully updated."
    else
      render :edit
    end
  end

  def destroy
    @contact.destroy
    redirect_to @user, notice: "#{params[:type]} was successfully destroyed."
  end

  private

    def set_contact
      @user = User.find(params[:user_id])
      @contact = @user.send(set_type.pluralize).find(params[:id])
    end

    def set_type
      case params[:type]
      when 'Friend'
        'friend'
      when 'Emergency'
        'emergency'
      end
    end

    def contact_params
      params.require(set_type.to_sym).permit(:type, :first_name, :last_name, :phone_number, :address, :city, :state, :zip, :birthday)
    end
end


5896287?v=3&s=64
mchlfhr said 3 months ago:

Hi,

Nice cast! But would it not be possible to dry out the class definitions a little bit more? As Emergency and Friend anyway inherits from Contact, it would also be possible to declare the association and the "similar" validations in there:

class Contact < ApplicationRecord
  scope :friends, -> { where(type: 'Friend') }
  scope :emergencies, -> { where(type: 'Emergency') }
  belongs_to :user 
  validates :last_name, presence: true
  validates :first_name, presence: true
end
class Emergency < Contact
  validates :phone_number, presence: true
end
class Friend < Contact
  validates :birthday, presence: true
end

635114?v=3&s=64
kobaltz PRO said 3 months ago:

Absolutely. However, I was illustrating the point of how the different classes would act. However, since the Emergency and Friend inherit from Contact, any common duplication would best live in Contact.

Photo
MANISH ANAND said about 1 month ago:

Hi,

While creating new emergencies the url is: http://localhost:3000/users/17/emergencies/new

If validation fails, the url is getting change as: http://localhost:3000/users/17/emergencies

And, than If i refresh the page, its getting crash, because its trying to find list of contacts.

So, how I can I fix this. Please help. 


635114?v=3&s=64
kobaltz PRO said about 1 month ago:

This is common and typically due to the validation failure handling of 

render :new

Have a look at https://stackoverflow.com/a/41520362/722274

One way around this is to do client side validations which I have an example of this at https://www.driftingruby.com/episodes/client-side-validations

Login to Comment