How to Add Dynamic Subdomain in Rails Application

How to Add Dynamic Subdomain in Rails Application

For those who want to implement subdomain programmatically within your rails application, you can follow these few steps:

1. Generate Account Model

First, let’s create an Account model as a table to store subdomain names that we will accept in our application.

rails g model Account subdomain:string

From the above generator, it generates a migration file. We can then do rails db:migrate.

2. Add Request-based Constraints dan Modify routes.rb

Create a new file inside the /lib folder and give it the name, say subdomain.rb.

Add this code inside that file:

class Subdomain
  def self.matches?(request)
    request.subdomain.present? && request.subdomain != "www"
  end
end

Then we need to modify a bit our routes.rb by adding constraints to every URLs that we will use under the subdomain. For an example, pages_controller.rb needs to be accessible under a specific subdomain, so we can write our route like this:

Rails.application.routes.draw do
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html

  constraints(Subdomain) do

    get '/', to: 'pages#index'

  end

end

3. Add Authorization to Controller

We will then need to add before_action :require_subdomain since the controllers need to be strictly accessible from the subdomain only. This is achievable by modifying our application_controller.rb:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  helper_method :require_subdomain

  private

  def require_subdomain
    not_found unless subdomain_exist?
  end

  def subdomain_exist?
    account = Account.find_by_subdomain(request.subdomain)
    !!account
  end

  def not_found
    raise ActionController::RoutingError.new('404')
  end
end

In the application_controller.rb above we now have a helper method called require_subdomain, which is reusable to each controller that requires to be authorized by the subdomain.

class PagesController < ApplicationController
  before_action :require_subdomain

  def index
    # controller logic goes here
  end
end

4. Try It Out

Let's use rails console to create a dummy for our Account or subdomain.

$ rails c
Running via Spring preloader in process 15151
Loading development environment (Rails 6.0.0)
2.6.5 :001 > Account.new(subdomain: 'test').save
   (0.4ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
   (0.3ms)  BEGIN
  Account Exists (0.7ms)  SELECT  1 AS one FROM `accounts` WHERE `accounts`.`subdomain` = BINARY 'test' LIMIT 1
  SQL (0.5ms)  INSERT INTO `accounts` (`subdomain`, `created_at`, `updated_at`) VALUES ('test', '2017-07-22 14:58:35', '2017-07-22 14:58:35')
   (35.5ms)  COMMIT
 => true

Open the browser then visit test.lvh.me:3000 and also try accessing some random subdomain, for example, doesnotexist.lvh.me:3000.

About lvh.me domain, you can check this article out

If the subdomain is registered in Account

download (2).png

Subdomain doesn't exist in Account

download (1).png

Tip:

We can add some extra validation layers in the Account creation so we can preserve some subdomain names so your user cannot claim those (i.e. www, admin, app, etc).

Thank you!