An extremely common practice for Rails applications is to provide keyed

access through subdomains (i.e. http://someaccount.awesomeapp.com/). However,

there has never been a real unified convention for handling this functionality.

DHH’s Account Location

works for some circumstances but is more tailored for a Basecamp domain model

(i.e. the app is on a separate domain from all other functionality, so you

can always expect a subdomain) than the more common usage of one domain only.

SubdomainFu aims to provide a simple, generic toolset for dealing with subdomains

in Rails applications. Rather than tie the functionality to something specific

like an account, SubdomainFu simply provides a foundation upon which any

subdomain-keyed system can easily be built.

Usage Fu

SubdomainFu works by riding on top of the URL Rewriting engine provided with

Rails. This way you can use it anywhere you normally generate URLs: through

url_for , in named routes, and in resources -based routes. There’s a small

amount of configuration that is needed to get you running (though the defaults

should work for most).

To set it up, you can modify any of these settings (the defaults are shown):

# in environment.rb # These are the sizes of the domain (i.e. 0 for localhost, 1 for something.com) # for each of your environments SubdomainFu.tld_sizes = { :development => 0, :test => 0, :production => 1 } # These are the subdomains that will be equivalent to no subdomain SubdomainFu.mirrors = ["www"] # This is the "preferred mirror" if you would rather show this subdomain # in the URL than no subdomain at all. SubdomainFu.preferred_mirror = "www" 1 # in environment.rb # These are the sizes of the domain (i.e. 0 for localhost, 1 for something.com) # for each of your environments SubdomainFu.tld_sizes = { :development => 0, :test => 0, :production => 1 } # These are the subdomains that will be equivalent to no subdomain SubdomainFu.mirrors = ["www"] # This is the "preferred mirror" if you would rather show this subdomain # in the URL than no subdomain at all. SubdomainFu.preferred_mirror = "www"

Now when you’re in your application, you will have access to two useful

features: a current_subdomain method and the URL Rewriting helpers.

The current_subdomain method will give you the current subdomain or

return nil if there is no subdomain or the current subdomain is a mirror:

# http://some_subdomain.myapp.com/ current_subdomain # => "some_subdomain" # http://www.myapp.com/ or http://myapp.com/ current_subdomain # => nil # http://some.subdomain.myapp.com current_subdomain # => "some.subdomain" 1 # http://some_subdomain.myapp.com/ current_subdomain # => "some_subdomain" # http://www.myapp.com/ or http://myapp.com/ current_subdomain # => nil # http://some.subdomain.myapp.com current_subdomain # => "some.subdomain"

The URL rewriting features of SubdomainFu come through a :subdomain option

passed to any URL generating method. Here are some examples (in these examples,

the current page is considered to be ‘http://intridea.com/’):

url_for(:controller => "my_controller", :action => "my_action", :subdomain => "awesome") # => http://awesome.intridea.com/my_controller/my_action users_url(:subdomain => false) # => http://intridea.com/users # The full URL will be generated if the subdomain is not the same as the # current subdomain, regardless of whether _path or _url is used. users_path(:subdomain => "fun") # => http://fun.intridea.com/users users_path(:subdomain => false) # => /users 1 url_for ( : controller =& gt ; "my_controller" , : action =& gt ; "my_action" , : subdomain =& gt ; "awesome" ) # => http://awesome.intridea.com/my_controller/my_action users_url(:subdomain => false) # => http://intridea.com/users # The full URL will be generated if the subdomain is not the same as the # current subdomain, regardless of whether _path or _url is used. users_path(:subdomain => "fun") # => http://fun.intridea.com/users users_path(:subdomain => false) # => /users

While this is just a simple set of tools, it can allow the easy creation

of powerful subdomain-using tools. Note that the easiest way to locally

test multiple subdomains on your app is to edit /etc/hosts and add

subdomains like so:

127.0.0.1 localhost subdomain1.localhost subdomain2.localhost www.localhost 1 127.0.0.1 localhost subdomain1 . localhost subdomain2 . localhost www . localhost

Adding an entry for each subdomain you want to use locally. Then you need

to flush your local DNS cache to make sure your changes are picked up:

sudo dscacheutil -flushcache 1 sudo dscacheutil - flushcache

Installation

SubdomainFu is available both as a traditional plugin and as a GemPlugin

for Rails 2.1 and later. For a traditional plugin, install like so:

script/plugin install git://github.com/mbleigh/subdomain-fu.git 1 script / plugin install git : //github.com/mbleigh/subdomain-fu.git

For a GemPlugin, add this dependency to your environment.rb :

config.gem 'mbleigh-subdomain-fu', :source => "http://gems.github.com/", :lib => "subdomain-fu" 1 config . gem 'mbleigh-subdomain-fu' , : source =& gt ; "http://gems.github.com/" , : lib =& gt ; "subdomain-fu"

Implementing A Simple Account Key System

Let’s take this functionality and implement a simple account-key system based

off of the subdomain. We’ll start with some controller code (assuming that

we have an Account model with a ‘subdomain’ field):

class ApplicationController < ActionController::Base protected # Will either fetch the current account or return nil if none is found def current_account @account ||= Account.find_by_subdomain(current_subdomain) end # Make this method visible to views as well helper_method :current_account # This is a before_filter we'll use in other controllers def account_required unless current_account flash[:error] = "Could not find the account '#{current_subdomain}'" redirect_to :controller => "site", :action => "home", :subdomain => false end end end 1 class ApplicationController & lt ; ActionController :: Base protected # Will either fetch the current account or return nil if none is found def current_account @account ||= Account.find_by_subdomain(current_subdomain) end # Make this method visible to views as well helper_method :current_account # This is a before_filter we'll use in other controllers def account_required unless current_account flash[:error] = "Could not find the account '#{current_subdomain}'" redirect_to :controller => "site", :action => "home", :subdomain => false end end end

That’s really all we need for a basic setup, now let’s say we have a

ProjectsController that you must specify an account to access:

class ProjectsController < ApplicationController # Redirect users away if no subdomain is specified before_filter :account_required end 1 class ProjectsController & lt ; ApplicationController # Redirect users away if no subdomain is specified before_filter :account_required end

There’s lots more you can do with the plugin, but this is a simple use case

that everyone can relate to.

Resources and Plans

A feature that I hoped would make it to the first release of SubdomainFu

but is now a planned feature is subdomain-aware routing so that you can

add conditional subdomain routes to your routes.rb file. Keep an eye

out for more on that in the future.

In the meantime, the project will live at its home on Acts As Community for intermittent

updates, is available on GitHub as always, and bugs/feature requests may

be passed on through the Lighthouse.