Ruby on Rails Tutorial
Изучение Rails на Примерах
Майкл Хартл
Содержание
- Предисловие к русскому изданию
- Глава 1 От нуля к развертыванию
- Глава 2 demo app
- Глава 3 В основном статические страницы
- Глава 4 Rails-приправленный Ruby
- Глава 5 Заполнение шаблона
- Глава 6 Моделирование пользователей
- Chapter 7 Регистрация
- Глава 8 Войти, выйти
- Глава 9 Обновление, демонстрация и удаление пользователей
- Глава 10 Микросообщения пользователей
- Глава 11 Слежение за сообщениями пользователей
Предисловие
Моя компания (CD Baby) была одной из первых громко перешедших на Ruby on Rails, а затем еще громче вернувшейся обратно на PHP (Google расскажет вам об этой драме). Эту книгу, написанную Майклом Хартлом так высоко рекомендовали, что я должен был попробовать её, и Ruby on Rails Tutorial это всё, что я использовал, чтобы вернуться к Rails.
Хотя я уже прошел через много книг по Rails, это одна из немногих, что, наконец, зацепила меня. Было много написано книг типа «Путь Rails» — после которых я чувствовал себя неестественно, но после этой книги я наконец почувствовал себя естественно. Это также единственная книга по Rails, которая соблюдает методику «разработка через тестирование» на всем своем протяжении, этот подход строго рекомендуется специалистами, но он никогда не был так чётко продемонстрирован ранее. Наконец, Git, GitHub и Heroku присутствуют в демо-примерах, автор действительно дает вам почувствовать, что он хотел сделать реальный проект. Учебный код примеров не изолирован.
Линейное повествование — отличный формат. Лично я прошел Rails Tutorial в течении трёх долгих дней, делая все примеры и задачи в конце каждой главы. Делайте всё от начала до конца, не прыгая, и вы получите максимальную пользу.
Наслаждайтесь!
Derek Sivers (sivers.org)
Основатель CD Baby
Благодарности
Ruby On Rails Учебник во многом обязан моей предыдущей книге по Rails, RailsSpace и, следовательно, моему соавтору Aurelius Prochazka. Я хотел бы поблагодарить Aure как за работу, которую он проделал над прошлой книгой, так и за поддержку этой. Я также хотел бы поблагодарить Debra Williams Cauley, редактора обеих книг RailsSpace и Rails Tutorial; до тех пор, пока она не прекратит брать меня на бейсбол, я буду продолжать писать книги для нее.
Я хотел бы поблагодарить огромное количество Рубистов учивших и вдохновлявших меня на протяжении многих лет: David Heinemeier Hansson, Yehuda Katz, Carl Lerche, Jeremy Kemper, Xavier Noria, Ryan Bates, Geoffrey Grosenbach, Peter Cooper, Matt Aimonetti, Gregg Pollack, Wayne E. Seguin, Amy Hoy, Dave Chelimsky, Pat Maddox, Tom Preston-Werner, Chris Wanstrath, Chad Fowler, Josh Susser, Obie Fernandez, Ian McFarland, Steven Bristol, Pratik Naik, Sarah Mei, Sarah Allen, Wolfram Arnold, Alex Chaffee, Giles Bowkett, Evan Dorn, Long Nguyen, James Lindenbaum, Adam Wiggins, Tikhon Bernstam, Ron Evans, Wyatt Greene, Miles Forrest, хороших людей из Pivotal Labs, команду Heroku, thoughtbot ребят, и команду GitHub. Наконец, многих, многих читателей - слишком много чтобы перечислять их здесь - внёсших большое количество предложений по улучшению и сообщивших об ошибках во время написания этой книги, и я с благодарностью признаю их помощь в написании ее настолько хорошей, насколько это было возможно.
Об авторе
Майкл Хартл – автор Ruby on Rails Tutorial, лидирующего введения в веб разработку на Ruby on Rails. Его предыдущий опыт включает в себя написание и разработку RailsSpace - чрезвычайно устаревшего учебника по Rails и разработку Insoshi - некогда популярной, а ныне устаревшей платформы для социальных сетей написанной на Ruby on Rails. В 2011, Майкл получил Ruby Hero Award за его вклад в Ruby сообщество. Он закончил Harvard College, имеет степень Кандидата Физических Наук присвоенную в Caltech и является выпускником предпринимательских курсов Y Combinator.
Копирайт и лицензия
Ruby on Rails Tutorial: Learn Web Development with Rails. Copyright © 2012 by Michael Hartl. Весь исходный код в Ruby on Rails Tutorial доступен под MIT License и Beerware License.
Лицензия MIT
Copyright (c) 2013 Michael Hartl
Данная лицензия разрешает лицам, получившим копию данного программного
обеспечения и сопутствующей документации (в дальнейшем именуемыми
«Программное Обеспечение»), безвозмездно использовать Программное
Обеспечение без ограничений, включая неограниченное право на использование,
копирование, изменение, добавление, публикацию, распространение,
сублицензирование и/или продажу копий Программного Обеспечения, также
как и лицам, которым предоставляется данное Программное Обеспечение,
при соблюдении следующих условий:
Указанное выше уведомление об авторском праве и данные условия должны быть
включены во все копии или значимые части данного Программного Обеспечения.
ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО
ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ
ГАРАНТИЯМИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И
ОТСУТСТВИЯ НАРУШЕНИЙ ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ
НЕСУТ ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ ИЛИ ДРУГИХ
ТРЕБОВАНИЙ ПО ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ ИНОМУ, ВОЗНИКШИМ ИЗ,
ИМЕЮЩИМ ПРИЧИНОЙ ИЛИ СВЯЗАННЫМ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ
ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.
/*
* ----------------------------------------------------------------------------
* "ПИВНАЯ ЛИЦЕНЗИЯ" (Ревизия 42):
* Весь код написан Майклом Хартлом. До тех пор пока вы осознаете это,
* вы можете делать с ним все что захотите. Если мы когда нибудь
* встретимся, и если это того стоило, вы можете купить мне
* пиво в ответ.
* ----------------------------------------------------------------------------
*/
Глава 3 В основном статические страницы
В этой главе мы начнем разработку примера приложения, которое будет служить нам образцом в остальной части этого учебника. Хотя в примере приложения, в конечном счете будут пользователи, сообщения и полная система входа и аутентификации, мы начнем с, казалось бы, ограниченной темы: с создания статических страниц. Несмотря на свою кажущуюся простоту, создание статических страниц весьма поучительное упражнение — идеальное начало для нашего зарождающегося приложения.
Хотя Rails предназначен для создания динамических веб сайтов, поддерживаемых базой данных, он также хорош при создании статических страниц, которые мы могли бы сделать на чистом HTML. В самом деле, использование Rails даже для статических страниц дает неоспоримое преимущество: мы можем легко добавлять лишь небольшое количество динамического содержания. В этой главе мы узнаем, как. По пути мы получим наш первый опыт автоматического тестирования, которое поможет нам быть более уверенными, в том, что наш код является правильным. Кроме того, хороший набор тестов позволит нам реорганизовывать (refactor) наш код с уверенностью, изменяя его форму, не меняя функций.
В этой главе довольно много кода, особенно в Разделе 3.2 и Разделе 3.3 и если вы новичок в Руби, вам не стоит беспокоиться о полном понимании происходящего. Как отмечалось в Разделе 1.1.1, один из возможных вариантов - копипастинг тестов и использование их для проверки кода приложения, не особо заботясь в этой точке о понимании того как они работают. К тому же, Глава 4 раскрывает тему Руби более полно, так что у этих идей будет масса возможностей осесть в вашей голове. Наконец, RSpec тесты будут часто повторяться на протяжении учебника, так что если вы застрянете, я рекомендую прорываться вперед; вы будете поражены тем, как, буквально через несколько глав, изначально непостижимый код совершенно внезапно окажется простым. (Вы также можете пройти RSpec курс в Code School, один из читателей сказал что он нашел в этом курсе ответы на бОльшую часть своих вопросов связанных с RSpec.)
Как и в Главе 2, перед началом работы нам необходимо создать новый Rails-проект, который мы в этот раз назовем sample_app
:
$ cd ~/rails_projects
$ rails new sample_app --skip-test-unit
$ cd sample_app
Здесь --skip-test-unit опция команды rails
говорит Rails не генерировать директорию test
, связанную с дефолтным Test::Unit
фреймворком. И это вовсе не потому что мы не хотим писать тесты; наоборот, начиная с Раздела 3.2 мы будем использовать альтернативный тестовый фреймворк RSpec для написания основательного набора тестов.
Как и в Разделе 2.1, нашим следующим шагом будет использование текстового редактора для добавления в Gemfile
гемов, необходимых нашему приложению. Также, для примера приложения нам понадобятся два гема, в которых мы ранее не нуждались: гем для RSpec и гем для Rails-специфичной RSpec библиотеки. Код для их включения показан в Листинге 3.1. (Примечание: если вы хотите установить все гемы, необходимые для примера приложения, вам следует использовать код из Листинга 9.47.)
Gemfile
для примера приложения.source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0
gem 'rails', '4.0.2'
group :development, :test do
gem 'sqlite3', '1.3.8'
gem 'rspec-rails', '2.13.1'
end
group :test do
gem 'selenium-webdriver', '2.35.1'
gem 'capybara', '2.1.0'
end
gem 'sass-rails', '4.0.1'
gem 'uglifier', '2.1.1'
gem 'coffee-rails', '4.0.1'
gem 'jquery-rails', '3.0.4'
gem 'turbolinks', '1.1.1'
gem 'jbuilder', '1.0.2'
group :doc do
gem 'sdoc', '0.3.20', require: false
end
group :production do
gem 'pg', '0.15.1'
gem 'rails_12factor', '0.0.2'
end
Это включает rspec-rails в development mode таким образом мы получаем доступ к RSpec-специфичным генераторам и это включает его в test mode где он нужен для запуска тестов. Нам нет надобности устанавливать сам RSpec, поскольку он является зависимостью гема rspec-rails и поэтому он будет установлен автоматически. Мы также включили гем Capybara, который позволит нам симулировать взаимодействие пользователя с нашим примером приложения используя понятный Англоподобный синтаксис, совместно с Selenium, одной из зависимостей Capybara’.1 Как и в Главе 2, мы также должны включить pg и rails_12factor гемы в production для развертывания на Heroku:
group :production do
gem 'pg', '0.15.1'
gem 'rails_12factor', '0.0.2'
end
Heroku не рекомендует использовать разные базы данных для девелопмент и продакшн окружений; но для примера приложения это не будет иметь никакого значения, и SQLite намного проще чем PostgreSQL в установке и конфигурировании. Установку и конфигурирование PostgreSQL на вашей локальной машине остается в качестве амбициозного задания для авантюристов и мазохистов (Раздел 3.5).
Для установки и подключения новых гемов мы запускаем bundle update
и bundle install
:
$ bundle install --without production
$ bundle update
$ bundle install
Аналогично Разделу 1.4.1 и Главе 2, мы предотвратили установку продакшн гемов используя опцию --without production. Это “запоминаемая опция” и это означает что нам не придется добавлять ее каждый раз при вызове Bundler. Вместо этого мы можем просто писать bundle install
и production гемы будут игнорированы автоматически.2
Поскольку наш пример приложения будет храниться в публичном репозитории, важно обновить так называемый secret token используемый в Rails для шифрования переменных связанных с сессией таким образом чтобы он генерировался динамически, а не был жестко прописан в коде (Листинг 3.2). (Код в Листинге 3.2 является довольно сложным для самого начала учебника, но, так как это может оказаться серьезной дырой в безопасности, я чувствую что важно включить его, даже на этой ранней стадии.) Убедитесь что вы используете расширенный файл .gitignore
из Листинга 1.7 чтобы ключ .secret
не отображался в вашем репозитории.
config/initializers/secret_token.rb
# Be sure to restart your server when you modify this file.
# Your secret key is used for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
# You can use `rake secret` to generate a secure secret key.
# Make sure your secret_key_base is kept private
# if you're sharing your code publicly.
require 'securerandom'
def secure_token
token_file = Rails.root.join('.secret')
if File.exist?(token_file)
# Use the existing token.
File.read(token_file).chomp
else
# Generate a new token and store it in token_file.
token = SecureRandom.hex(64)
File.write(token_file, token)
token
end
end
SampleApp::Application.config.secret_key_base = secure_token
Затем нам необходимо сконфигурировать Rails для использования RSpec вместо Test::Unit
. Это может быть достигнуто с помощью rails generate rspec:install
:
$ rails generate rspec:install
Если ваша система жалуется на отсутствие JavaScript runtime, посетите execjs страницу на GitHub, где приведен список возможных решений. Я особенно рекомендую установку Node.js.
Все что нам осталось — инициализировать Git репозиторий:3
$ git init
$ git add .
$ git commit -m "Initial commit"
Как и с первым приложением, я советую обновить README
файл (находится в корневой директории приложения) чтобы сделать его более полезным и описательным, как показано в Листинге 3.3.
README
файл для примера приложения.# Перевод Ruby on Rails Tutorial: пример приложения
Это пример приложения для
[*Ruby on Rails Tutorial*](https://railstutorial.org/)
by [Майкл Хартл](http://michaelhartl.com/).
Затем изменим его расширение на markdown
и зафиксируем изменения:
$ git mv README.rdoc README.md
$ git commit -am "Improve the README"
Вы можете вспомнить из Раздела 1.3.5 что мы использовали Git команду git commit -a -m "Message"
, с флагами для “все изменения” (-a
) и сообщение (-m
). Как показано во второй команде выше, Git также позволяет свернуть два флага в один: git commit -am "Message"
.
Поскольку мы будем использовать этот пример приложения на протяжении оставшейся части учебника, хорошей идеей будет создание нового репозитория на GitHub отправка туда нашего кода:
$ git remote add origin https://github.com/<username>/sample_app.git
$ git push -u origin master
В результате выполнения мною этого шага вы можете найти код примера приложения на GitHub (от имени пользователя railstutorial и немного другим названием sample_app_rails_4).4
Конечно, мы также можем задеплоить приложение на Heroku даже на этой ранней стадии:
$ heroku create
$ git push heroku master
$ heroku run rake db:migrate
(Как было отмечено в Разделе 1.4.1, некоторые читатели сообщали о необходимости прекомпиляции ассетов при помощи команды:
# Это должно быть использовано только если вы не можете задеплоить на Heroku.
$ rake assets:precompile
$ git add .
$ git commit -m "Add precompiled assets for Heroku"
$ git push heroku master
Шаг прекомпиляции ассетов по идее не нужен и я не смог воспроизвести ситуацию в которой он был бы необходим, но количество получаемых сообщений об ошибках таково что я вынужден упомянуть здесь о решении.)
Я рекомендую регулярно пушить и деплоить ваш пример приложения в процессе чтения учебника:
$ git push
$ git push heroku
$ heroku run rake db:migrate
Это обеспечит вас бэкапами в удаленном (не локальном) хранилище и позволит отловить продакшен-ошибки настолько быстро, насколько это вообще возможно. Если вы столкнетесь с проблемами на Heroku, вам, вероятно, захочется взглянуть на продакшен логи для того чтобы попытаться диагностировать проблему:
$ heroku logs
Закончив все приготовления, мы наконец-то готовы начать разработку примера приложения.
3.1 Статические страницы
В этом разделе мы предпримем первые шаги в направлении создания динамических страниц создав набор Rails actions (действий) и views(представлений) содержащих (пока) только статический HTML.5 Rails действия объединены внутри контроллеров (C в MVC из Раздела 1.2.6), которые содержат наборы действий связанных общим назначением. Мы получили некоторое представление о контроллерах в Главе 2 и придем к их более глубокому пониманию когда начнем использовать REST архитектуру более плотно (начиная с Главы 6); по сути, контроллер это контейнер для группы (возможно динамических) веб страниц.
Для того чтобы сориентироваться, полезно будет вспомнить структуру директорий Rails из Раздела 1.2.3 (Рис. 1.2). В этом разделе мы в основном будем работать в app/controllers
и app/views
директориях. (В Разделе 3.2 мы даже добавим свою собственную директорию.) А сейчас я советую вам открыть пример приложения в вашем текстовом редакторе (или IDE) (Box 3.1).
Полезно иметь возможность открывать весь Rails-проект в вашем текстовом редакторе или IDE. К сожалению, способ сделать это зависит от системы, но в большинстве случаев вы можете открыть текущую директорию приложения (которая представлена в Unix точкой “.
”), используя команду командной строки для вашего редактора:
$ cd ~/rails_projects/sample_app $ <название редактора> .
Например, для того чтобы открыть пример приложения в Sublime Text, вы наберете
$ subl .
Для Vim это будет
$ vim .
(где vim
может быть gvim
или mvim
в зависимости от того какой именно Vim вы используете).
Для того чтобы начать со статическими страницами, вспомните из Раздела 1.3.5 что, при использовании Git, хорошей практикой является делать нашу работу в отдельной, а не в master ветке. Если вы используете Git для контроля версий, вам следует выполнить следующую команду:
$ git checkout -b static-pages
Rails поставляется со скриптом для создания контроллеров называемым generate
; имя контроллера это все, что ему (скрипту) нужно для его магической работы. Поскольку мы будем делать контроллер для обработки статических страниц, мы назовем его StaticPages. Мы также планируем сделать действия для страниц Home, Help и About. Скрипт generate
принимает в качестве необязательного параметра список действий, так что мы включим два из начальных действий непостредственно в команду вызова скрипта (Листинг 3.4).
$ rails generate controller StaticPages home help --no-test-framework
create app/controllers/static_pages_controller.rb
route get "static_pages/help"
route get "static_pages/home"
invoke erb
create app/views/static_pages
create app/views/static_pages/home.html.erb
create app/views/static_pages/help.html.erb
invoke helper
create app/helpers/static_pages_helper.rb
invoke assets
invoke coffee
create app/assets/javascripts/static_pages.js.coffee
invoke scss
create app/assets/stylesheets/static_pages.css.scss
Обратите внимание на опцию --no-test-framework используемую для подавления генерации дефолтных RSpec тестов, которые мы не планируем использовать. Вместо этого мы создадим тесты вручную в Разделе 3.2. Мы также намеренно не включили about
действие в аргументы команды из Листинга 3.4 поэтому мы сможем увидеть как добавить его используя разработку через тестирование, или TDD (Раздел 3.2).
В Листинге 3.4, обратите внимание на то что мы передали имя контроллера в так называемом CamelCase, что привело к созданию файла контроллера, название которого написано в snake_case, таким образом контроллер названный StaticPages привел к файлу названному static_pages_controller.rb
. Это просто соглашение и, фактически, использование snake_case в командной строке допустимо: команда
$ rails generate controller static_pages ...
тоже сгенерирует контроллер с названием static_pages_controller.rb
. Поскольку Ruby использует CamelCase для имен классов (Раздел 4.4), при упоминании контроллеров я предпочитаю использовать их названия в CamelCase, но это дело вкуса. (Поскольку Рубишные названия файлов обычно используют snake_case, Rails генератор конвертирует CamelCase в snake_case с помощью метода underscore.)
Кстати, на случай если вы когда-либо сделаете ошибку при генерации кода, вам пригодятся знания о том как обратить процесс. См. в Блоке 3.2 несколько техник переделывания в Rails.
Даже если вы очень осторожны, все же некоторые вещи иногда могут пойти не так при разработке Rails приложений. К счастью, в Rails есть несколько возможностей которые могут помочь вам откатить изменения.
Одним возможным сценарием является желание откатить генерацию кода связанное с необходимостью изменить название контроллера. При генерации контроллера, Rails создает гораздо большее количество файлов чем сам файл контроллера (как видно из Листинга 3.4). Откат генерации подразумевает не только удаление основного, но и всех сгенерированных вспомогательных файлов. (Фактически мы также хотим отменить все автоматические правки сделанные в файле routes.rb.) В Rails, это может быть осуществлено с помощью rails destroy. В частности, эти две команды отменяют друг друга:
$ rails generate controller FooBars baz quux $ rails destroy controller FooBars baz quux
Аналогично, в Главе 6 мы будем генерировать модель следующим образом:
$ rails generate model Foo bar:string baz:integer
Это действие может быть отменено с помощью
$ rails destroy model Foo
(В данном случае, оказывается, мы можем опустить остальные аргументы командной строки. Посмотрим, сможете ли вы понять почему по окончании Главы 6.)
Другая техника, связанная с моделями позволяет откатывать миграции, которые мы видели вкратце в Главе 2 и с которыми мы более подробно познакомимся в Главе 6. Миграции изменяют состояние базы данных с помощью
$ rake db:migrate
Мы можем откатить один шаг миграции с помощью
$ rake db:rollback
Для того чтобы откатить к самому началу (все миграции), мы можем использовать
$ rake db:migrate VERSION=0
Как вы возможно догадались, подстановка любого другого числа вместо 0 откатит миграции до соответствующей версии, где номера версий образуются из последовательного списка миграций.
С этими техниками на руках мы хорошо упакованы для того чтобы справиться с неизбежными при разработке казусами.
Генерация контроллера StaticPages в Листинге 3.4 автоматически обновляет routes файл, из каталога config/routes.rb
, который Rails использует для определения соответствий между URL и веб страницами. Tак как это наше первое знакомство с config
директорией, полезно взглянуть на нее (Рис. 3.1). Сonfig
— директория где Rails хранит файлы, необходимые для конфигурации приложения — отсюда и название.
Так как мы сгенерировали home
и help
действия, файл маршрутов уже имеет правила для каждого из них, что видно в Листинге 3.5.
home
и help
действий контроллера StaticPages. config/routes.rb
SampleApp::Application.routes.draw do
get "static_pages/home"
get "static_pages/help"
.
.
.
end
Здесь правило
get "static_pages/home"
направляет запрос для URL /static_pages/home в home
действие контроллера StaticPages. Более того, используя get
мы приговариваем маршрут отвечать на GET запрос, который является одним из основных HTTP глаголов поддерживаемых протоколом передачи гипертекста (Блок 3.3). В нашем случае это значит, что когда мы сгенерировали home
действие внутри контроллера StaticPages мы автоматически получили страницу по адресу /static_pages/home. Чтобы увидеть результаты, убейте сервер, нажав Ctrl-C, запустите rails server
, а затем перейдите на /static_pages/home (Рис. 3.2).
Hyper Text Transfer Protocol ("протокол передачи гипертекста") (HTTP) определяет четыре основных операции, cответствующие четырем глаголам GET (получить), POST (отправить), PATCH (править) и DELETE (удалить). Они относятся к операциям между клиентским компьютером (как правило, работает веб-браузер, например Firefox или Safari) и сервером (как правило, работает веб-сервер, такой как Apache или Nginx). (Важно понимать, что при разработке Rails приложения на локальном компьютере, клиент и сервер на одной физической машине, но в целом они разные.) Акцент на глаголы HTTP является типичным для фреймворков (веб-платформ) (включая Rails), находящихся под влиянием REST архитектуры, (ее основы рассматриваются в Главе 2 и более подробно в Главе 7.
GET является самой распространенной операцией HTTP, используемой для чтения данных в Интернете; это просто означает "получить страницу", и каждый раз, когда вы посещаете сайт, такой как google.com или wikipedia.org , ваш браузер передает запрос GET. POST это следующая наиболее распространенная операция, это запрос, передаваемый вашим браузером при предоставлении (заполнении) формы. В Rails приложениях, POST запросы обычно используются для создания вещи (хотя HTTP позволяет выполнять обновления и через POST ), например, запрос POST отправляется, когда вы отправляете заполненную регистрационную форму создающую нового пользователя на удаленном сайте. Два других глагола, PATCH и DELETE, предназначены для обновления и уничтожения вещей на удаленном сервере. Эти запросы встречаются реже, чем GET и POST поскольку браузеры не в состоянии отправить их в естественном виде, но некоторые фреймворки (включая Ruby on Rails) могут делать вид что браузеры отправляют такой запрос.
Кстати, предыдущие версии Rails использовали PUT вместо PATCH и Rails 4.0 все еще поддерживает такой подход, но PATCH лучше соответствует семантике HTTP и является предпочтительным для новых приложений.
Чтобы понять, откуда появилась эта страница, для начала посмотрите на контроллер StaticPages в текстовом редакторе, вы должны увидеть что-то вроде Листинга 3.6. Вы можете отметить, что, в отличие от демо контроллеров Users и Microposts из Главы 2, контроллер StaticPages не использует стандартные REST действия. И это нормально для набора статических страниц: REST архитектура — не панацея для всех проблем.
app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
def home
end
def help
end
end
Здесь мы видим что static_pages_controller.rb
определяет class называемый StaticPagesController
. Классы это всего лишь удобный способ организации функций (также называемых методами) таких как home
и help
действия, определяемых с помощью ключевого слова def
. Угловая скобка <
указывает на то, что StaticPagesController
наследует от Rails класса ApplicationController
; как мы увидим через мгновение, это означает, что наши страницы оснащены большим количеством Rails-специфичных функций. (Мы узнаем больше об этих двух классах и наследовании в Разделе 4.4.)
В случае со StaticPages контроллером, оба его метода изначально пусты:
def home
end
def help
end
В переводе на простой Ruby, эти методы просто ничего не делают. В Rails, ситуация иная; StaticPagesController
это класс Ruby, но благодаря тому, что он наследует от ApplicationController
поведение его методов специфично для Rails: при посещении URL /static_pages/home, Rails смотрит в контроллер StaticPages и выполняет код в home
действии, а затем рендерит view (представление) (V в MVC из Раздела 1.2.6) соответствующее данному действию. В данном случае home
действие пустое, поэтому единственное что происходит при посещении /static_pages/home - рендеринг (отображение) представления. Итак, как же выглядит представление, и как мы можем его найти?
Если вы еще раз посмотрите на вывод в Листинге 3.4, вы можете найти соответствие между действиями и представлениями: действие, такое как home
имеет соответствующее представление home.html.erb
. Мы узнаем в Разделе 3.3, что означает .erb
часть расширения; .html
часть расширения подсказывает, что представление, в основном, выглядит как HTML (Листинг 3.7).
app/views/static_pages/home.html.erb
<h1>StaticPages#home</h1>
<p>Find me in app/views/static_pages/home.html.erb</p>
Представление для help
действия аналогично (Listing 3.8).
app/views/static_pages/help.html.erb
<h1>StaticPages#help</h1>
<p>Find me in app/views/static_pages/help.html.erb</p>
Оба эти представления — лишь заглушки: у них есть заголовок (внутри h1
тега) и абзац (p
тег) с указанием полного пути к соответствующему файлу. Мы добавим немного (очень немного) динамического контента, начиная с Раздела 3.3, но пока они статичны, эти представления подчеркивают важный момент: Rails представления могут просто содержать статический HTML.
В оставшейся части этой главы мы добавим немного собственного контента в Home и Help страницы, а затем добавим About страницу которую мы пропустили в Разделе 3.1. Затем добавим очень небольшое количество динамического содержимого заменив статичный тайтл на тайтл, меняющийся в зависимости от страницы.
Прежде чем двинуться дальше, если вы используете Git, хорошей идеей будет добавление файлов для StaticPages контроллера в репозиторий:
$ git add .
$ git commit -m "Add a StaticPages controller"
3.2 Наши первые тесты
Rails Tutorial имеет интуитивно понятный подход к тестированию, который уделяет особое внимание поведению приложения, а не деталям его реализации, что является вариантом разработки через тестирование (TDD) известным как разработка через поведение (BDD). Нашими основными инструментами будут интеграционные тесты (начиная с этого раздела) и юнит тесты (начиная с Главы 6). Интеграционные тесты, известные в контексте RSpec как request specs, позволят нам симулировать действия пользователя взаимодействующего с нашим приложением через веб браузер. Совместно с замечательным синтаксисом предоставляемым Capybara, интеграционные тесты обеспечивают мощную методику тестирования функционала нашего приложения без необходимости вручную проверять каждую страницу в браузере. (Другой популярный инструмент для BDD, называемый Cucumber, будет представлен в Разделе 8.3.)
Основополагающий принцип TDD это написание тестов до написания кода приложения. Вначале это может оказаться непривычным и потребуется некоторое время для того чтобы привыкнуть, но оно того стоит. Написание провального теста и последующая реализация кода приложения для его прохождения увеличивает нашу уверенность в том, что данный тест действительно покрывает функциональность для которой он был написан. Кроме того, цикл разработки провал-реализация-прохождение вызывает потоковое состояние, что приводит к получению удовольствия от кодинга и высокой производительности. Наконец, тесты играют роль клиента для кода приложения, что обычно приводит к более элегантному дизайну софта.
Важно понимать, что TDD не всегда правильный инструмент для работы. В частности, когда вы не полностью уверены, в том, как решить данную проблему программирования, часто бывает полезным пропустить тесты и писать только код приложения, просто чтобы получить представление о том, как решение будет выглядеть. (На языке Экстремального Программирования (XP) этот поисковый этап называется spike.) Как только вы увидите общую форму решения, вы сможете использовать TDD для реализации более изысканной версии.
В этом разделе мы будем запускать тесты используя rspec
команду, предоставляемую гемом RSpec. Такая практика эффективна, но не идеальна, и если вы являетесь более продвинутым пользователем, то я советую настроить вашу систему как это описано в Разделе 3.6.
3.2.1 Разработка через тестирование
В Test-Driven Development, мы вначале пишем провальный тест, который в большинстве инструментов тестирования представлен красным цветом. Затем мы реализуем код необходимый для прохождения теста, проходящий тест обычно представлен зеленым цветом. Наконец, если необходимо, мы реорганизуем (рефакторим) код, меняя его форму (например, устраняя повторения) не меняя его функций. Этот цикл известен как “Красный, Зеленый, Реорганизация”.
Мы начнем с того что добавим немного контента в Home страницу используя разработку через тестирование, включая заголовок верхнего уровня (<h1>
) содержащий Sample App
. Первый шаг это генерация интеграционного теста (request spec) для наших статических страниц:
$ rails generate integration_test static_pages
invoke rspec
create spec/requests/static_pages_spec.rb
Это создает static_pages_spec.rb
в директории spec/requests
. Как и бОльшая часть генерируемого кода, полученный результат удручает, так что мы откроем static_pages_spec.rb
в текстовом редакторе и заменим его содержимым Листинга 3.9.
spec/requests/static_pages_spec.rb
require 'spec_helper'
describe "Static pages" do
describe "Home page" do
it "should have the content 'Sample App'" do
visit '/static_pages/home'
expect(page).to have_content('Sample App')
end
end
end
Код в Листинге 3.9 является чистым Ruby, но даже если вы изучали Ruby прежде, он, вероятно, выглядит не очень знакомым. Это происходит потому, что RSpec использует общую гибкость Ruby для определения предметно-ориентированного языка (DSL) созданного только для тестирования. Важным моментом является то, что вам не нужно понимать синтаксис RSpec, чтобы иметь возможность использовать RSpec. На первый взгляд это может показаться магией, но RSpec и Capybara разработаны так, чтобы быть более или менее похожими на английский и если вы будете следовать примерам generate
скрипта и другим примерам в этом учебнике вы разберетесь с ним довольно быстро.
Листинг 3.9 содержит describe
блок с одним примером, т.е., блоком, начинающимся с it "…" do
:
describe "Home page" do
it "should have the content 'Sample App'" do
visit '/static_pages/home'
expect(page).to have_content('Sample App')
end
end
Первая строка указывает на то, что мы описываем (describing) Home страницу. Это описание является простой строкой и может содержать все что вам угодно; это не важно для RSpec, но имеет значение для вас и прочих читателей вашего кода. Затем spec (# далее по тексту - спек, спеки) говорит что при посещении Home страницы по адресу /static_pages/home
, контент должен содержать слова “Sample App”. Как и в случае с первой строкой, происходящее внутри кавычек не имеет значения для RSpec и предназначено для повышения читабельности кода. Затем строка
visit '/static_pages/home'
использует Capybara функцию visit
для симуляции посещения URL /static_pages/home
в браузере, в то время как строка
expect(page).to have_content('Sample App')
использует переменную page
(также предоставленную Capybara) для описания ожидания того что данная страница должна содержать правильный контент.
Для того чтобы правильно запустить тесты мы должны добавить строку в файл spec_helper.rb
, как это показано в Листинге 3.10. (В полноценном третьем издании Rails Tutorial я планирую избавиться от этого требования с помощью более новой техники которая называется feature specs.)
spec/spec_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
.
.
.
RSpec.configure do |config|
.
.
.
config.include Capybara::DSL
end
Существует несколько способов запуска тестов, в том числе удобные, но более продвинутые инструменты описанные в Разделе 3.6. В данный момент мы будем использовать консольную команду rspec
(выполняемую с bundle exec
для вящей уверенности в том что RSpec запускает ее в окружении которое определено нашим Gemfile
):6
$ bundle exec rspec spec/requests/static_pages_spec.rb
Что выдаст провальный тест. Внешний вид результата зависит от вашей системы; на моей системе красный провальный тест выглядит как на Рис. 3.3.7
Для прохождения первого теста мы заменим текст дефолтной Home страницы на HTML из Листинга 3.11.
app/views/static_pages/home.html.erb
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
Что дает нам заголовок верхнего уровня (<h1>
) с контентом Sample App
, необходимый для прохождения теста. Мы также включили якорный тег a
, который создает ссылку на конкретный URL (называемую “href”, или “гипертекстовая ссылка”, в контексте якорного тега):
<a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
Теперь перезапустим тест чтобы увидеть эффект:
$ bundle exec rspec spec/requests/static_pages_spec.rb
На моей системе проходящий тест выглядит как на Рис. 3.4.
Опираясь на пример Home страницы вы возможно предвидите аналогичный тест и код приложения для страницы Help. Мы начнем с тестирования на наличие значимого контента, в данном случае, строки ’Help’
(Листинг 3.12).
spec/requests/static_pages_spec.rb
require 'spec_helper'
describe "Static pages" do
describe "Home page" do
it "should have the content 'Sample App'" do
visit '/static_pages/home'
expect(page).to have_content('Sample App')
end
end
describe "Help page" do
it "should have the content 'Help'" do
visit '/static_pages/help'
expect(page).to have_content('Help')
end
end
end
Затем запускаем тесты:
$ bundle exec rspec spec/requests/static_pages_spec.rb
Один тест должен провалиться. (Поскольку системы будут развиваться, и в связи с тем, что при таком количестве тестов отслеживание этих изменений на каждой итерации учебника это ночной кошмар техподдержки, я буду опускать вывод RSpec с этого момента.)
Как видно из Листинга 3.13, код приложения аналогичен коду из Листинга 3.11.
app/views/static_pages/help.html.erb
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="https://railstutorial.org/help">Rails Tutorial help page</a>.
To get help on this sample app, see the
<a href="https://railstutorial.org/book">Rails Tutorial book</a>.
</p>
Теперь тест должен пройти:
$ bundle exec rspec spec/requests/static_pages_spec.rb
3.2.2 Добавление страницы
Посмотрев на разработку через тестирование в действии на простом примере, мы используем ту же технику для решения немного более сложной задачи по добавлению новой страницы, а именно, страницы About которую мы умышленно пропустили в Разделе 3.1. Посредством написания теста и запуска RSpec на каждом шаге, мы увидим как TDD может играть роль проводника при разработке кода нашего приложения.
Красный
Мы доберемся до Красной части цикла Красный-Зеленый написав провальный тест для страницы About. Следуя модели из Листинга 3.12, вы, вероятно, можете предугадать правильный тест (Листинг 3.14).
spec/requests/static_pages_spec.rb
require 'spec_helper'
describe "Static pages" do
describe "Home page" do
it "should have the content 'Sample App'" do
visit '/static_pages/home'
expect(page).to have_content('Sample App')
end
end
describe "Help page" do
it "should have the content 'Help'" do
visit '/static_pages/help'
expect(page).to have_content('Help')
end
end
describe "About page" do
it "should have the content 'About Us'" do
visit '/static_pages/about'
expect(page).to have_content('About Us')
end
end
end
Зеленый
Вспомним из Раздела 3.1 что в Rails мы можем генерировать статические страницы создавая действие и соответствующее ему представление с названием страницы. В нашем случае, странице About в первую очередь понадобится действие about
в контроллере StaticPages. Имея написанный провальный тест, мы можем быть уверены в том, что получив его прохождение, мы действительно создали рабочую страницу About.
Если вы запускаете RSpec используя
$ bundle exec rspec spec/requests/static_pages_spec.rb
вывод содержит следующую жалобу:
No route matches [GET] "/static_pages/about"
Что намекает на необхоимость добавления правила для URL /static_pages/about
в файле маршрутов, мы можем сделать это следуя паттерну из Листинга 3.5, как это показано в Листинге 3.15.
about
маршрута. config/routes.rb
SampleApp::Application.routes.draw do
get "static_pages/home"
get "static_pages/help"
get "static_pages/about"
.
.
.
end
Теперь запуск
$ bundle exec rspec spec/requests/static_pages_spec.rb
выдает жалобу
The action 'about' could not be found for StaticPagesController
Для решения этой проблемы мы последуем примеру home
и help
из Листинга 3.6, добавив about
действие в StaticPages контроллер (Листинг 3.16).
about
действием. app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
def home
end
def help
end
def about
end
end
Теперь запуск
$ bundle exec rspec spec/requests/static_pages_spec.rb
говорит что нам не хватает “шаблона”, т.е., представления:
Missing template static_pages/about
для решения этой проблемы мы добавим about
представление. Для этого потребуется создать новый файл about.html.erb
в директории app/views/static_pages
с контентом показанным в Листинге 3.17.
app/views/static_pages/about.html.erb
<h1>About Us</h1>
<p>
The <a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
is a project to make a book and screencasts to teach web development
with <a href="https://rubyonrails.org/">Ruby on Rails</a>. This
is the sample application for the tutorial.
</p>
Теперь запуск RSpec должен вернуть нас к Зеленому:
$ bundle exec rspec spec/requests/static_pages_spec.rb
Конечно, это никогда не плохая идея, чтобы посмотреть на страницу в браузере, чтобы убедиться, что наши тесты не являются абсолютно сумасшедшими (Рис. 3.5).
Реорганизация
Теперь, когда мы Позеленели, мы свободны, чтобы реорганизовать наш код, изменяя его форму не меняя функции. Часто код начинает “вонять”, что означает, что он становится уродливым, раздутым, или наполненным повторениями. Компьютеру, конечно, все равно, но не людям, поэтому важно хранить базу кода в чистоте, часто делая рефакторинг (реорганизацию). Наличие хорошего набора тестов является бесценным инструментом в этой связи, так как это резко снижает вероятность внесения ошибки в процессе рефакторинга.
Наш пример приложения слишком мал, чтобы реорганизовать его прямо сейчас, но вонь от кода проникает в каждую трещину, так что нам не придется долго ждать: мы займемся рефакторингом (реорганизацией) в Разделе 3.3.4.
3.3 Немного динамические страницы
Теперь, когда мы создали действия и представления для нескольких статических страниц, мы сделаем их чуть-чуть динамическими, добавив содержимое, которое будет меняться на каждой странице: мы получим заголовок (тайтл) каждой страницы, изменяющийся, отражая ее содержание. Является ли это действительно динамическим контентом — спорно, но это в любом случае закладывает необходимую основу для однозначно динамического содержимого в Главе 7.
Если вы пропустили TDD материал в Разделе 3.2, убедитесь что к этому моменту вы создали About страницу используя код из Листинга 3.15, Листинга 3.16 и Листинга 3.17.
3.3.1 Тестирование изменения заголовка
Наш план заключается в добавлении к страницам Home, Help, и About заголовка (тайтла) меняющегося на каждой странице. Это повлечет за собой использование <title>
тега в представления наших страниц. Большинство браузеров отображают содержимое title тега в верхней части окна браузера (Google Chrome является странным исключением из этого правила), к тому же он важен для поисковой оптимизации. Мы начнем с написания тестов для заголовков, затем добавим сами заголовки, а затем используем layout файл для рефакторинга получившихся страниц и устранения повторяющегося кода.
Вы, возможно, заметили, что команда rails new
уже создала layout файл. Мы вскоре узнаем о его назначении, но пока вам следует переименовать его прежде чем мы продолжим:
$ mv app/views/layouts/application.html.erb foobar # временное изменение
(mv
это Unix команда; на Windows вам возможно придется переименовать файл используя файловый браузер или команду rename
.) Обычно этого не нужно делать в реальном приложении, но нам проще будет понять его назначение если мы предварительно выведем его из строя.
Страница | URL | Базовый заголовок | Изменяющийся заголовок |
---|---|---|---|
Home | /static_pages/home | "Ruby on Rails Tutorial Sample App" | "Home" |
Help | /static_pages/help | "Ruby on Rails Tutorial Sample App" | "Help" |
About | /static_pages/about | "Ruby on Rails Tutorial Sample App" | "About" |
К концу этого раздела, все три наши статические страницы будут иметь заголовок (тайтл) вида “Ruby on Rails Tutorial Sample App | Home”, где последняя часть заголовка будет меняться в зависимости от страницы (Таблица 3.1). Мы достроим тесты из Листинга 3.14, добавив тесты заголовка (тайтла) в соответствии с моделью в Листинге 3.18.
it "should have the right title" do
visit '/static_pages/home'
expect(page).to have_title("Ruby on Rails Tutorial Sample App | Home")
end
Здесь используется метод have_title
, который проверяет наличие HTML заголовка с данным контентом. Другими словами, код
expect(page).to have_title("Ruby on Rails Tutorial Sample App | Home")
проверяет что внутри тега title
содержится
"Ruby on Rails Tutorial Sample App | Home"
Важно отметить что контент не должен быть абсолютно идентичным; достаточно совпадения лишь некоторой части текста, т.е.
expect(page).to have_title("Home")
сработает также как и полный текст заголовка.
Добавление новых тестов для каждой из трех статических страниц, следуя модели из Листинга 3.18, приводит нас к новому StaticPages тесту (Листинг 3.19). Обратите внимание на то что здесь имеется значительное повторение от которого мы избавимся в Разделе 5.3.4.
spec/requests/static_pages_spec.rb
require 'spec_helper'
describe "Static pages" do
describe "Home page" do
it "should have the content 'Sample App'" do
visit '/static_pages/home'
expect(page).to have_content('Sample App')
end
it "should have the title 'Home'" do
visit '/static_pages/home'
expect(page).to have_title("Ruby on Rails Tutorial Sample App | Home")
end
end
describe "Help page" do
it "should have the content 'Help'" do
visit '/static_pages/help'
expect(page).to have_content('Help')
end
it "should have the title 'Help'" do
visit '/static_pages/help'
expect(page).to have_title("Ruby on Rails Tutorial Sample App | Help")
end
end
describe "About page" do
it "should have the content 'About Us'" do
visit '/static_pages/about'
expect(page).to have_content('About Us')
end
it "should have the title 'About Us'" do
visit '/static_pages/about'
expect(page).to have_title("Ruby on Rails Tutorial Sample App | About Us")
end
end
end
Поместив тесты из Листинга 3.19 куда следует, нужно запустить
$ bundle exec rspec spec/requests/static_pages_spec.rb
чтобы проверить что наш код в Красном (провальные тесты).
3.3.2 Прохождение тестов заголовка
Теперь мы заставим тесты заголовков пройти, и в то же время добавим полную структуру HTML чтобы сделать валидные веб-страницы. Разметка современной веб страницы соответствует форме:
<!DOCTYPE html>
<html>
<head>
<title>Greeting</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
Эта структура включает тип документа, или doctype - декларацию в самом верху страницы которая говорит браузерам какую версию HTML мы используем (в данном случае, HTML5);8 head
раздел, в данном случае с “Greeting” внутри тега title
; и раздел body
, в данном случае с “Hello, world!” внутри тега p
(параграф). (Отступы необязательны — HTML нечувствителен к пробелам и игнорирует и табы и пробелы — но они делают структуру документа более смотрибельной.)
Применение этой базовой структуры к странице Home приводит к Листингу 3.20.
app/views/static_pages/home.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Ruby on Rails Tutorial Sample App | Home</title>
</head>
<body>
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
</body>
</html>
Листинг 3.20 использует заголовки протестированные в Листинге 3.19:
<title>Ruby on Rails Tutorial Sample App | Home</title>
Как результат, тесты для Home страницы теперь должны пройти. Мы все еще в Красном из-за провальных Contact и Help тестов, и мы можем попасть в Зеленый с кодом из Листинга 3.21 и Листинга 3.22.
app/views/static_pages/help.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Ruby on Rails Tutorial Sample App | Help</title>
</head>
<body>
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="https://railstutorial.org/help">Rails Tutorial help page</a>.
To get help on this sample app, see the
<a href="https://railstutorial.org/book">Rails Tutorial book</a>.
</p>
</body>
</html>
app/views/static_pages/about.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Ruby on Rails Tutorial Sample App | About Us</title>
</head>
<body>
<h1>About Us</h1>
<p>
The <a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
is a project to make a book and screencasts to teach web development
with <a href="https://rubyonrails.org/">Ruby on Rails</a>. This
is the sample application for the tutorial.
</p>
</body>
</html>
3.3.3 Embedded (встроенный) Ruby
Мы уже многого добились в этом разделе, создав три валидные страницы с использованием Rails контроллеров и действий, но они содержат совершенно статический HTML и, следовательно, не раскрывают возможностей Rails. Кроме того, они страдают от ужасного дублирования:
- Заголовки страниц почти (но не совсем) одинаковые.
- “Ruby on Rails Tutorial Sample App” является общим для всех трех заголовков (тайтлов).
- Весь скелет структуры HTML повторяется на каждой странице.
Этот повторяющийся код - нарушение важного, “Don’t Repeat Yourself” (DRY, “Не Повторяй Себя”) принципа; в этом и следующем разделах мы будем “DRY out” (# сушить) наш код, удаляя повторения.
Как это ни парадоксально, но первым шагом на пути устранения дублирования будет усиление дублирования: мы сделаем названия страниц, которые в настоящее время очень похожи, полностью совпадающими. Это позволит легко удалить все повторения одним махом.
Техника включает в себя использование Embedded Ruby в наших представлениях. Поскольку заголовки страниц Home, Help и About имеют переменную составляющую, мы будем использовать специальную Rails функцию называемую provide
для установки отличающегося заголовка на каждой странице. Мы можем увидеть как это работает заменив буквальный заголовок “Home” в представлении home.html.erb
кодом из Листинга 3.23.
app/views/static_pages/home.html.erb
<% provide(:title, 'Home') %>
<!DOCTYPE html>
<html>
<head>
<title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
</head>
<body>
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
</body>
</html>
Листинг 3.23 это наш первый пример Embedded Ruby, также называемого ERb. (Теперь вы знаете, почему HTML представления имеют расширение .html.erb
.) ERb это основная система шаблонов для включения динамического контента в веб-страницы.9 Код
<% provide(:title, 'Home') %>
указывает с помощью <% ... %> что Rails должны вызвать функцию provide
и связать строку ’Home’
с заголовком :title
.10 Затем, в заголовке, мы используем тесно связанную нотацию <%= ... %> для вставки заголовка в шаблон используя Ruby-функцию yield
:11
<title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
(Разница между этими двумя типами встроенного Ruby заключается в том, что <% ... %> исполняет код внутри, в то время как <%= ... %> исполняет и вставляет его в шаблон.) Получившаяся в результате страница совершенно не изменилась, вот только изменяющаяся часть заголовка динамически сгенерирована встроенным Руби.
Мы можем проверить что все это работает запустив тесты из Раздела 3.3.1 и увидев что они все еще проходят:
$ bundle exec rspec spec/requests/static_pages_spec.rb
Затем мы можем сделать соответствующие замены для Help и About страниц (Листинг 3.24 и Листинг 3.25).
app/views/static_pages/help.html.erb
<% provide(:title, 'Help') %>
<!DOCTYPE html>
<html>
<head>
<title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
</head>
<body>
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="https://railstutorial.org/help">Rails Tutorial help page</a>.
To get help on this sample app, see the
<a href="https://railstutorial.org/book">Rails Tutorial book</a>.
</p>
</body>
</html>
app/views/static_pages/about.html.erb
<% provide(:title, 'About Us') %>
<!DOCTYPE html>
<html>
<head>
<title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
</head>
<body>
<h1>About Us</h1>
<p>
The <a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
is a project to make a book and screencasts to teach web development
with <a href="https://rubyonrails.org/">Ruby on Rails</a>. This
is the sample application for the tutorial.
</p>
</body>
</html>
3.3.4 Устранение дублирования шаблонами
Теперь, когда мы заменили переменную часть заголовков страниц на ERb, каждая из наших страниц выглядит примерно так:
<% provide(:title, 'Foo') %>
<!DOCTYPE html>
<html>
<head>
<title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
</head>
<body>
Contents
</body>
</html>
Другими словами, все наши страницы имеют одинаковую структуру, в том числе, заголовки (из-за Embedded Ruby), исключая содержимое body
тега.
Для того чтобы факторизировать ("вынести за скобки") эту повторяющуюся структуру, Rails предоставляет специальный layout файл называемый application.html.erb
, который мы переименовали в Разделе 3.3.1 и который мы теперь восстановим:
$ mv foobar app/views/layouts/application.html.erb
Для того чтобы получить работающий макет, мы должны заменить дефолтный заголовок на Embedded Ruby из примеров выше:
<title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
Получившийся макет представлен в Листинге 3.25.
app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Ruby on Rails Tutorial Sample App | <%= yield(:title) %></title>
<%= stylesheet_link_tag "application", media: "all",
"data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
</body>
</html>
Обратите здесь внимание на специальную строку
<%= yield %>
Этот код отвечает за вставку содержимого каждой страницы в макет. Нет необходимости понимать как это на самом деле работает; куда важнее то, что использование этого макета обеспечивает, например, преобразование содержимого home.html.erb
в HTML и последующую его вставку на место <%= yield %>.
Также стоит отметить что дефолтный Rails-макет включает несколько дополнительных строк:
<%= stylesheet_link_tag ... %>
<%= javascript_include_tag "application", ... %>
<%= csrf_meta_tags %>
Этот код организует подключение стилей и JavaScript приложения, являющихся частью файлопровода (Раздел 5.2.1), совместно с Rails методом csrf_meta_tags
, который предотвращает подделку межсайтовых запросов (CSRF), вид злонамеренной веб-атаки.
Конечно, представления в Листинге 3.23, Листинге 3.24, и Листинге 3.25 по-прежнему заполнены полной HTML структурой включенной в макет, так что мы должны удалить ее, оставив только внутреннее содержимое. Получившиеся после очистки представления представлены в Листинге 3.27, Листинге 3.28, и Листинге 3.29.
app/views/static_pages/home.html.erb
<% provide(:title, 'Home') %>
<h1>Sample App</h1>
<p>
This is the home page for the
<a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
sample application.
</p>
app/views/static_pages/help.html.erb
<% provide(:title, 'Help') %>
<h1>Help</h1>
<p>
Get help on the Ruby on Rails Tutorial at the
<a href="https://railstutorial.org/help">Rails Tutorial help page</a>.
To get help on this sample app, see the
<a href="https://railstutorial.org/book">Rails Tutorial book</a>.
</p>
app/views/static_pages/about.html.erb
<% provide(:title, 'About Us') %>
<h1>About Us</h1>
<p>
The <a href="https://railstutorial.org/">Ruby on Rails Tutorial</a>
is a project to make a book and screencasts to teach web development
with <a href="https://rubyonrails.org/">Ruby on Rails</a>. This
is the sample application for the tutorial.
</p>
С таким определением представлений, Home, Help, и About страницы точно такие же, как и прежде, но дублирования стало гораздо меньше. Проверка того что набор тестов по-прежнему проходит даст нам уверенность в том что этот рефакторинг кода прошел успешно:
$ bundle exec rspec spec/requests/static_pages_spec.rb
3.4 Заключение
При взгляде со стороны, в этой главе мы вряд ли чего то добились: мы начали со статических страниц, а закончили… в основном статическими страницами. Но внешность обманчива: разрабатывая в терминах Rails, контроллеры, действия и представления, мы теперь в состоянии добавить произвольное количество динамического содержимого на нашем сайте.
Прежде чем двигаться дальше, давайте потратим минуту, чтобы закоммитить наши изменения и объединить их с мастер веткой. Еще в Разделе 3.1 мы создали в Git ветку для разработки статических страниц. Если вы не делали коммиты во время чтения, сначала сделайте коммит указывающий что мы достигли точки остановки:
$ git add .
$ git commit -m "Finish static pages"
Затем объедините изменения с мастер веткой, используя ту же технику, что и в Разделе 1.3.5:
$ git checkout master
$ git merge static-pages
Как только вы достигаете точки остановки, такой как эта, обычно это хорошая идея, чтобы отправить (to push) ваш код в удаленное хранилище (которым, если вы следовали шагам в Разделе 1.3.4, будет GitHub):
$ git push
Если вы хотите, в этот момент вы даже можете развернуть обновленное приложение на Heroku:
$ git push heroku
3.5 Упражнения
- Создайте страницу Contact для примера приложения. Следуя модели в Листинге 3.19, сначала напишите тесты на существовани страницы по URL /static_pages/contact которые проверяют правильность тайтла “Ruby on Rails Tutorial Sample App | Contact”. Добейтесь прохождения тестов и затем заполните страницу Contact содержимым из Листинга 3.30. (Решение этого упражнения является частью Раздела 5.3.)
- Вы могли заметить некоторые повторения в спеке StaticPages контроллера (Листинг 3.19). В частности, базовый заголовок, “Ruby on Rails Tutorial Sample App” одинаков для каждого теста заголовка. Используя RSpec функцию
let
которая создает переменную соответствующую ее аргументу, проверьте что тесты в Листинге 3.31 все еще проходят. Листинг 3.31 вводит интерполяцию строк о которой будет рассказано в Разделе 4.2.2. - (продвинутое) Как отмечается в Heroku page on using sqlite3 for development, хорошей идеей является использование одинаковой базы данных в девелопмент, тест и продакшн окружениях для минимизации возможных несовместимостей. Следуя инструкциям Heroku по локальной установке PostgreSQL установите базу данных PostgreSQL на вашу систему. Обновите ваш
Gemfile
как показано в Листинге 3.32 для устранения гема sqlite3 и использования только pg гема. Вам также следует разузнать о файлеconfig/database.yml
и локальном запуске PostgreSQL. (Обратите внимание - для обеспечения безопасности вам следует добавитьdatabase.yml
в ваш файл.gitignore
, как это показано в Листинге 1.7.) Вашей целью должно быть создание и конфигурирование девелопмент и тестовой баз данных для использования PostgreSQL. Я особенно рекомендую использовать Induction для подключения и просмотра локальной PostgreSQL базы данных. Предупреждение: Это упражнение является очень сложным и я рекомендую его только для продвинутых пользователей. Если вы застрянете, не стесняйтесь пропустить это упражнение. И не переживайте — как было отмечено ранее, пример приложения разрабатываемый в этом учебнике полностью совместим как со SQLite так и с PostgreSQL.
app/views/static_pages/contact.html.erb
<% provide(:title, 'Contact') %>
<h1>Contact</h1>
<p>
Contact Ruby on Rails Tutorial about the sample app at the
<a href="https://railstutorial.org/contact">contact page</a>.
</p>
spec/requests/static_pages_spec.rb
require 'spec_helper'
describe "Static pages" do
let(:base_title) { "Ruby on Rails Tutorial Sample App" }
describe "Home page" do
it "should have the content 'Sample App'" do
visit '/static_pages/home'
expect(page).to have_content('Sample App')
end
it "should have the title 'Home'" do
visit '/static_pages/home'
expect(page).to have_title("#{base_title} | Home")
end
end
describe "Help page" do
it "should have the content 'Help'" do
visit '/static_pages/help'
expect(page).to have_content('Help')
end
it "should have the title 'Help'" do
visit '/static_pages/help'
expect(page).to have_title("#{base_title} | Help")
end
end
describe "About page" do
it "should have the content 'About Us'" do
visit '/static_pages/about'
expect(page).to have_content('About Us')
end
it "should have the title 'About Us'" do
visit '/static_pages/about'
expect(page).to have_title("#{base_title} | About Us")
end
end
describe "Contact page" do
it "should have the content 'Contact'" do
visit '/static_pages/contact'
expect(page).to have_content('Contact')
end
it "should have the title 'Contact'" do
visit '/static_pages/contact'
expect(page).to have_title("#{base_title} | Contact")
end
end
end
Gemfile
необходимый для использования PostgreSQL вместо SQLite.source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0
gem 'rails', '4.0.2'
gem 'pg', '0.15.1'
group :development, :test do
gem 'rspec-rails', '2.13.1'
end
group :test do
gem 'selenium-webdriver', '2.35.1'
gem 'capybara', '2.1.0'
end
gem 'sass-rails', '4.0.1'
gem 'uglifier', '2.1.1'
gem 'coffee-rails', '4.0.1'
gem 'jquery-rails', '3.0.4'
gem 'turbolinks', '1.1.1'
gem 'jbuilder', '1.0.2'
group :doc do
gem 'sdoc', '0.3.20', require: false
end
group :production do
gem 'rails_12factor', '0.0.2'
end
3.6 Продвинутые настройки
Как упоминалось в Разделе 3.2, непосредственное использование команды rspec
не лишено недостатков. В этом разделе мы вначале обсудим способ устранения необходимости каждый раз набирать bundle exec
, а затем настроим установку тестов для автоматизации запуска набора тестов с помошью Guard (Раздел 3.6.2) и, опционально, Spork (Раздел 3.6.3). Наконец мы упомянем о способе запуска тестов непосредственно внутри Sublime Text - технике, особенно полезной в паре со Spork.
Этот раздел предназначен для довольно подвинутых пользователей и может быть пропущен без каких либо потерь целостности. Помимо всего прочего, этот материал вероятно устареет гораздо быстрее чем остальные части учебника, так что не стоит ожидать что на вашей системе все будет происходить в точности как это показано в примерах, и вам возможно даже придется погуглить для того чтобы заставить все работать.
3.6.1 Избавляемся от bundle exec
Как было вкратце отмечено в Разделе 3.2.1, зачастую необходимо предварять такие команды как rake
или rspec
командой bundle exec
для того чтобы программы запускались с набором гемов прописанных в вашем Gemfile
. (По техническим причинам единственным исключением является сама команда rails
.) Такая практика является довольно громоздкой и в этом разделе мы обсудим два способа устранения этой необходимости.
RVM Bundler интеграция
Первый и наиболее предпочтительный метод заключается в использовании RVM, который включает интеграцию с Bundler начиная с версии 1.11. Вы можете проверить что у вас достаточно свежая версия RVM следующим образом:
$ rvm get stable
$ rvm -v
rvm 1.19.5 (stable)
До тех пор пока версия больше чем 1.11.x, установленные гемы будут автоматически извлекаться в правильном Bundler окружении, так что вы можете писать (например)
$ rspec spec/
опуская bundle exec
. Если это ваш случай, вам следует пропустить остальную часть этого раздела.
Если вы по каким-либо причинам ограничены более ранней версией RVM, вы все же можете избежать bundle exec
с помощью RVM Bundler интеграции12 для конфигурирования Менеджера Версий Руби таким образом чтобы он в локальном окружении включал правильные программы автоматически. Шаги просты, хотя и немного загадочны. Для начала, выполните эти две команды:
$ rvm get head && rvm reload
$ chmod +x $rvm_path/hooks/after_cd_bundler
Затем выполните эти:
$ cd ~/rails_projects/sample_app
$ bundle install --without production --binstubs=./bundler_stubs
Эти команды комбинируют магию RVM и Bundler с тем чтобы обеспечить выполнение таких команд как rake
и rspec
в правильном окружении. Поскольку эти файлы специфичны для вашей локальной настройки, вы должны добавить bundler_stubs
директорию в ваш .gitignore
файл (Листинг 3.33).
bundler_stubs
в .gitignore
файл.# Игнорирование конфига bundler
/.bundle
# Игнорирование дефолтной базы данных SQLite.
/db/*.sqlite3
/db/*.sqlite3-journal
# Игнорирование всех логов и временных файлова.
/log/*.log
/tmp
# Игнорирование прочих ненужных файлов.
doc/
*.swp
*~
.project
.DS_Store
.idea
bundler_stubs/
При добавлении другой исполняемой задачи (такой как guard
в Разделе 3.6.2), вам нужно перезапустить команду bundle install
:
$ bundle --binstubs=./bundler_stubs
binstubs
Если вы не используете RVM, вы все же можете избежать набора bundle exec
с помощью гема rubygems-bundler:
$ gem install rubygems-bundler
$ gem regenerate_binstubs
Эта команда создает все необходимые исполняемые файлы локально, таким образом что мы теперь можем запускать набор тестов следующим образом:
$ rspec spec/
Аналогично для rake
, и т.д.:
$ rake db:migrate
При добавлении другой исполняемой задачи (такой как guard
в Разделе 3.6.2), вам возможно придется перезапустить gem regenerate_binstubs
. (Все должно работать нормально и без этого, я упомянул эту возможность просто на всякий случай.)
Ради читателей пропустивших этот раздел, остальная часть этого учебника будет ошибаться в сторону осторожности и явно использовать bundle exec
, но, конечно же, вы запросто можете использовать более компактную версию если ваша система сконфигурирована должным образом.
3.6.2 Автоматизируем тесты с Guard
Одно из неудобств связанных с использованием команды rspec
это необходимость переключаться на командную строку и запускать тесты вручную. (Второе неудобство - медленный запуск набора тестов рассматривается в Разделе 3.6.3.) В этом разделе мы покажем как использовать Guard для автоматизации запуска тестов. Guard отслеживает изменения в файловой системе так что, например, когда мы изменяем файл static_pages_spec.rb
запустится только этот тест. Более того, мы можем настроить Guard таким образом, что при, скажем, изменении файла home.html.erb
автоматически запустится static_pages_spec.rb
.
Вначале мы добавим guard-rspec
в Gemfile
(Листинг 3.34).
Gemfile
включающий Guard для примера приложения.source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0
gem 'rails', '4.0.2'
group :development, :test do
gem 'sqlite3', '1.3.8'
gem 'rspec-rails', '2.13.1'
gem 'guard-rspec', '2.5.0'
end
group :test do
gem 'selenium-webdriver', '2.35.1'
gem 'capybara', '2.1.0'
# OS X: раскомментируйте эти строки.
# gem 'growl', '1.0.3'
# Linux: раскомментируйте эти строки.
# gem 'libnotify', '0.8.0'
# Windows: раскомментируйте эти строки.
# gem 'rb-notifu', '0.0.4'
# gem 'win32console', '1.3.2'
# gem 'wdm', '0.1.0'
end
gem 'sass-rails', '4.0.1'
gem 'uglifier', '2.1.1'
gem 'coffee-rails', '4.0.1'
gem 'jquery-rails', '3.0.4'
gem 'turbolinks', '1.1.1'
gem 'jbuilder', '1.0.2'
group :doc do
gem 'sdoc', '0.3.20', require: false
end
group :production do
gem 'pg', '0.15.1'
gem 'rails_12factor', '0.0.2'
end
Убедитесь в том что вы раскомментировали строки соответствующие вашей системе в тестовой группе. (Обратите внимание - если вы хотите использовать уведомления Growl, вам придется приобрести Growl, который доступен в Apple App Store по весьма скромной цене.)
Затем мы устанавливаем гемы запустив bundle install
:
$ bundle install
Затем инициализируем Guard для работы с RSpec:
$ bundle exec guard init rspec
Writing new Guardfile to /Users/mhartl/rails_projects/sample_app/Guardfile
rspec guard added to Guardfile, feel free to edit it
Теперь отредактируем сгенерированный Guardfile
так чтобы Guard запускал правильные тесты после обновления интеграционных тестов и представлений (Листинг 3.35).
Guardfile
. Обратите внимание на добавленный require
.require 'active_support/inflector'
guard 'rspec', all_after_pass: false do
.
.
.
watch('config/routes.rb')
# Custom Rails Tutorial specs
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) do |m|
["spec/routing/#{m[1]}_routing_spec.rb",
"spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb",
"spec/acceptance/#{m[1]}_spec.rb",
(m[1][/_pages/] ? "spec/requests/#{m[1]}_spec.rb" :
"spec/requests/#{m[1].singularize}_pages_spec.rb")]
end
watch(%r{^app/views/(.+)/}) do |m|
(m[1][/_pages/] ? "spec/requests/#{m[1]}_spec.rb" :
"spec/requests/#{m[1].singularize}_pages_spec.rb")
end
watch(%r{^app/controllers/sessions_controller\.rb$}) do |m|
"spec/requests/authentication_pages_spec.rb"
end
.
.
.
end
Здесь строка
guard 'rspec', all_after_pass: false do
обеспечивает незапуск полного набора тестов после прохождения провальных тестов (для ускорения цикла Красный-Зеленый-Реорганизация).
Теперь мы можем запустить guard
следующим образом:
$ bundle exec guard
Для того чтобы избавиться от необходимости запускать команду с префиксом bundle exec
, повторите шаги из Раздела 3.6.1.
Кстати, если Guard будет жаловаться на отсутствие директории spec/routing
, вы можете исправить ситуацию создав пустую директорию с соответствующим названием:
$ mkdir spec/routing
3.6.3 Ускоряем тесты с помощью Spork
При запуске bundle exec rspec
вы могли заметить что перед началом запуска тестов проходит несколько секунд, тогда как выполнение самих тестов происходит довольно быстро. Это связано с тем, что при каждом запуске тестов RSpec должен перезагрузить все Рельсовое окружение. Тестовый сервер Spork 13 предназначен для решения этой проблемы. Spork загружает окружение однократно, а затем поддерживает пул процессов для запуска следующих тестов. Spork особенно полезен в комбинации с Guard (Раздел 3.6.2).
Первый шаг это добавление spork
гем зависимости в Gemfile
(Листинг 3.36).
Gemfile
для примера приложения.source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0
gem 'rails', '4.0.2'
group :development, :test do
.
.
.
gem 'spork-rails', '4.0.0'
gem 'guard-spork', '1.5.0'
gem 'childprocess', '0.3.6'
end
.
.
.
Затем установите Spork используя bundle install
:
$ bundle install
Затем сделайте начальную загрузку конфигурации Spork:
$ bundle exec spork --bootstrap
Теперь нам необходимо отредактировать конфигурационный файл RSpec spec/spec_helper.rb
таким образом, чтобы окружение загружалось в блоке prefork который обеспечит его однократную загрузку (Листинг 3.37).
Spork.prefork
. spec/spec_helper.rb
require 'rubygems'
require 'spork'
Spork.prefork do
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
# Checks for pending migrations before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)
RSpec.configure do |config|
# ## Mock Framework
#
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
#
# config.mock_with :mocha
# config.mock_with :flexmock
# config.mock_with :rr
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
config.use_transactional_fixtures = true
# If true, the base class of anonymous controllers will be inferred
# automatically. This will be the default behavior in future versions of
# rspec-rails.
config.infer_base_class_for_anonymous_controllers = false
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = "random"
config.include Capybara::DSL
end
end
Spork.each_run do
# This code will be run each time you run your specs.
end
Перед запуском Spork мы можем сделать контрольный замер времени необходимого на прохождения тестов следующим образом:
$ time bundle exec rspec spec/requests/static_pages_spec.rb
......
6 examples, 0 failures
real 0m8.633s
user 0m7.240s
sys 0m1.068s
Здесь набор тестов занимает более семи секунд, даже при том, что фактически тесты отрабатывают менее чем за одну десятую секунды. Чтобы ускорить это мы можем открыть отдельное окно терминала, переместиться в корневой Rails каталог, а затем запустить сервер Spork:
$ bundle exec spork
Using RSpec
Loading Spork.prefork block...
Spork is ready and listening on 8989!
(Для того чтобы избавиться от необходимости запускать команду с префиксом bundle exec
, повторите шаги из Раздела 3.6.1.) В другом терминальном окне, мы теперь можем запустить наш набор тестов с --drb опцией (“распределенный Ruby”) и проверить что издержки, связанные с постоянной перезагрузкой окружения значительно сократились:
$ time bundle exec rspec spec/requests/static_pages_spec.rb --drb
......
6 examples, 0 failures
real 0m2.649s
user 0m1.259s
sys 0m0.258s
Включать опцию --drb при каждом запуске rspec
довольно неудобно, поэтому я рекомендую добавить ее в .rspec
файл расположенный в корневой директории проекта, как это показано в Листинге 3.38.
.rspec
--colour
--drb
Небольшой совет касательно использования Spork: если ваши тесты провальны, а вы думаете, что они должны проходить, проблема может быть связана со Spork, который иногда может не перегружать необходимые файлы. (Например, это часто происходит при изменении конфигурационных файлов, таких как routes.rb
.) При возникновении подобных сомнений, отключите Spork сервер командой Ctrl-C и рестартуйте его:
$ bundle exec spork
Using RSpec
Loading Spork.prefork block...
Spork is ready and listening on 8989!
^C
$ bundle exec spork
Guard и Spork
Спорк особенно полезен в комбинации с Guard, мы можем подружить их следующим образом:
$ bundle exec guard init spork
Затем нам необходимо изменить Guardfile
как в Листинге 3.39.
Guardfile
обновленный для использования Spork. Guardfile
require 'active_support/inflector'
guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'test' },
:rspec_env => { 'RAILS_ENV' => 'test' } do
watch('config/application.rb')
watch('config/environment.rb')
watch('config/environments/test.rb')
watch(%r{^config/initializers/.+\.rb$})
watch('Gemfile')
watch('Gemfile.lock')
watch('spec/spec_helper.rb') { :rspec }
watch('test/test_helper.rb') { :test_unit }
watch(%r{features/support/}) { :cucumber }
end
guard 'rspec', all_after_pass: false, cli: '--drb' do
.
.
.
end
Обратите внимание что мы обновили аргументы guard
включив cli: '--drb', которая обеспечивает использование Guard-ом интерфейса командной строки (cli) для Spork сервера. Мы также добавили команду для слежения за директорией features/support/
, которую мы начнем менять начиная с Главы 5.
Закончив с конфигурацией, мы можем одновременно стартовать Guard и Spork командой guard
:
$ bundle exec guard
Guard автоматически стартует Spork сервер, кардинально уменьшая издержки при запуске тестов.
Хорошо сконфигурированное тестовое окружение с Guard, Spork и (опционально) уведомлениями вызывают привыкание к разработке через тестирование. См. более подробно об этом в скринкастах Rails Tutorial14.
3.6.4 Запускаем тесты внутри Sublime Text
Если вы используете Sublime Text, то у вас есть большое количество вспомогательных команд для запуска тестов непосредственно в редакторе. Для того чтобы научиться пользоваться ими, следуйте инструкциям для своей платформы на Sublime Text 2 Ruby Tests.15 На моей платформе (Macintosh OS X), я могу установить команды следующим образом:
$ cd ~/Library/Application\ Support/Sublime\ Text\ 2/Packages
$ git clone https://github.com/maltize/sublime-text-2-ruby-tests.git RubyTest
Вы также можете пока использовать инструкции по настройке для Rails Tutorial Sublime Text.16
После перезапуска Sublime Text, пакет RubyTest предоставляет следующие команды:
- Command-Shift-R: запуск одного теста (если запускается на
it
блоке) или группы тестов (если выполняется наdescribe
блоке) - Command-Shift-E: запуск последнего теста(-ов)
- Command-Shift-T: запуск всех тестов в текущем файле
Поскольку набор тестов может стать довольно медленным даже для относительно небольших проектов, возможность запускать один тест (или небольшую группу тестов) за раз может сильно упростить жизнь разработчика. Даже один единственный тест требует загрузки Rails окружения, вот почему эти команды отлично работают вместе со Spork: запуск одного теста устраняет необходимость выполнения всего файла с тестами, в то время как Spork устраняет издержки связанные с рестартом тестового окружения. Вот последовательность команд которую я рекомендую:
- Стартовать Spork в терминале.
- Написать один тест или небольшую группу тестов.
- Выполнить Command-Shift-R чтобы убедиться что тест или группа тестов в красном.
- Написать соответствующий код приложения.
- Выполнить Command-Shift-E для повторного запуска того же теста/группы тестов для проверки того что они позеленели.
- Повторить шаги 2 – 5 при необходимости.
- По достижении естественной точки остановки (например, перед коммитом), запустить
rspec spec/
в командной строке для того, чтобы убедиться что полный набор тестов по-прежнему в зеленом.
Даже с возможностью запуска тестов внутри Sublime Text, я по-прежнему предпочитаю использовать Guard, но в настоящий момент меня кормит TDD техника описанная выше.
- Преемник Webrat, Capybara назван в честь крупнейшего грызуна в мире. ↑
- Фактически, вы можете даже опустить
install
—bundle
сам является алиасом дляbundle install
. ↑ - Как и прежде, увеличенный файл из Листинга 1.7 может оказаться более удобным. ↑
- https://github.com/railstutorial/sample_app_rails_4 ↑
- Наш метод создания статических страниц является, вероятно, самым простым, но это не единственный путь. Оптимальный метод действительно зависит от ваших потребностей; если вы ожидаете большое количество статических страниц, использование контроллера StaticPages может стать довольно громоздким, но в нашем демонстрационном приложении нам понадобятся лишь несколько. ↑
- Постоянно писать
bundle exec
это довольно обременительно; в разделе 3.6 описано несколько способов избежать этого. ↑ - Я на самом деле использую черный фон и для терминала и для редактора, но светлый фон лучше выглядит на скриншотах. ↑
- HTML меняется со времненем; явно указав doctype мы увеличили вероятность того что браузеры будут правильно отображать наши страницы в будущем. Экстремально простой doctype
<!DOCTYPE html>
характерен для самого последнего HTML стандарта - HTML5. ↑ - Существует еще одна популярная система темплейтов которая называется Haml, которую лично мне очень нравится, но она пока не настолько популярна чтобы использовать ее во вводном учебнике. ↑
- Опытные Rails программисты возможно ожидали увидеть здесь
content_for
, но он плохо работает с файлопроводом. Функцияprovide
это его замена. ↑ - Если вы изучали Ruby прежде, вы могли бы подозревать, что Rails передает содержимое в блок, и ваша догадка была бы правильной. Но для разработки веб-приложений с Rails это не имеет значения. ↑
- http://rvm.io/integration/bundler/ ↑
- Spork это комбинация spoon-fork (ложка-вилка). Название проекта обыгрывает использование Spork-ом форков POSIX-а. ↑
- https://railstutorial.org/screencasts ↑
- https://github.com/maltize/sublime-text-2-ruby-tests ↑
- https://github.com/mhartl/rails_tutorial_sublime_text ↑