Subdomain Routes in Rails 4.2+
Written around 2015, published in 2026 from my old notes.
Recently I upgraded an old Rails 3.2 site to 4.2. The site serves many regional subdomains: one instance of the Rails app serves both region1.site.com and region99.site.com. By design, a page on one subdomain can link to pages on another. So this site had long used a “_url routes by default instead of _path routes” rule: when you generate a link, you must remember that it can lead to a neighboring subdomain.
To set the subdomain, the site used an old hack from Railscasts #221 Subdomains in Rails 3:
# Include in view helpers
module UrlHelper
def with_subdomain(subdomain)
subdomain = (subdomain || "")
subdomain += "." unless subdomain.empty?
[subdomain, request.domain, request.port_string].join
end
def url_for(options = nil)
if options.kind_of?(Hash) && options.has_key?(:subdomain)
options[:host] = with_subdomain(options.delete(:subdomain))
end
super
end
end
# Use in a view
<%= post_url(post, subdomain: region.subdomain) #=> http://region1.site.com/posts/1 %>
The hack works because in Rails 3 all routes are generated through this method: ActionView::Helpers::UrlHelper#url_for.
In Rails 4 a different method with the same name does a similar job (the generation of every route goes through it): ActionDispatch::Http::URL.url_for. And it turned out that the new Rails does not need these hacks at all: starting with version 4, route helpers support the subdomain: subdomain option.
I still had to hack a little, because we used a different variant: a region option did the job of the subdomain option. It took an object and got the subdomain from it. So instead of post_url(post, subdomain: region.subdomain) we could write post_url(post, region: region) — 13 characters shorter!
# lib/region_as_subdomain_url_patch.rb
require 'action_dispatch/http/url'
module ActionDispatch::Http::URL
class << self
alias_method :original_full_url_for, :full_url_for
def full_url_for(options)
if options[:params].is_a?(Hash)
if region = options[:params].delete(:region)
options[:subdomain] = region.subdomain
end
end
original_full_url_for(options)
end
end
end