Routage de rails pour gérer plusieurs domaines sur une seule application

90

J'ai été incapable de trouver une solution viable à ce problème, malgré plusieurs questions similaires ici et ailleurs. Il semble probable que cette question n'ait pas été répondue pour Rails 3, alors voici:

J'ai une application qui permet actuellement aux utilisateurs de créer leur propre sous-domaine contenant leur instance de l'application. Alors que dans Rails 2, vous étiez mieux servi en utilisant le gem subdomain-fu, dans la version 3, c'est considérablement plus simple, selon le Railscast - http://railscasts.com/episodes/221-subdomains-in-rails-3 .

C'est une bonne chose, mais je souhaite également offrir aux utilisateurs la possibilité d'associer leur propre nom de domaine à leur compte. Donc, bien qu'ils puissent avoir http://userx.mydomain.com , j'aimerais qu'ils choisissent d' associer http://userx.com également.

J'ai trouvé quelques références à faire cela dans Rails 2, mais ces techniques ne semblent plus fonctionner (en particulier celle-ci: https://feefighters.com/blog/hosting-multiple-domains-from-a-single-rails -app / ).

Quelqu'un peut-il recommander un moyen d'utiliser des routes pour accepter un domaine arbitraire et le transmettre à un contrôleur afin que je puisse afficher le contenu approprié?

Mise à jour : j'ai obtenu la plupart des réponses maintenant, grâce à la réponse rapide de Leonid et à un nouveau regard sur le code. Il a finalement fallu un ajout au code de sous-domaine existant que j'utilisais (à partir de la solution Railscast), puis en ajoutant un peu à routes.rb. Je ne suis pas encore tout à fait là-bas, mais je veux publier ce que j'ai jusqu'à présent.

Dans 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

J'ai ajouté la deuxième classe en imitant la première, qui est connue pour fonctionner. J'ajoute simplement une condition qui garantit que le domaine entrant n'est pas celui pour lequel j'héberge le site principal.

Cette classe est utilisée dans routes.rb:

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

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

Ici, je préfère le code de sous-domaine existant (encore une fois, cela fonctionne bien) avec une strophe pour vérifier le domaine. Si ce serveur répond à ce domaine et que ce n'est pas celui sous lequel le site principal fonctionne, transférez-le au contrôleur spécifié.

Et bien que cela semble fonctionner, je n'ai pas encore tout à fait fonctionné, mais je pense que ce problème particulier a été résolu.

Aaron Vegh
la source
1
Merci beaucoup pour votre modification, Aaron. Je suis confronté exactement à la même situation en ce moment. En guise de question complémentaire, comment faites-vous pour que votre serveur accepte tout domaine qui lui est transféré? Je suppose que ce serait un paramètre dans le fichier .conf, mais je ne sais pas quoi. Toute aide serait appréciée!
deadwards
Aaron, je suis avec toi. je veux faire la même chose. Mais je ne veux pas coder en dur le domaine. Je veux que tout soit fait par programme sans fichiers de zone et redémarrage du serveur Web.
Michael K Madison
1
Michael, tu dois renverser le problème. Déclarez et codez explicitement les routes qui sont exclusivement pour votre application (par exemple, inscription) avec une contrainte d'hôte ou de sous-domaine, puis traitez vos routes principales comme "n'importe quel domaine ou sous-domaine". Il est alors de la responsabilité de vos contrôleurs de rechercher le domaine ou sous-domaine actuel et de le mapper au bon client.
Justin French

Réponses:

95

C'est en fait plus simple dans Rails 3, selon http://guides.rubyonrails.org/routing.html#advanced-constraints :

1) Définissez une classe de contraintes personnalisée dans lib/domain_constraint.rb:

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

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

2) utilisez la classe dans vos routes avec la nouvelle syntaxe de bloc

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

root :to => 'main#index'

ou la syntaxe d'option à l'ancienne

root :to => 'mydomain#index', :constraints => DomainConstraint.new('mydomain.com')
Léonid Chevtsov
la source
6
Cette réponse me paraît beaucoup plus simple.
Jared
7
C'est une excellente solution. Comment ça marche avec un environnement de développement?
superluminaire du
2
@superluminary cela fonctionne parfaitement bien si vous configurez des domaines locaux pour le développement (par exemple, via /etc/hosts).
Leonid Shevtsov
7
Remarque: si vous utilisez Pow localement et que vous avez mydomain.com.dev, request.domainrenvoie .com.dev. Changez request.domainpour request.hostet cela fonctionne parfaitement.
Eric Muyser
2
J'ai constaté que je devais créer des itinéraires sans nom pour que cela fonctionne, sinon j'obtiens l' Invalid route name, already in use: 'root'erreur ... Pour ce faire, j'ai changé l'itinéraire enroot :to => 'mydomain#index', as: nil
Just Lucky Really
5

Dans Rails 5, vous pouvez simplement le faire dans vos itinéraires:

constraints subdomain: 'blogs' do
  match '/' => 'blogs#show'
end
user3033467
la source