Rails 3.0 introduced support for routing constrained by subdomains.
A subdomain can be specified explicitly, like this:
match '/' => 'home#index', :constraints => { :subdomain => 'www' }
Or a set of subdomains can be matched using a regular expression:
match '/' => 'profiles#show', :constraints => { :subdomain => /.+/ }
Finally, for greatest flexibility, router constraints can also take objects, allowing custom code.
Create a class like this:
lib/subdomain.rb
class Subdomain
def self.matches?(request)
case request.subdomain
when 'www', '', nil
false
else
true
end
end
end
This class allows use of a route when a subdomain is present in the request object. If the subdomain is www
, the class will respond as if a subdomain is not present.
Make sure the class is autoloaded when the application starts. You can require 'subdomain'
at the top of the config/routes.rb file. Or you can modify the file config/application.rb (recommended):
# config.autoload_paths += %W(#{config.root}/extras)
config.autoload_paths += %W(#{config.root}/lib)
Use this class when you create routes in the file config/routes.rb:
devise_for :users
resources :users, :only => :show
constraints(Subdomain) do
match '/' => 'profiles#show'
end
root :to => "home#index"
A match from a /
URL (such as http://myname.myapp.com) will route to the show
action of the Profiles
controller only when a subdomain is present. If a subdomain is not present (or is www
), a route with less priority will be applied (in this case, a route to the index
action of the home_controller.rb
).
Be sure to comment out (or remove) the route that was added by the Rails generator when we created the controller:
#get "profiles/show"
The rails3-mongoid-devise example app provides a home page that lists all registered users. We'll modify the home page to add a link to each user's profile page, using a URL with a custom subdomain.
app/views/home/index.html.haml
%h4 Home
- @users.each do |user|
%br/
User: #{link_to user.name, user}
Profile: #{link_to root_url(:subdomain => user.name), root_url(:subdomain => user.name)}
Applications that do not use subdomains use routing helpers to generate links that either include the site's hostname (for example, users_url
generates http://mysite.com/users
) or links that only contain a relative path (for example, users_path
generates /users
). To provide navigation between sites hosted on the subdomains and the main site, you must use URL helpers (users_url
) not path helpers (users_path
) because path helpers do not include a hostname. Rails 3.1 provides a way to include a subdomain as part of the hostname when generating links.
You can specify a hostname when creating a link, with the syntax:
root_url :subdomain => @subdomain
If you need a link to the main site (a URL without a subdomain), you can force the URL helper to drop the subdomain:
root_url :host => request.domain
Is there a better way to do this? Open an issue if you have a suggestion.
If you launch the application, it will be running at http://localhost:3000/
or http://0.0.0.0:3000/
. However, unless you've made some configuration changes to your computer, you won't be able to resolve an address that uses a subdomain, such as http://foo.localhost:3000/
.
There are several complex solutions to this problem. You could set up your own domain name server on your localhost and create an A entry to catch all subdomains. You could modify your /etc/hosts file (but it won't accommodate dynamically created subdomains). You can create a proxy auto-config (PAC) file and set it up as the proxy in your web browser preferences.
There's a far simpler solution that does not require reconfiguring your computer or web browser preferences. The developer Levi Cook registered a domain, http://lvh.me:3000/
(short for: local virtual host me), that resolves to the localhost IP address 127.0.0.1
and supports wildcards (accommodating dynamically created subdomains).