Дополнительные материалы

Введение

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

Пункты для размышления

Постарайтесь ответить на предложенные вопросы. После выполнения задания попробуйте ответить на них ещё раз

  • Когда в маршрутизаторе следует использовать одиночный ресурс, а не множественные?
  • Какой маршрут "исчезает" при использовании одиночного ресурса (если вы выполните $ rake routes, их будет только 6)? Что пропадает из других маршрутов?
  • Для чего вы могли бы использовать вложенные маршруты?
  • В каком порядке вы указываете их соответствующие ID? Как они называются в params?
  • Зачем нужны маршруты "member"?
  • В чем сильное сходство маршрутов "member" и "collection"? В чем они немного различаются?
  • Как настроить перенаправление маршрута, которое передается вместе с любыми другими параметрами?
  • Как переименовать маршрут с помощью алиаса?
  • Для чего можно использовать вложенные или множественные макеты?
  • Как бы вы это реализовали (приблизительно)?
  • Как передать переменные между макетами?
  • Как использовать метод #yield для содержимого #content_for?
  • Что такое метапрограммирование?
  • Как использовать метод #send для вызова другого метода?
  • Как создать новый метод "на лету"?
  • В каком случае Ruby вызывает метод #method_missing?
  • Как вы можете обратить метод #method_missing в свою пользу?
  • Что такое шаблоны проектирования?
  • Что такое принципы SOLID?

Продвинутая маршрутизация

Сейчас вы знакомы с основами маршрутизации - превращением RESTful запросов, используя знакомые нам методы HTTP, в действия контроллера (либо используя метод #resources, или же напрямую вызывая их с помощью метода get). В 90% случаев вы будете использовать файл маршрутов именно для этих целей.. но остальные 10% включают такой набор возможностей, как например перенаправление напрямую из файла маршрутов, вложение маршрутов друг в друга или парсинг параметров из входящего запроса.

Одиночные ресурсы.

Должно быть вы уже сталкивались с ними, но не вникали в их суть. До этого мы говорили о ресурсах (таких как "posts" или "users"), которые состоят из множества объектов. В файле маршрутов, вы прописываете такие ресурсы строчкой вида resources :users.

Иногда же для некоторых ресурсов имеет смысл наличие только одного объекта. Например, у нас есть информационная панель, отображающая какую-то информацию при условии, что пользователь залогинился. Также у нас есть единственный шаблон этой панели, и ему необходимо отображать только ту информацию, которая принадлежит текущему пользователю.

В этом случае, для этой панели нет смысла в действии "index", так как она единственная (и изменяется в зависимости от того, кто залогинился). Также для остальных действий, которые обычно требуют ID для идентификации ресурса (как например для #show), необходимость в параметре id отпадает.

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

    # in config/routes.rb
    resource :dashboard

Обратите внимание, что слова "resource" и dashboard записаны в единственном числе. Этот момент вводит в заблуждение множество людей, которые опечатались, и вместо "resources" (что более распространено) напечатали "resource".

Команда $ rake routes для одиночного ресурса выдаст только 6 маршрутов (#index нам больше не пригодится), а также вы не увидите :id в маршрутах, например:

  new_dashboard  GET /dashboard/new(.:format)  dashboards#new

...сравните с записью множественных маршрутов:

  new_post  GET /posts/:id/new(.:format)  posts#new

Дополнительные ресурсы

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

Вложенные маршруты

Иногда имеет смысл поместить один ресурс внутри другого. Например, список уроков, подобных этим, логически укладывается в список курсов, и в итоге можно представить такой URL - http://example.com/courses/1/lessons/3. Достичь этого можно буквально вложив ресурс в блок другого ресурса, как в этом примере:

    # config/routes.rb
    TestApp::Application.routes.draw do
      resources :courses do
        resources :lessons
      end
    end

Теперь метод #resources принимает блок, содержащий набор маршрутов.

В таком URL надо указывать параметры :id ОБОИХ объектов. Команда $ rake routes выдаст здесь следующее:

    course_lesson  GET  /courses/:course_id/lessons/:id(.:format)  lessons#show

Помните, что запрос будет обрабатывать контроллер наиболее вложенного ресурса, и здесь будет параметр :id, который будет назван просто :id (любые параметры родительских ресурсов будут названы специфически, например в приведенном выше соучае - :course_id).

Подобным образом (как можно видеть в выводе $ rake routes) автоматически создаются и хелперы представлений. При использовании хелпера, подобного #course_lesson_path, будет необходимо указать по порядку оба параметра, например course_lesson_path(1,3).

Не увлекайтесь вложенностью! Если вы создадите более чем двухуровневую вложенность, что-то может различаться. Зачастую вы увидите, что только некоторые действия контроллера оказались вложенными - только те, которым необходим родительский ID для их уникальной идентификации. Например, вы можете вытащить нужный вам урок, зная его ID. Но чтобы собрать все уроки внутри определенного курса, вам нужен ID курса, так что они должны быть вложены. То же верно для создания уроков - для них необходимо указывать родителя:

    # config/routes.rb
    TestApp::Application.routes.draw do
      resources :courses do
        resources :lessons, :only => [:index, :create]
      end
    end

Даже если это выглядит сложновато, вы быстро с этим разберетесь, как только столкнетесь с такими маршрутами в своей практике. Если вы работаете с контроллером и понимаете, что вам необходим родительский ID, то это значит, что маршрут должен быть вложенным. В противном случае, вложенные маршруты вам не нужны. Все достаточно просто.

Добавление маршрутов к элементам и коллекциям

Иногда необходимо добавить не-RESTful маршрут в ресурс. Если вы желаете добавить маршрут к единственному элементу ресурса, используйте метод #member:

    # config/routes.rb
    TestApp::Application.routes.draw do
      resources :courses do
        member do
          get "preview"   # показ одного курса
        end
      end
    end

Маршрут ведет на действие courses#preview. Вы можете создавать такие маршруты в любом необходимом вам количестве.

Если необходимо добавить не-RESTful маршрут к целой коллекции (и при этом не указывать параметр :id, как например в действии index), то необходимо использовать метод #collection:

    # config/routes.rb
    TestApp::Application.routes.draw do
      resources :courses do
        member do
          get "preview"  # показ одного курса (требуется ID)
        end
        collection do
          get "upcoming"  # Показывается список *всех* курсов (ID не требуется)
        end
      end
    end

Маршрут upcoming будет вести на действие courses#upcoming и не будет принимать параметр :id.

Если что-то здесь для вас непонятно, попробуйте поиграться с такими маршрутами самостоятельно, и с помощью $ rake routes отследить происходящее за кулисами.

Перенаправления и динамические (Wildcard) маршруты

Вы можете создать URL для удобства пользователя, а также направить его на другой, уже имеющийся. Используйте перенаправление:

    # config/routes.rb
    TestApp::Application.routes.draw do
      get 'courses/:course_name' => redirect('/courses/%{course_name}/lessons'), :as => "course"
    end

Так, это становится интересным. Здесь мы используем метод #redirect для отправки одного маршрута в другой. Если маршрут простой, то ничего сложного здесь нет. Но если необходимо передавать исходные параметры, то необходимо будет потрудиться, чтобы поместить их %{здесь}. Обратите внимание на одинарные кавычки.

В примере выше, мы также переименовали маршрут для удобства, используя алиас с параметром :as. Это дает возможность использовать это имя таких методах, как хелперы #_path. И снова, если возникли вопросы, посмотрите результаты команды $ rake routes.

Дополнительно по макетам: Вложенные макеты и передача информации

В уроке о Представлениях мы познакомились с макетами представления, но другая тема включает рендеринг множественных макетов для одной страницы, что позволяет создавать уникальные разделы, использующие общую для сайта стилизацию (например в футере). Например, вы хотите, чтобы страницы пользователей имели отличную от домашней страницы стилизацию. Первой мыслью может быть создание различных наборов стилей для каждого макета, но помните, что файлопровод Rails их всегда объединяет в одно целое.

Лучшим способом тут будет сказать макету, чтобы он выполнил некую работу (ту, что вы могли бы заставить выполнить макет в обычном режиме), а затем отрендерить другой макет, используя метод render :template => "your_layout.html.erb". Здесь вы используете ваши макеты также, как вьюха использует свои парциалы.

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

К примеру, у вас есть макет для статичных страниц app/views/layouts/static_pages.html.erb. Этот файл будет рендериться по умолчанию (если он существует) для вьюх, создаваемых из контроллера StaticPagesController. Скажем, что вы хотите, чтобы ваши статичные страницы выглядели подобно остальным страницам сайта, но не хотите отображения навигационной панели сверху страницы.

Здесь можно заставить макет static_pages.html.erb вызвать application.html.erb, а также передать последнему код CSS, используя для этого метод #content_for, например:

    # app/views/layouts/static_pages.html.erb
    <% content_for :stylesheets do %>
      #navbar {display: none}
    <% end %>
    <%= render :template => "layouts/application" %>

Теперь надо изменить макет application.html.erb, чтобы он принимал и использовал передаваемое ему содержимое. Для этого, к примеру, можно добавить метод #yield:

    # app/views/layouts/application.html.erb
    ...
    <head>
      ...
      <style><%= yield :stylesheets %></style>
    </head>
    ...
    render :template => "static_pages.html.erb"
    ...

Когда вы применяете #yield к блоку содержимого, в данном случае к :stylesheets, метод помещает в него код из блока content_for. И в вышеприведенном примере, мы добавляем таким способом CSS стиль в макет приложения, сначала используя рендеринг application.html.erb, а затем метод #content_for передает CSS стиль в макет application.html.erb. В итоге получается так:

    # app/views/layouts/application.html.erb
    ...
    <head>
      ...
      <style> #navbar {display: none} </style>
    </head>
    ...

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

Метапрограммирование в Rails

Что такое "Метапрограммирование"? Это великолепная концепция, используемая в Rails, и вы также можете ее применять в своей работе. Основная идея здесь состоит в том, что ваше приложение или скрипт, выполняясь, создает функции или методы "на лету", а также вызывает их. Это одна из граней использования Ruby... можно сказать, что эта возможность "зашита" в язык. Здесь мы лишь слегка коснемся этой темы, но вам определенно следует изучить данный функционал самостоятельно, чтобы чувствовать себя комфортно с Rails.

Примером метапрограммирования в Rails являются хелперы маршрута. При первом запуске приложения, оно загружает файл config/routes.rb, который может содержать строку get "home" => "static_pages#home", и пользователи могут перейти на домашнюю страницу по адресу http://www.yoursite.com/home. Затем Rails создает для вас пару методов, включающих хелперы home_path и home_url. Это часть метапрограммирования!

Пример с маршрутам не совсем точен, так как вы написали файл routes.rb и, вероятно, захардкодили вызовы методов #home_path или #home_url, заранее зная о том, что должно произойти. Как насчет динамических ситуаций, где вы не знаете наперед, какой метод будет вызван?

Для выхода из положения, Ruby предоставляет метод #send. Если хотите выполнить какой-то метод на объекте, просто отправьте этот метод с помощью send вместе с любыми нужными вам аргументами. Пример такого использования в командной строке - сложение 1+2:

  > 1 + 2
  => 3
  > 1.send(:+, 2)
  => 3

Обычно в таком нет необходимости, но #send помогает, если вы не знаете заранее, какой метод будет необходимо вызвать. Просто передайте символизированное имя нужного вам метода для выполнения на объекте, и Ruby будет его искать.

Но все же как определить новый метод "на лету"? Здесь пригодится метод #define_method, который принимает символ того, что необходимо определить, и блок, представляющий сам метод. Последующие примеры взяты из этой инструкции по метапрограммированию:

    class Rubyist
      define_method :hello do |my_arg|
        my_arg
      end
    end
    obj = Rubyist.new
    puts(obj.hello('Matz')) # => Matz

Еще одним мощным инструментом является метод #method_missing. Вы, конечно, видели ошибки вида "Эй, ты вызываешь метод, которого не существует!", и в стеке при этом будет содержаться что-то содержащее method_missing. Источником ошибки в данном случае наверняка была опечатка или неверное название метода.

method_missing - это метод класса BasicObject, который наследуется в каждом объекте Ruby и срабатывает всегда, когда вы пытаетесь вызвать несуществующий у объекта метод. Он также принимает все посланные вами аргументы и любой передаваемый блок. Это значит, что вы можете переписать метод #method_missing для выбранного объекта и использовать все, что было вызвано, например для выдачи сообщения об имени вызванного метода и его аргументах:

    class Rubyist
      def method_missing(m, *args, &block)
        str = "Called #{m} with #{args.inspect}"
        if block_given?
          puts str + " and also a block: #{block}"
        else
          puts str
        end
      end
    end
  > Rubyist.new.anything
  Called anything with []
  => nil

  > Rubyist.new.anything(3, 4) { "something" }
  Called anything with [3, 4] and also a block: #<Proc:0x007fa0261d2ae0@(irb):38>
  => nil

Метапрограммирование очень изящно, и есть куча интересных примеров его применения. Нет нужды полностью овладевать этим умением изучая Rails, так что изучите его, когда будете чувствовать в Rails достаточно комфортно, это очень пригодится вам в реальных проектах. Там вы увидите все разнообразие применения метапрограммирования, но подробности этого сейчас выходят за рамки нашего обучения.

Здесь хороший пример простого метапрограммирования для приведения вашего кода к принципу DRY.

Если вы заинтересовались, посмотрите Метапрограммирование в Ruby.

Шаблоны проектирования (паттерны проектирования)

Паттерны проектирования имеют неоднозначную оценку среди разработчиков ПО. С одной стороны они представляют "best practices" для каких-то ситуаций (не конкретный код, а просто шаблон, использующийся для решения каких-то задач). С другой же стороны, это необязательное предписание. Если вам интересно, посмотрите статью в Википедии на эту тему. В этом курсе мы не будем разбирать отдельные шаблоны.

Статья о принципах SOLID хорошо освещает эту тему и предоставляет хорошие ссылки на тематические ресурсы. Если вы желаете писать качественный код, вам необходимо знать эти принципы:

  • Single Responsibility Principle (На каждый класс должна быть возложена одна-единственная обязанность)
  • Open/Closed Principle (программные сущности (классы, модули, функции и т. п.) должны быть открыты для расширения, но закрыты для изменения)
  • Liskov Substitution Principle (Функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа, не зная об этом)
  • Interface Segregation Principle (Много специализированных интерфейсов лучше, чем один универсальный)
  • Dependency Inversion Principle (Зависимости внутри системы строятся на основе абстракций. Модули верхнего уровня не зависят от модулей нижнего уровня. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций)

К счастью, Rails работает по этим принципам, так что вам просто следует их понять и следовать им. Уделите минуту и почитайте о каждом из них - это основополагающие принципы разработки программного обеспечения, а также они пригодятся вам на профессиональном собеседовании.

Если вы решили следовать шаблонам проектирования, познакомьтесь с паттернами, известными как "Банда четырех ("Gang of Four" (GoF))" в этом посте.

А это полезная книга об антипаттернах, которая поможет почистить ваш код от "неприятных запахов".

I18n: Интернационализация

Интернационализация и локализация - это процесс адаптации вашего приложения к географическому местоположению и/или языкам. Он выходит за рамки нашего курса, но интересующиеся могут посмотреть это руководство на Sitepoint.

Ваши задания

  1. Просмотрите раздел 2.6 Документации Rails по маршрутизации, в котором речь идет о пространстве имен.
  2. В той же документации прочтите разделы 2.7-3.7 о вложенных маршрутах, коллекциях и многом другом.
  3. В разделах 3.8-3.15 for прочитайте о различных вариантах использования маршрутизации, таких как применение ограничений и перенаправлений.
  4. Пробегитесь по главе 4. Что-то мы уже видели, но большинство материала здесь раскроет перед вами дополнительные возможности Rails. Когда-нибудь они вам понадобятся, и не исключено, что Google приведет вам именно сюда.
  5. Прочтите раздел 3.5 Документации по макетам в Rails. Здесь речь идет о передаче информации между файлами представлений и макетов, включая стили CSS. Уделите минуту, чтобы понять происходящее в имеющемся там примере.
  6. Если вас заинтересовало метапрограммирование, прочтите http://ruby-metaprogramming.rubylearning.com/. Оно не обязательно для использования в ваших ранних приложениях Rails, но вы определенно начнете использовать его в будущем.
  7. Ознакомьтесь с этой презентацией о принципах SOLID.

Заключение

В этом курсе мы прошлись по разнообразным концепциям, с которыми вы не будете встречаться каждый день, но нем не менее очень любопытным и необходимым. Здесь важен реальный опыт - в процессе создания приложений вам понадобится то, что вы только что прочитали, и это должно избавить вас от лишних временных затрат и сложности кода.

Более общие принципы, такие как SOLID (принципы объектно-ориентированного программирования) и метапрограммирование, будут полезны для вас в будущем, когда вы освоитесь с Ruby и Rails, или начнете оттачивать свое мастерство до блеска.

Итак, теперь нам не остается ничего, кроме как... начать наш заключительный проект!

Дополнительные ресурсы

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

Поделиться уроком: