Маршрутизация внутри приложений на Ruby on Rails 3

ноября 22, 2010  |  Published in Ruby on Rails, Ruby on Rails 3  |  6 Comments

ruby on rails routingДанная статья написана в продолжение серии статей посвященным переходу на Ruby on Rails 3. Для тех же людей, которые только знакомятся с фреймворком Ruby on Rails, эта серия статей также может послужить не плохим руководством.

Другие статьи из этой серии:
Active Record запросы в Ruby on Rails 3

В Ruby on Rails 3 изменения коснулись не только ActiveRecord, но и маршрутизации. Для начала давайте определимся с терминологией: Маршрутизация или роутинг – это набор правил, которые определяют, к какому ресурсу получит доступ пользователь, перейдя по определенному адресу URL. Роут — одно правило маршрутизации (роутинга).

Для того, чтобы показать, как же работает маршрутизация, давайте создадим новое приложение Rails с именем detour, и зададим для него некоторые роуты:

rails detour

Теперь, когда приложение Ruby on Rails создано, мы можем в папке с приложением открыть файл config/routes.rb, в котором описываются правила маршрутизации нашего приложения. По умолчанию файл настройки роутинга содержит небольшое пояснение к работе с маршрутизацией и примеры нового API. Авторы оригинала статьи и я — ее переводчик рекомендуем вам уделить несколько минут ознакомлению с представленными в routes.rb примерами.

Мы заменим содержимое routes.rb примером старой реализации роутинга в Ruby on Rails и его аналогом написанным согласно стилю Ruby on Rails 3. Итак, как же маршрутизация выглядела бы в Rails 2.x:

Detour::Application.routes.draw do |map|
  map.resources :products, :member => { :detailed => :get }

  map.resources :forums, :collection => 
    { :sortable => :get, :sort => :put } do |forums|
    forums.resources :topics
  end

  map.root :controller => "home", :action => "index"

  map.about "/about", :controller => "info", :action => "about"
end

Начнем с первого правила маршрутизации представленной выше:

map.resources :products, :member = { :detailed => :get }

Это правило маршрутизации имеет ресурс products и member — специальный дополнительный экшен, который в данном примере называется detailed, и вызывается посредством GET – запроса.

Первое, отличие в маршрутизации на Ruby on Rails 3, которое бросается в глаза это то, что мы больше не имеем дела с объектом map, вместо этого вызывается resources непосредственно в блоке routes.draw. Экшены для :member и :collection внутри resources объявляются в блоке. Данные правила маршрутизации для Ruby on Rails 3 приобретут следующий вид:

resources :products do
  get :detailed, :on => :member
end

Далее следует более сложный роут:

map.resources :forums, :collection =>
   { :sortable => :get, :sort => :put } do |forums|
  forums.resources :topics
end

В данном роуте мы имеем ресурс forum с двумя дополнительными пунктами в collection и вложенный ресурс topics. С новым API это правило маршрутизации приобретет следующий вид:

resources :forums do
  collection do
    get :sortable
    put :sort
  end
  resources :topics
end

Вновь мы используем resources вместо map.resources и передаем в него блок. В данном роуте мы имеем два экшена в блоке collection. Мы можем сделать это и в тот же способ, в который мы объявили роут для экшена detailed в первом примере, где используется параметр :on вместо блока collection (члены ресурса могут определятся обсолютно так же и с использованием метода member). Все роуты определенные внутри блока collection будут действовать в forums. В нашем блоке мы объявили два новых действия sortable как GET-запрос и sort как PUT-запрос.

Для вложенного ресурса topics мы просто снова вызываем resources, но уже внутри роута для forums, влаживая ресурс topics внутрь forums.

В следующем примеры вы можете увидеть роут, который указывает, какой контроллер и какой экшен будет представлен на главной странице (в корне сайта — root):

map.root :controller => "home", :action => "index"

В Rails 3 мы можем просто вызвать root и использовать аргумент :to, через который мы передаем строку, которая содержит имя контроллера и экшен разделенные символом «решетки — #»:

root :to => "home#index"

Это одно из нововвведений в Ruby on Rails 3 — указывать контроллер и экшен с помощью строк типа: «controller#action». В Ruby on Rails мы также можем сделать что-то похожее для именованного роута:

map.about "/about", :controller => "info", :action => "about"

В Ruby on Rails 3 этот роут может быть переписан следующим образом:

match "/about" => "info#about", :as => :about

Без аргумента :as вышеописанный роут будет просто безымянным роутом. Добавленный параметр :as делает его именованным роутом, таким образом давая нам возможность использовать хэлперы about_path или about_url в нашем приложении.

Новые возможности в маршрутизации в Ruby on Rails 3

Как вы уже заметили в примерах выше, преобразовать роуты со старым API в роуты с новым не сложно, однако фактором, который делает новый API маршрутизации действительно интересным, являются новые возможности, которые новое API Ruby on Rails 3 предоставляет нам. Мы потратим оставшуюся часть статьи на обзор некоторых из них.

Опциональные параметры
Опциональные параметры поддерживались в предыдущих версиях Ruby on Rails, однако синтаксис был немного неуклюжий. Давайте посмотрим, как они выглядят в Ruby on Rails 3.

Для наглядности вам необходимо создать контроллер info в котором будет заключен экшен about. Обратите внимание на то, что в Ruby on Rails 3 мы можем использовать rails g как более короткий синоним для команды rails generate.

rails g controller info about

Мы можем запустить сервер нашего приложения другой сокращенной командой:

rails s

Теперь если мы перейдем по адресу http://localhost:3000/about мы увидем экшен info#about, так как в примерах выше мы уже создали правило маршрутизации для него.

Info#about
Find me in app/views/info/about.html.erb

Вместе с этим мы хотим предоставить пользователям PDF –версию этого экшена, однако, если мы перейдем на http://localhost:3000/about.pdf мы получим ошибку маршрутизации так как наше приложение не знает как обработать данный запрос.

В файле настройки маршрутизации мы можем изменить роут about на доступ к параметру формата добавив точку и :format:

match "/about.:format" => "info#about", :as => :about

Теперь, если мы перезагрузим PDF-представление, роут будет обработан, однако мы увидем шаблон missing error т.к. наше приложение не знает как рендерить этот экшен в PDF.

Template is missing
Missing template info/bout with {:formats=>[]} in view path
/User/eifion/rails/apps_for_asciicasts/ep203/detour/app/views

Это выглядит так, как будто мы исправили проблему, но формат является обязательным, а не опциональным, таким образом когда мы переходим в представление по умолчанию для этой страницы мы получим ошибку маршрутизации. Все, что нам необходимо сделать, чтобы подчеркнуть необязательность параметра — это заключить его в круглые скобки:

match "/about(.:format)" => "info#about", :as => :about

Теперь мы можем посещать http://localhost:3000/about и http://localhost:3000/about.pdf без наблюдения ошибок маршрутизации.

Далее вы узнаете как использовать более сложные опциональные параметры. Давайте представим, что наше приложение — блог, который имеет набор постов, которые мы бы хотели иметь возможность фильтровать по году с опциональным указанием месяца (или месяца и дня) в URL. Мы можем сделать это объявив роут с указанием соответствующих параметров. Запомните, что необязательные параметры следует влаживать в круглые скобки.

match "/:year(/:month(/:day))" => "info#about"

Давайте добавим в код представления некоторый код для дебага, который будет показывать нам, какой параметр был передан:

Info#about

Find me in app/views/info/about.html.erb

<%= debug params %>

Результат работы вставки кода для дебага:

Info#about
Find me in app/views/info/about.html.erb
— !map:ActiveSupport::HashWithIndifferentAccess
controller: info
action: about
year: «1984″
month: «05″
day: «15″

Этот роут не проверяет переданные параметры, благодаря чему мы можем перейти по адресу http://localhost:3000/foo/bar и получить year: foo и month: bar. Чтобы осуществить проверку URL необходимо воспользоваться ограничениями.

Ограничения
Ограничения доступны еще в Ruby on Rails 2.х где они были известны как требования. Мы можем передать опцию :constraints в наш роут с хэшем из ключей — параметров и значений — ограничений, которым должно соответствовать каждый соответствующий параметр URL. Мы ограничим параметры роута таким образом, что им будут соответствовать только численные значения, для года это 4 цыфры, а для месяца и дня — 2:

match "/:year(/:month(/:day))" => "info#about", :constraints => 
{ :year => /\d{4}/, :month => /\d{2}/, :day => /\d{2}/ }

С этим ограничением при посещении адреса /foo/bar мы получим ошибку маршрутизации так как переданные параметры не соответствуют описанному выше формату параметров.

Routing Error
No rote metches «/foo/bar»

Все, что мы сдесь сделали с помощью ограничений Ruby on Rails 3 возможно и в Ruby on Rails 2.x, однако разработчики Ruby on Rails3 подхотовили нам много интересных возможностей, которые ранее были недоступны. Например, мы можем использовать параметр user_agent для ограничения браузеру, в нашем случае Firefox:

match "/secret" => "info#about", :constraints => { :user_agent => /Firefox/ }

Если мы посетим http://localhost:3000/secret в Safari, Chrome или Opera мы увидим ошибку маршрутизации, однако если мы используем firefox, или другой браузер идентифицирующий себя как firefox, то параметр user_agent передаваемый от браузера будет соответствовать ограничениям и мы увидим следущую страницу:

Info#about
Find me in app/views/info/about.html.erb
— !map:ActiveSupport::HashWithIndifferentAccess
controller: info
action: about

Мы также можем добавить более полезные ограничения, скажем для host:

match "/secret" => "info#about", :constraints => { :host => /localhost/ }

Это ограничение означает, что вы можете посетить http://loacalhost:3000/secret и увидеть страницу, но вы получите ошибку маршрутизации если вы попытаетесь посетить http://127.0.0.1/secret даже при том, что оба адреса эквивалентны. Этот параметр может быть использован для ограничения определенных роутов по субдомену, но эта возможность, в настоящее время, немного неуклюжа, тем не менее в следующих версиях Ruby on Rails будет возможна передача опции :subdomain для выполнения этого типа ограничения.

Если у вас есть некоторое количество роутов что соответствуют определенному ограничению, то вам возможно придется дублировать один и тот же код несколько раз. Согласно принципу DRY, мы можем уменьшить эти повторения кода используя метод constraints и помещая соответствующие роуты в блок.

constraints :host => /localhost/ do
  match "/secret" => "info#about"
  match "/topsecret" => "info#about"
end

С помощью ограничений мы можем сделать много чего, однако все это разнообразие возможностей ограничений — тема другой статьи.

Маршрутизация с Rack
О маршрутизации в Ruby on Rails 3 можно рассказывать долго, но мы вернемся к этому вопросу в одной из следующих статей. Одна из последних возможностей на которую мы обратим свое внимание – это то, как маршрутизация в Ruby on Rails 3 взаимодействует с Rack. Обычно мы передаем контроллер и имя экшена в роут, но мы также можем передавать и Rack Application, что является чрезвычайно мощной возможностью. Для демонстрации этого мы создадим роут, что определяет маршрутизацию для простого приложение Rack, которое определено непосредственно в файле конфигурации роутинга .

match "/hello" => proc { |env| [200, {}, "Hello Rack!"] }

Если вы еще не знакомы с Rack, то для знакомства можете прочитать две следующие статьи: Дружимся с Rack #1: Hello Rack! и Дружимся с Rack #2: Rack::Builder. Все что мы делаем выше это передача кода ответа сервера, пустой хэш http-заголовков и простое тело документа.

Если мы сейчас посетим адрес http://localhost/hello в браузере, то мы увидим «Hello, Rack!», таким образом мы убеждаемся в том, что наше приложение Rack работает. Эта возможность наделяет ваше приложение на Ruby on Rails 3 большой гибкостью и мощностью, например вы можете сделать роут на ваше приложение написанное с использованием фреймворка Sinatra.

Нововведения в маршрутизации Ruby on Rails 3 предоставляют множество новых возможностей и улучшений старых. Эта статья не претендует на полноту изложения материала касательно маршрутизации в приложениях на Ruby on Rails 3, и я обязательно напишу или переведу статью посвященную этой теме.

Оригинал статьи на английском здесь:

Tags:

Responses

  1. Andrey says:

    января 23, 2011 at 01:56 (#)

    спасибо за хорошую статью.
    мне кажется или что-то пропущено?
    «Если вы еще не знакомы с Rack, то для знакомства можете {тут что-то должно быть} Все что мы делаем выше это передача кода ответа сервера, пустой хэш http-заголовков и простое тело документа.»

  2. admin says:

    января 23, 2011 at 02:14 (#)

    Спасибо за спасибо и за то, что указали на пропуск. Добавил ссылки на статьи по работе с Rack.

  3. Artem says:

    февраля 28, 2011 at 07:58 (#)

    Допустим стоит такая задача: все запросы к админке должны пересылаться на экшн вида admin_{action}.

    Вот такая конструкция не работает:
    match ‘/admin/:controller(/:action)’ => ‘%{controller}#admin_%{action}’
    Я так понял в этот шаблон ‘controller#action’ вообще ничего динамического запихнуть нельзя. Что можно сделать?

  4. Aigool says:

    марта 31, 2013 at 23:57 (#)

    Подскажите, как правильно настроить роутинг в случае, если у меня есть блог с главной страницей, на которой выводятся все посты всех пользователей. Так же нужна возможность, чтобы авторизованный пользователь мог посмотреть только свои посты. Могу я передать в контроллер параметр, допустим my_posts, чтобы загрузилась та же страница, но только с постами пользователя?

    Контроллер

    def index
        if params[my_posts]
            @calendars = Calendar.where(:user_id => current_user.id)
        else 
            @calendars = Calendar.all
        end
    
        respond_to do |format|
          format.html # index.html.erb
          format.json { render json: @calendars }
        end
      end
    
  5. admin says:

    мая 4, 2013 at 08:55 (#)

    resources :users do
      resources :posts
    end
    

    Тогда получим возможность обращаться к постам конкретного пользователя по: users/:user_id/posts

  6. igor says:

    июня 20, 2013 at 15:48 (#)

    Подскажите как можно сделать так чтобы в адресной строке было следующее
    сайт.ru/название_статьи_1
    сайт.ru/название_статьи_2

    сайт.ru/название_статьи_n

    сейчас ссылки вида сайт.ru/статии/1

Leave a Response

Для подсветки кода используйте BB - коды: [language]...[/language], где language может быть: ruby, javascript, css, html.