Rails routing to handle multiple domains on single application
Asked Answered
C

3

94

I've been unable to find a workable solution to this problem, despite several similar questions here and elsewhere. It seems likely that this question hasn't been answered for Rails 3, so here goes:

I have an application that currently allows users to create their own subdomain that contains their instance of the application. While in Rails 2 you were best served using the subdomain-fu gem, in version 3 it's dramatically simpler, as per the Railscast -- http://railscasts.com/episodes/221-subdomains-in-rails-3.

That's good stuff, but I also want to provide the option for users to associate their own domain name with their account. So while they might have http://userx.mydomain.com, I'd like them to choose to have http://userx.com associated as well.

I found a few references to doing this in Rails 2, but those techniques don't appear to work anymore (particularly this one: https://feefighters.com/blog/hosting-multiple-domains-from-a-single-rails-app/).

Can anyone recommend a way to use routes to accept an arbitrary domain and pass it along to a controller so I can show the appropriate content?

Update: I've gotten most of an answer now, thanks to Leonid's timely response, and a fresh look at the code. It ultimately required an addition to the existing Subdomain code that I was using (from the Railscast solution) and then adding a bit to routes.rb. I'm not all the way there yet but I want to post what I have so far.

In lib/subdomain.rb:

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

class Domain
  def self.matches?(request)
    request.domain.present? && request.domain != "mydomain.com"
  end
end

I've added the second class in imitation of the first, which is known working. I simply add a condition that ensures that the incoming domain is not the one for which I'm hosting the main site.

This class is used in routes.rb:

require 'subdomain'
constraints(Domain) do
  match '/' => 'blogs#show'
end

constraints(Subdomain) do
  match '/' => 'blogs#show'
end

Here, I'm prepending the existing subdomain code (again, it's working fine) with a stanza to check for the Domain. If this server responds to that domain and it's not the one under which the main site operates, forward to the specified controller.

And while that appears to be working, I don't quite have the whole thing working yet, but I think this particular problem has been solved.

Calcic answered 17/11, 2010 at 18:14 Comment(3)
Thanks so much for your edit, Aaron. I'm dealing with the exact same situation right now. As a follow-up question, how do you get your server to accept any domain that is being forwarded to it? I assume it would be a setting in the .conf file, but I'm not sure what. Any help would be appreciated!Beseem
Aaron, I'm with you. I want to do the same thing. But I don't want to hardcode the domain. I want it all done programmatically without zone files and web server restarts.Mohock
Michael, you need to flip the problem around. Explicitly declare & hardcode the routes that are exclusively for your application (e.g. sign-up) with a host or subdomain constraint, then treat your main routes as "any domain or subdomain". It's then the responsibility of your controllers to lookup the current domain or subdomain and map it to the right customer.Meteoric
H
98

It's actually simpler in Rails 3, as per http://guides.rubyonrails.org/routing.html#advanced-constraints:

1) define a custom constraint class in lib/domain_constraint.rb:

class DomainConstraint
  def initialize(domain)
    @domains = [domain].flatten
  end

  def matches?(request)
    @domains.include? request.domain
  end
end

2) use the class in your routes with the new block syntax

constraints DomainConstraint.new('mydomain.com') do
  root :to => 'mydomain#index'
end

root :to => 'main#index'

or the old-fashioned option syntax

root :to => 'mydomain#index', :constraints => DomainConstraint.new('mydomain.com')
Hhd answered 19/1, 2011 at 15:20 Comment(8)
Thanks for your answer... I had given up hope! But looking at this code, it appears that I need to anticipate the domain being used. I want to use any arbitrary domain. But I've now discovered the answer (mostly), so I'll edit my question above.Calcic
Why would you declare the class in an initializer? Shouldn't that be in lib?Deberadeberry
This answer seems much simpler to me.Waynant
This is a great solution. How does it work with a development environment?Edp
@Edp it works perfectly fine if you set up local domains for development (for example, via /etc/hosts).Hhd
Note: if you use Pow locally and have mydomain.com.dev, then request.domain returns .com.dev. Change request.domain to request.host and it works perfectly.Hodman
I've found that I have to create unnamed routes for this to work, otherwise I get the Invalid route name, already in use: 'root' error ... To do this, I changed the route to root :to => 'mydomain#index', as: nilSupernatural
I made one minor change to def matches?, from "request.domain" to "request.host" to be specific to the subdomain.Nf
B
11

In Rails 5, you can simply do this in your routes:

constraints subdomain: 'blogs' do
  match '/' => 'blogs#show'
end
Breannebrear answered 8/8, 2018 at 16:53 Comment(2)
if its not subdomain but just domain?Rosenquist
see @Mohd.Zafranudin's answer. It is built into Rails.Projectile
F
3

To extend @user3033467 answer, you are not limited to subdomain.

As per the docs:

For my specific case, I need both constraint for "domain" and "subdomain".

So this works for me:

constraints host: %w[domain.com] do

end

For subdomain:

constraints subdomain: %w[subdomain www-subdomain] do

end

You can even go as far as specifying the ports too.

constraints subdomain: %w[subdomain www-subdomain], ports: 3000 do

end

For future reference, here's the full list you can pass in (click for full size):

enter image description here

Frump answered 5/10, 2023 at 9:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.