Как стать автором
Обновить

Поддомены в Rails

Ruby on Rails
Tutorial
Думаю каждый кто работал с под-доменами в Rails 3 видел данный скринкаст.

Когда столкнулся с этим примером стояла задача сделать динамические поддомены + некоторые фиксированные. Динамические должны были соответствовать некоторому полю одной из моделей. Таким образом были выдвинуты условия для конечного решения:
  • возможность указания фиксированного поддомена, либо группы поддоменов;
  • возможность привязки к полю из модели ActiveRecord;
  • удобный синтаксис для записи всего этого в routes.rb.

Реализация


Для реализации первого пункта за основу был взят выше указанный пример и расширен возможностью передачи своего имени поддомена или их массива.
subdomain/base.rb
module Subdomain
  class Base
    attr_accessor :subdomain
    def initialize(param = ["www"] )
      @subdomains = case param.class.to_s
      when "String", "Symbol"
        [param]
      when "Array"
        param
      else
        []
      end
    end

    def matches?(request)      
      self.subdomain?( request ) && ( @subdomains.map {|i| i.to_s == request.subdomain }.include? true )
    end

    protected

    def subdomain?(request)
      request.subdomain.present? and ( request.subdomain != "www" )
    end
  end
end

Думаю объяснять тут ничего не надо.
Далее дошла очередь и для реализации второго пункта.
В данном классе в методе проверки совпадения происходит получения экземпляра класса по его имени. Далее как проверяем наличие поддомена в таблице.
subdomain/active_record.rb
class Subdomain::ActiveRecord < Subdomain::Base
  attr_reader   :model
  attr_accessor :field
  
  def initialize(params)
    p = params.first
    @model = p[1]
    @field = p[0]
  end

  def matches?(request)
    obj = case @model.class    
    when String
      @model.classify.constantize
    when Symbol
      @model.to_s.classify.constantize
    else
      @model
    end    
    
    subdomain?(request) and
     ( obj.superclass == ActiveRecord::Base ) and
     obj.where(field.to_sym => request.subdomain).first.present?

  end
end

И третьим этапом было упрощение записи правил в routes.rb. Если использовать тот вариант что предлагает пример, то получались довольно громоздкие записи и не совсем понятные на первый взгляд.
constraints( Subdomain::ActiveRecord.new Site::User, :login ) do
  root :to => "main#index"
end

constraints( Subdomain::Base.new [:admin] ) do
  scope :module => "admin", :as => :admin  do
    root :to => "main#index"
  end
end

Чтобы добавить свой метод необходимо расширить класс, отвечающий за пути, а именно ActionDispatch::Routing::Mapper
Для этого был написан модуль, который примешивался к основному классу в процессе загрузки приложения.
config/initializers/subdomain.rb
ActionDispatch::Routing::Mapper.send(:include, Subdomain::Extension)

subdomain/extension.rb
module Subdomain
  module Extension
    def subdomain( sub, params = {} )

      if params[:scope]
        scope :module => params[:scope].to_s, :as => params[:scope].to_sym  do
          unscoped_subdomain( sub )  { yield }
        end
      else
        m = case sub.class.to_s
        when "Symbol", "String"
          sub.to_s
        when "Array"
          sub.first
        when "Hash"
          nil
        end
        if m.blank? or params.key?(:scope)
          unscoped_subdomain( sub )  { yield }
        else
          scope :module => m, :as => m.to_sym  do
            unscoped_subdomain( sub ) { yield }
          end
        end
      end
    end

    def unscoped_subdomain( sub )
      case sub.class.to_s
      when "Symbol", "String", "Array"
        constraints( Subdomain::Base.new sub ) { yield }
      when "Hash"
        p = sub.first
        inst = "subdomain/#{p[0].to_s}".classify.constantize
        constraints( inst.new p[1] ) { yield }
      end
    end
  end
end


После чего указанные ранее конструкции были заменены на следующие:
subdomain( :active_record => { :login => Site::user } ) do
  root :to => "main#index"
end

subdomain( :admin ) do
  root :to => "main#index"
end

#равносильно
subdomain( :base => :admin ) do
  root :to => "main#index"
end

Результат


В итоге получился довольно удобный интерфейс для работы с поддоменами.
subdomain( "subdomain" ) do
    root :to => "main#index"# controller => subdomain/main     path => subdomain_root
end

subdomain( "subdomain", :scope => "probe" ) do
    root :to => "main#index"# controller => probe/main      path => probe_root
end

subdomain( "subdomain", :scope => false ) do
    root :to => "main#index"# controller => main
end

subdomain( ["subdomain1", "subdomain2"], :scope => "test" ) do
    root :to => "main#index" # controller => test/main
end

subdomain( ["subdomain1", "subdomain2"] , :scope => false ) do
    root :to => "main#index" # controller => main
end

subdomain(:имя_класса => параметры ) do
    root :to => "main#index"# controller => main
end

#для ActiveRecord
subdomain( :active_record => { :имя_поля => модель } ) do
    root :to => "main#index"# controller => main
end

subdomain( :active_record => { :имя_поля => модель }, :scope => "models" ) do
    root :to => "main#index"# controller => models/main
end

Конструктивная критика и советы приветствуются. Заранее приношу извинения за местами кривой код.

PS: файлы доступны в архиве.
PPS: касательно подсветки синтаксиса, в предпросмотре все есть, в конечном варианте почему-то нет.
Теги:ruby on railsrails 3subdomain
Хабы: Ruby on Rails
Всего голосов 34: ↑31 и ↓3 +28
Просмотры6.1K

Похожие публикации

UI-дизайнер
25 июня 202147 940 ₽Нетология
Python для работы с данными
25 июня 202131 500 ₽Нетология
Веб-дизайнер
28 июня 202183 000 ₽GeekBrains
Node.js: серверный JavaScript
28 июня 202127 000 ₽Loftschool

Лучшие публикации за сутки