Working with Subdomains

Episode #85 by Teacher's Avatar David Kimura

Summary

Learn to create a multi-tenant application where access to tenants are determined by the subdomain.
rails multitenancy 7:40

Resources

Episode #40 puma-dev - https://www.driftingruby.com/episodes/puma-dev-replacement-for-pow-and-prax
Source - https://github.com/driftingruby/085-working-with-subdomains

Important to note that if you are going to use subdomains and serve traffic over HTTPS, you will need a Wildcard SSL Certificate.

Summary

# Terminal
puma-dev link -n example .
pkill -USR1 puma-dev && puma-dev -install && tail -f log/development.log

# application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :ensure_subdomain

  def current_blog
    @current_blog ||= Blog.find_by(subdomain: request.subdomain)
  end
  helper_method :current_blog

  private

  def ensure_subdomain
    redirect_to root_url(subdomain: :www) unless current_blog.present?
  end
end

# routes.rb
Rails.application.routes.draw do
  constraints(SubdomainRoutes) do
    resources :blogs, only: [:new]
    root 'welcome#index'
  end
  
  constraints(!SubdomainRoutes) do
    resources :blogs, except: [:index, :new] do
      resources :posts
    end
    root 'blogs#show'
  end
end

# config/initializers/subdomain_routes.rb
class SubdomainRoutes
  def self.matches? request
    case request.subdomain
    when '', 'www'
      true
    else
      false
    end
  end
end

# blogs_controller.rb
class BlogsController < ApplicationController
  before_action :set_blog, only: [:show, :edit, :update, :destroy]
  skip_before_action :ensure_subdomain, only: [:new, :create]

  def show
  end

  def new
    @blog = Blog.new
  end

  def edit
  end

  def create
    @blog = Blog.new(blog_params)
    if @blog.save
      redirect_to root_path, notice: 'Blog was successfully created.'
    else
      render :new
    end
  end

  def update
    if @blog.update(blog_params)
      redirect_to root_path, notice: 'Blog was successfully updated.'
    else
      render :edit
    end
  end

  def destroy
    @blog.destroy
    redirect_to root_url(subdomain: nil), notice: 'Blog was successfully destroyed.'
  end

  private

    def set_blog
      @blog = current_blog
    end

    def blog_params
      params.require(:blog).permit(:name, :subdomain)
    end
end

# welcome_controller.rb
class WelcomeController < ApplicationController
  skip_before_action :ensure_subdomain

  def index
    @blogs = Blog.all
  end
end

# models/blog.rb
class Blog < ApplicationRecord
  validates :subdomain, 
            exclusion: { in: %w(www), 
            message: "%{value} is reserved." }, 
            presence: true, 
            uniqueness: true
  before_validation :sanitize_subdomain

  has_many :posts

  private

  def sanitize_subdomain
    self.subdomain = self.subdomain.parameterize
  end
end

# blogs/show.html.erb
<%= link_to 'Blog Home', root_path, class: 'btn btn-info' %>
<%= link_to 'Site Home', root_url(subdomain: nil), class: 'btn btn-info' %>

# welcome/index.html.erb
<%= link_to "#{blog.subdomain}.example.dev", root_url(subdomain: blog.subdomain) %>