0

Maybe you have seen/read the Railscast/Asciicast about subdomains in Rails 3. I'd like you to ask about best practices on how to implement an application behavior when the parent (in this article: "blog") is not found. Let me explain.

blog1.example.com/articles # it's normal situation
example.com/articles # abnormal situation. 

In the second example no blog to find, but articles's routes are still available. I know, I can use something like this ...

def rescue_action(exception)
  case exception
    when ActiveRecord::RecordNotFound
      return redirect_to blogs_path, :status => :moved_permanently
  end
  super
end 

... but is it the "Rails way"? Any idea/comment on this?

JJD
  • 50,076
  • 60
  • 203
  • 339
Alexey Poimtsev
  • 2,845
  • 3
  • 35
  • 63

1 Answers1

0

What I did in this case, was to restrict the routing based on subdomain or no subdomain. In that case, you can easily have routes that only work on subdomains, resulting in a routing error (404) if someone tries to access that same route without a subdomain.

So for example:

routes.rb

Backend::Application.routes.draw do

  constraints AppDomainRoutes.new do

    # signup paths
    get   "/signup" => "accounts#new", as: "signup"
    post  "/signup" => "accounts#create", as: "signup"

    # root
    root to: "accounts#new"
  end

  constraints AccountDomainRoutes.new do

    # password reset paths
    get   "/reset_password/:password_reset_token" => "reset_passwords#edit", as: "reset_user_password"
    put   "/reset_password/:password_reset_token" => "reset_passwords#update", as: "reset_user_password"

    # websites
    resources :websites

    # root
    root to: "websites#new"
  end

  # request password reset paths
  get   "/reset_password" => "reset_passwords#new", as: "reset_password_request"
  post  "/reset_password" => "reset_passwords#create", as: "reset_password_request"

  # login paths
  get "/login"   => "sessions#new",      as: "login"
  post "/login"  => "sessions#create",   as: "login"

  # logout paths
  get "/logout"    => "sessions#destroy",  as: "logout"
  delete "/logout" => "sessions#destroy",  as: "logout"


end

And then in lib/routes:

app_domain_routes.rb

class AppDomainRoutes
  def matches?(request)
    request.subdomain.blank? || request.subdomain == "www"
  end
end

account_domain_routes.rb

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

Now, /signup is only accessible from the main application domain www.mydomain.com or mydomain.com and /websites/new is only accessible from *.mydomain.com. But /login is still accessible in both situations, for convenience sake.

Obviously this doesn't solve the issue of visiting invalid.mydomain.com when invalid in fact is not an account in the database.

For this you go back to the application_controller.rb and handle redirection there, like this:

application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery
  before_filter :redirect_unknown_account

  private

  # returns current subdomain (account.subdomain) or nil
  def account_subdomain
    @account_subdomain ||= request.subdomain if request.subdomain.present? && request.subdomain != "www"
  end

  def current_account
    @current_account ||= Account.find_by_username(account_subdomain) if account_subdomain
  end

  def redirect_unknown_account
    if account_subdomain && ! current_account
      redirect_to signup_url(host: app_domain), alert: "This account does not exist."
    end
  end

  def account_domain
    @account_domain ||= "#{current_account.username}.#{app_domain}" if current_account
  end

  def app_domain
    @app_domain ||= "mydomain.com"
  end

end
JeanMertz
  • 2,250
  • 2
  • 21
  • 26