Гибкая разработка
веб-приложений
в среде Rails
Дэйв Томас
Дэвид Хэнссон
Sam Ruby
Dave Thomas
David Heinem eier Hansson
Сэм Руби
Дэйв Томас
Дэвид Хэнссон
С ^П П ТЕР
М осква ■Санкт-Петербург ■Нижний Новгород ■Воронеж
Ростов-на-Дону ■Екатеринбург ■С ам ара ■Новосибирск
Киев ■Харьков ■М инск
2012
ББК 32.988.02
УДК 004.738.5
Р82
ISBN 978-5-459-00312-3
П е р е д вам и н о в о е и зд а н и е б е с т с е л л е р а « A g ile w e b d e v e lo p m e n t w ith R a ils » , н а п и с а н н о г о С э м о м
Р у б и — р у к о в о д и т е л е м A p a c h e S o ftw a r e F o u n d a tio n и р а зр а б о т ч и к о м ф о р м а та A to m , Д э й в о м Т о м а
с о м — а в т о р о м книги « P r o g ra m m in g R u b y » и Д э в и д о м Х э н с с о н о м — с о з д а т е л е м т е х н о л о г и и R a ils.
R a ils п р ед ст а в л я ет с о б о й с р е д у , о б л е г ч а ю щ у ю р а зр а б о т к у , р а зв ер т ы в а н и е и о б с л у ж и в а н и е в е б
п р и л о ж е н и й . З а в р ем я , п р о ш е д ш е е с м о м е н т а е е п е р в о г о р ел и за , R a ils п р о ш л а путь о т м а л о и зв е с т
н о й т е х н о л о г и и д о ф е н о м е н а м и р о в о г о м а сш т а б а и ст а л а и м е н н о т о й с р е д о й , к о т о р у ю в ы б и р а ю т ,
ч т обы со зд а в а т ь так н а зы в а ем ы е « п р и л о ж е н и я W eb 2 .0 » .
Э т а книга, у ж е д а в н о став ш ая н а ст о л ь н о й п о и з у ч е н и ю R u b y o n R a ils, п р е д н а зн а ч е н а д л я в сех
п р о гр а м м и ст о в , с о б и р а ю щ и х с я со зд а в а т ь и разв ер ты в ать со в р е м е н н н ы е в е б -п р и л о ж е н и я . И з п е р
вой ч асти книги вы п о л у ч и т е н а ч а л ь н о е п р е д с т а в л е н и е о язы к е R u b y и о б щ и е с в е д е н и я о са м о й
с р е д е R a ils. Д а л е е н а п р и м е р е с о зд а н и я и н т е р н е т -м а г а зи н а вы и зу ч и т е к о н ц е п ц и и , п о л о ж е н н ы е
в о с н о в у R a ils. В т р ет ь е й ч аст и р а ссм а т р и в а ет ся вся э к о с и с т е м а R a ils: е е ф у н к ц и и , в о зм о ж н о с т и
и д о п о л н и т е л ь н ы е м о д у л и . Ч ет в ер т о е и зд а н и е книги о п и с ы в а ет р а б о т у с R a ils 3.1 и R u b y 1 .9 .2 .
ББК 32.988.02
УДК 004.738.5
Права на издание получены по соглашению с Pragmatic Bookshelf. Все права защищены. Никакая часть данной
книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев
авторских прав.
Введение............................................................................................ 14
Rails является средством гибкой разработки..................................................... 16
Для кого предназначена эта книга........................................................................17
Как нужно читать эту к н и гу .................................................................................18
От издательства.......................................................................................................20
Часть I. Начало.................................................................................. 21
Глава 1. Установка R a ils.............................................................................................22
1.1. Установка под Windows.................................................................................. 23
1.2. Установка под Mac OS X ................................................................................ 24
1.3. Установка под Linux........................................................................................25
1.4. Выбор версии R ails..........................................................................................27
1.5. Настройка среды разработки......................................................................... 27
1.6. Rails и базы данных.......................................................................................... 31
1.7. Наши достижения........................................................................................... 33
Глава 2. Немедленное использование.................................................................... 34
2.1. Создание нового приложения....................................................................... 34
2.2. Привет, Rails!.....................................................................................................36
2.3. Соединение страниц........................................................................................44
2.4. Наши достижения........................................................................................... 46
Глава 3. Архитектура Rails-приложений................................................................48
3.1. Модели, представления и контроллеры...................................................... 48
3.2. Поддержка модели R ails................................................................................ 51
3.3. Action Pack: представление и контроллер...................................................53
Глава 4. Введение в R u b y .......................................................................................... 56
4.1. Ruby — объектно-ориентированный я з ы к .................................................57
4.2. Типы данных.................................................................................................... 59
4.3. Л огика................................................................................................................62
6 Оглавление
Алфавитный указатель.................................................................459
Предисловие к четвертому изданию
Благодарности
Вы, наверное, думаете, что на выпуск нового издания книги нужно потратить мень
ше усилий. Ведь, в конце концов, весь текст уже есть. Остается только подправить
кое-где код, внести в некоторые места незначительные правки — и дело в шляпе.
Да не тут-то было...
Трудно передать все в точности, но у нас сложилось впечатление, что создание
каждого издания этой книги требовало от нас чуть ли не больше усилий, чем вы
пуск самого первого издания. Rails постоянно развивается, и вместе с ней изменя
ется и данная книга. Части приложения Depot переписывались по нескольку раз,
кроме этого обновлялись все комментарии к этому коду. Акцент на применении
REST и исключение ряда средств по мере того, как они попадали в разряд не реко
мендуемых, неоднократно приводили к изменению структуры книги, как только
горячее становилось еле теплым.
Поэтому данная книга не появилась бы на свет без огромной помощи со сторо
ны сообществ Ruby и Rails. Для начала перечислю очень полезных официальных
рецензентов проектов данной книги:
Джереми Андерсон (Jeremy Anderson), Кен Коар (Кеп Соаг), Джеф Коэн (Jeff
Cohen), Джоел Клермонт (Joel Clermont), Джеф Дрейк (Geoff Drake), Паван
Горакави (Pavan Gorakavi), Майкл Юревич (Michael Jurewitz), Майкл Линд-
саар (Mikel Lindsaar), Пол Рейнер (Paul Rayner), Мартин Реверс (M artin
Reuvers), Даг Ротен (Doug Rhoten), Гари Шерман (Gary Sherman), Даванум
Шринивас (Davanum Srinivas), Стефан Туралски (Stefan Turalski) и Хосе
Валим (Jose Valim).
Кроме этого, каждое издание данной книги выпускалось в бета-версии: каждая
версия публиковалась в PD F-формате, и люди комментировали ее в Интернете.
1 http://guides.rubyonrails.org/3_l_release_notes.html
Благодарности 13
Укоротить код Rails и сделать его более читаемым позволяют две другие фило
софские основы этой среды: DRY и превалирование соглашения над конфигу
рацией. DRY означает «don’t repeat yourself», то есть «никогда не повторяться»'.
каждая частичка знаний в системе должна быть выражена только в одном месте.
Чтобы воплотить все это в жизнь, Rails пользуется всей эффективностью языка
Ruby. В Rails-приложениях можно увидеть лишь малую долю повторений, то, что
нужно сказать, говорится только в одном месте, которое зачастую предлагается со
глашениями о MVC-архитектуре, и далее об этом можно уже не беспокоиться. Для
программистов, привыкших работать в других средах веб-разработки, где простое
изменение может заставить их вносить в код программы полдюжины, а то и больше
правок, это было открытием.
Превалирование соглашения над конфигурацией является не менее важным
принципом. Он означает, что в Rails практически для каждого аспекта, связы
вающего в единое целое ваше приложение, имеются рациональные умолчания.
Следуйте соглашениям, и тогда вы сможете написать Rails-приложение, ис
пользуя меньше кода, чем в обычном веб-приложении, написанном на Java и ис
пользующем XML-конфигурацию. Если нужно переписать соглашения, Rails
облегчает и эту задачу.
Разработчики, которые переходят на Rails, замечают еще одну особенность. Rails
не играет в догонялки со ставшими де-факто новыми стандартами: напротив, она
помогает их определять. К тому же Rails облегчает разработчикам интегрирование
в их код таких функций, как интерфейсы AJAX и RESTful, поскольку их поддержка
16 Введение
ИСПОЛНЯЕМ Ы Й К О Д ------------------------------------------------------------------------------
Больш инство демонстрируемых ф рагментов кода взяты из полноценных, работоспособных
примеров, которые можно загрузить.
Если листинг кода можно найти в загружаемых примерах, чтобы облегчить задачу его поис
ка, перед ним будет стоять заголовок (такой же, как в следую щем примере).
1 Скачайте файлы с сайта издательства «Питер» w w w .p ite r.co m или по адресу h ttp ://
p rag p ro g .c o m /title s/ra ils4 /so u rce _ c o d e
Как нужно читать эту книгу 19
► d ef goodbye
end
end
Д ЭВИД Г О В О Р И Т ...---------------------------------------------------------------------------------
Время от времени вам будут попадаться врезки « Д э в и д г о в о р и т ...» . В них Дэвид Хай-
немайер Хэнсон (David Heinem eier Hansson) будет делиться с вами ценными сведениями о
Rails — давать пояснения и рекомендации, показывать трюки и т. п. Поскольку он создатель
Rails, эти врезки не стоит пропускать, если вы хотите подойти к изучению этой среды про
фессионально.
ДЖ О С П РА Ш И В А ЕТ...------------------------------------------------------------------------------
В книге иногда появляется некий мифический разработчик по имени Джо, задаю щий во
просы по сущ еству изучаемого материала, на которые мы отвечаем.
От издательства
Ваши замечания, предложения, вопросы отправляйте по адресу электронной почты
comp@piter.com (издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
Все исходные тексты, приведенные в книге, вы можете найти по адресу http://
www.piter.com.
На веб-сайте издательства http://www.piter.com вы найдете подробную информа
цию о наших книгах.
О сн о в н ы е тем ы :
В первой части этой книги вам будут представлены язык Ruby и среда Rails. Но сна
чала нужно установить оба этих средства и убедиться в их работоспособности.
Чтобы среда Rails заработала на вашей системе, нужно располагать следующими
программными средствами:
О интерпретатором Ruby. Система Rails написана на Ruby, и свои приложения вы
также будете создавать на Ruby. Rails 3.1 требует применения Ruby версии 1.9.2
или Ruby 1.8.7. На Ruby 1.8.6 и Ruby 1.9.1 эта среда работать не будет.
Те различия в версиях Ruby, которые касаются материала данной книги,
рассмотрены в ближайшей врезке;
О используемой в Ruby системой упаковки программных средств, а именно
RubyGems. Этот выпуск основан на RubyGems версии 1.8.7;
О системой Ruby on Rails. Эта книга была написана с использованием Rails
версии 3.1 (на данный момент это Rails 3.1,0);
О интерпретатором JavaScript. Встроенные интерпретаторы этого языка име
ются как в Microsoft Windows, так и в Mac OS X, и Rails будет использовать
ту версию, которая уже установлена на вашей системе. В других операци
онных системах может понадобиться отдельная установка интерпретатора
JavaScript;
О некоторыми библиотеками, в зависимости от операционной системы;
О базой данных. В данной книге используются как SQLite 3, гак и MySQL 5.1.
Для машины, используемой в целях разработки программ, это практически все,
что нужно (не считая редактора, но о редакторах мы поговорим отдельно). Если
Глава 1 • Установка Rails 23
А вот как выглядит тот код, который будет сгенерирован Rails 3.1 вместо него при исполь
зовании Ruby 1.8.7:
Закройте это окно и откройте новое окно командной строки, щелкнув на кнопке
Пуск (Start) и выбрав пункт Выполнить... (Run...), после чего введите cmd и щелкните
на кнопке ОК.
А теперь пользователи Windows могут перейти сразу к разделу 1.4, «Выбор
версии Rails». Там мы и встретимся.
У вас будет запрошен корневой пароль для вашего сервера MySQL. Если оста
вить его поле пустым, он будет запрошен еще несколько раз. Если будет указан
пароль, его нужно будет использовать при создании базы данных, которое рас
сматривается в главе 16, в разделе «Использование MySQL для создания базы
данных».
Поскольку в Ubuntu 11.04 нет предустановленного Ruby 1.9.2, нужно будет его
загрузить и встроить. По нашему мнению, для этого проще всего воспользоваться
При наличии сразу нескольких версий Rails никто не сможет добиться нужного
результата, пока не будет способа выбора одной из них. И такой способ есть. Вызы
вая любую команду r a i l s , можно указать, какая версия Rails будет использоваться,
вставив перед первым аргументом команды полный номер версии, окруженный
символами подчеркивания:
$ r a i l s _ 3 .1 .0 _ --version
Это особенно важно при создании нового приложения, поскольку, если при
ложение создано с использование конкретной версии Rails, оно будет и дальше
продолжать использовать эту версию, даже если на системе установлены более
свежие версии, до тех пор, пока не будет принято решение обновить приложение.
Для обновления нужно будет просто указать номер новой версии в файле Gemfile,
который находится в корневом каталоге вашего приложения, и запустить коман
ду b u n d le i n s t a l l . Более подробно эта команда будет рассмотрена в главе 25, в
разделе «Управление компонентами, от которых зависит работа приложения с
помощью системы Bundler».
Командная строка
Основную работу мы проделываем в командной строке. Хотя появляются все
новые и новые средства графического пользовательского интерфейса (G U I),
помогающие генерировать Rails-приложения и управлять ими, мы считаем,
что командная строка по-прежнему является наиболее эффективным способом
разработки. Поэтому вам стоит потратить немного времени на ознакомление
с командной строкой вашей операционной системы. Нужно выяснить, как она
используется для редактирования вводимых команд, как можно найти и отредак
тировать предыдущие команды и как осуществлять автозавершение вводимых
имен файлов и команд.
Для оболочек Unix B a sh и z s h стандартным является автозавершение с по
мощью клавиши табуляции (так называемое tab -завершение). Оно позволяет
набирать первые несколько символов имени файла, и нажатием клавиши Tab за
ставлять оболочку провести поиск и завершить имя, используя соответствующие
имена файлов.
Управление версиями
Вся наша работа ведется в системе управления версиями (на данный момент в Git).
При создании нового Rails-проекта в Git заносится предмет проверки, и после
прохождения тестов туда передаются все изменения. Обычно передача данных
в репозиторий ведется по нескольку раз в час.
Для Ruby или Rails пока не сущ ествует совершенных IDE (хотя некоторые среды разработки
уже на подходе). Вместо них большинство Rails-разработчиков использую т старые добрые
текстовые редакторы. Оказывается, не стоит драматизировать ситуацию. Используя дру
гие, менее выразительные языки, программисты полагаются на IDE для того, чтобы она
делала за них большую часть рутинной работы: генерировала код, помогала осущ ествлять
навигацию по файловой системе и проводила постоянную компиляцию, моментально вы
давая предупреждения об ошибках.
При работе с Ruby такая мощная поддержка просто ни к чему. Текстовые редакторы, на
подобие TextMate, предоставят вам 90% всего, что вы получали от IDE, выступая при этом
в более легком весе.
Если работа над Rails-проектом ведется совместно с другими людьми, нужно по
думать об установке распределенной системы интеграции —continuous integration
Глава 1 • Установка Rails 29
Редакторы
Мы создаем свои Rails-программы, используя редакторы для программистов. За
годы работы мы поняли, что различные редакторы хороши для работы с различ
ными языками и средами. Например, Дэйв сначала написал эту главу, используя
редактор Emacs, поскольку его режим Filladapt он считает непревзойденным, когда
дело касается форматирования вводимого XML-кода. Сэм внес поправки в эту
главу, используя редактор Vim. Многие считают, что ни Emacs, ни Vim не являются
идеальными средствами для Rails-разработки, и предпочитают пользоваться редак
тором TextMate. Выбор редактора, конечно, дело сугубо личное, но в редакторе для
Rails стоит все же поискать следующие свойства:
О Поддержку выделения синтаксиса Ruby и HTML. Она идеально подойдет
для файлов формата .erb (формат файлов Rails, включающих в себя вставки
фрагментов Ruby в HTML-код).
О Поддержку автоматических отступов и обратных отступов в исходном коде
Ruby. Это свойство не только улучшает эстетическое восприятие: создание
редактором отступов по мере ввода программы — это наилучший способ
отслеживания неправильных структурных вложений в программном коде.
Способность делать обратные отступы важна при пересмотре кода и пере
мещении материала. (Очень удобной представляется способность редактора
TextMate обрабатывать обратные отступы после вставки кода из буфера.)
О Поддержку вставок обычных логических структур Ruby и Rails. Вам при
дется вводить множество коротких методов, и, если IDE создает структуру
метода путем нажатия одной или двух клавишных комбинаций, вы сможе
те сконцентрироваться на главном материале, находящемся внутри этой
структуры.
О Хорошую навигацию по файловой системе. Мы еще увидим, что Rails-
приложения состоят из множества файлов: только что созданное приложение
попадает в мир, состоящий из сорока шести файлов, разбросанных по трид
цати четырем каталогам, — и это еще до создания конкретного наполнения
этого приложения.
Вам нужна среда, помогающая осуществлять быстрые переходы: вы будете
добавлять строчку к контроллеру для загрузки значения, переключаться на
представление и добавлять строчку, чтобы вывести это значение на экран,
а затем переключаться на тестирование, чтобы убедиться, что все сделано
правильно. Такие средства, как Блокнот, в которых для выбора каждого
редактируемого файла приходится работать с диалоговым окном File ►Open,
30 Часть I • Начало
Рабочий стол
Мы не собираемся указывать вам, как следует оформлять Рабочий стол при работе
с Rails, но расскажем, как мы делаем это.
Чаще всего мы занимаемся написанием кода, запуском тестов и исследованием
работы своего приложения в браузере. Поэтому на нашем главном Рабочем столе
разработчика находятся в постоянно открытом состоянии окна редактора и брау
зера. Мы также хотим отслеживать регистрационные записи, генерируемые при
ложением, поэтому держим открытым еще и окно терминала. В нем для прокрутки
содержимого регистрационного файла по мере его обновления мы пользуемся
командой t a i l -f. Обычно это окно настроено на отображение информации очень
мелким шрифтом, чтобы оно занимало меньше места, но, как только будет замечено
появление чего-нибудь интересного, этот шрифт увеличивается, чтобы все можно
было как следует рассмотреть.
Нам также нужен доступ к документации Rails API, которая просматривается
в браузере. Во введении шла речь об использовании команды g e m _ s e rv e r для за
пуска локального веб-сервера, содержащего Rails-документацию. Это, конечно,
удобно, но, к сожалению, Rails-документация разбросана по нескольким отдельным
справочным каталогам. Если у вас есть постоянное подключение к Интернету, вы
можете воспользоваться ресурсом http://api.rubyonrails.org, чтобы увидеть всю Rails-
документацию в одном месте.
На последней стадии следует немного подождать. Когда все закончится, у вас будет до
кументация Rails API в дереве каталогов, начинающ емся с doc/api. Я советую переместить
эту папку на Рабочий стол, а затем удалить дерево dum m y_app.
Для просмотра документации Rails API откройте в своем браузере страницу doc/api/index.
html.
Если вы не сможете найти двоичную версию или если вы все равно хотите
создать драйвер из исходного кода, вам для его создания понадобится среда раз
работки, установленная на вашей машине. При работе под Windows это означает,
что у вас должна быть копия Visual C++. При работе под Linux вам понадобится
gcc и сопутствующие компоненты (которые, скорее всего, уже установлены).
При работе под OS X вам понадобится установить инструментарий разработ
чика (который поставляется вместе с операционной системой, но по умолчанию не
устанавливается). Вам также понадобится установить драйвер вашей базы данных
в подходящую версию Ruby. Если вы установили свою собственную копию Ruby в
обход встроенной копии, важно помнить, что при создании и установке драйвера
базы данных эта версия Ruby должна быть указана в пути поиска первой. Чтобы
убедиться, что Ruby не запускается из каталога /usr/bin, можно воспользоваться
командой w h ic h ruby.
В следующем списке перечислены все доступные адаптеры баз данных и при
ведены ссылки на веб-страницы, где они могут быть найдены:
Firebird http://rubyforge.org/projects/fireruby/
MySQL http://w ww .tm tm .org/en/m ysql/ruby/
Oracle http://rubyforge.org/projects/ruby-oci8
Postgres http://rubyforge.org/projects/ruby-pg
SQLite http://rubyforge.org/projects/sqlite-ruby
О сн о в н ы е тем ы :
П РИ М Е Ч А Н И Е----------------------------------------------------------------------------------------
Если, как описывалось в разделе 1.4 «Выбор версии Rails» нужно указать, какую из версий
Rails использовать, то это следует сделать именно здесь.
rubys> cd work
work> r a i l s new demo
c re a te
c re a te README
c re a te Rakefile
c re a te config.ru
Команда создала каталог по имени demo. Зайдите в него и выведите его содер
жимое (используя I s в окне Unix или d i r при работе под Windows).
Вы должны увидеть набор файлов и подкаталогов:
work> cd demo
demo> d i r /w
[• ] [••] . g itig n o r e [app] [config]
config. ru [db] [doc] Gemfile Gem file.lock
[lib ] [ lo g ] [p u b lic ] Rakefile README
[s c r ip t ] [te s t] [trnp] [vend or]
Поначалу все эти каталоги (и файлы, которые в них содержатся) могут отпуг
нуть, но на данный момент на существование большинства из них можно просто не
обращать внимания. В данной главе непосредственно будет использоваться только
один из них: каталог арр, в котором будет создаваться наше приложение.
36 Часть I • Начало
Включенные туда файлы представляют собой все, что нужно для запуска ав
тономного веб-сервера, способного выполнять наше только что созданное Rails-
приложение. Давайте без лишних проволочек запустим наше приложение под
названием demo:
demo> r a i l s server
=> Bo o tin g W EBrick
=> R a ils 3 .1 .0 a p p lic a tio n s t a r t in g on h t t p :/ / 0 .0 .0.0:3000
=> C a ll w ith -d to detach
=> C trl- C to shutdown s e rv e r
[2011-07-23 10:38:18] INFO W EBrick 1 .3 .1
[2011-07-23 10:38:18] INFO ruby 1 .9 .2 (2011-05-12) [x8 6_6 4-linux]
[2011-07-23 10:38:18] INFO W E B ric k : : H T T P S e rv e r# s ta rt: pid=6044 port=3000
браузер наше радостное приветствие. Как только оно заработает, мы украсим его
показом текущего времени и ссылками.
В главе 3 «Архитектура Rails-приложений» мы выясним, что Rails относится
к среде Модель—Представление—Контроллер (Model—View—Controller). Rails по
лучает входящие запросы от браузера, декодирует запрос для поиска контроллера
и вызывает в контроллере метод, который называется действием. Затем контроллер
вызывает соответствующее представление, отображающее результаты на экране
пользователя. Для нас Rails хороша тем, что берет на себя управление большей
частью внутренних каналов, связывающих все эти действия. Чтобы создать про
стейшее приложение Hello, World!, нам нужен код для контроллера и представления
и нужен связывающий их маршрут. Для модели нам код не нужен, поскольку мы
не имеем дела с данными. Намнем с контроллера.
Затем контроллер вызовет конкретное представление, чтобы пользователь мог
увидеть результаты.
B r o w s e th e
Welcome aboard d o c u m e n ta tio n
A b o u t v o u r a p p li c a ti o n 's e n v ir o n m e n t R a ils G u id es
R a ils API
R u b y core
d e f goodbye
end
шж тшштшь&шшшшш
С ti О localhost ☆ -ч
Привет от Rails!
— m o d e ls /
Придание динамичности
Пока у нашего Rails-приложения довольно скучный вид —оно просто отображает
статичную страницу. Для придания ему динамичности давайте заставим его по
казывать текущее время при каждом отображении страницы.
Для этого необходимо внести изменения в файл шаблона в представлении: те
перь в него нужно включить время в виде строки. При этом возникает два вопроса.
Во-первых, как добавляется динамическое содержимое к шаблону? И во-вторых,
откуда мы возьмем это время?
Динамическое содержимое
В Rails есть множество способов создания динамических шаблонов. Наиболее рас
пространенный из них, которым мы здесь воспользуемся, заключается во вставке
Глава 2 • Немедленное использование 41
кода Ruby непосредственно в шаблон. Именно поэтому наш файл шаблона назы
вается hello.html.erb —суффикс .html.erb предписывает Rails расширить содержимое
файла с помощью системы, которая называется ERB.
ERB — это фильтр, устанавливаемый как часть Rails, который берет файл
.erb и выдает преобразованную версию. Выходной файл в Rails чаще всего имеет
формат HTML, но вообще-то может иметь какой угодно формат. Обычно содер
жимое пропускается через фильтр без изменений. А содержимое, находящееся
между группами символов <%= и %>, интерпретируется как Ruby-код, который
выполняется. В результате этого выполнения содержимое превращается в стро
ку, значение которой подставляется в файл вместо последовательности <%=...%>.
Например, внесите в hello.html.erb изменения, позволяющие вывести текущее
время:
1<= !®
(^} Demo
^ С Й © lo c a l h o s t :00CVsay. h e llo ☆ Л
Привет от Rails!
С ей ч ас 2 0 1 1 -1 0 -0 9 13:21:13 + 0 3 0 0
1
Добавление времени
Исходная задача заключалась в демонстрации времени пользователям нашего
приложения. Теперь мы знаем, как заставить приложение выводить динамические
данные. Но нужно решить еще одну задачу: откуда брать время?
Мы показали, что подход со вставкой вызова Ruby-метода T im e .n o w ( ) в на
шем шаблоне hello.htm l.erb работает. При каждом обращении к этой странице
1 В Ruby 1.8.7 стандартный формат был другим и выглядел примерно следующим об
разом: Mon Jul 25 09:43:25 -0400 2011.
42 Часть I • Начало
пользователь будет видеть текущее время, вставленное в тело ответа. Для нашего
простейшего приложения этого может быть вполне достаточно. Но нам все-таки хо
чется сделать все немного по-другому. Мы переместим определение отображаемого
значения текущего времени в контроллер, а представлению оставим простую работу
по его отображению. Для этого мы изменим метод действия в контроллере, чтобы
присвоить значение времени переменной экземпляра под названием @time:
После обновления окна браузера мы опять увидим текущее время, что свиде
тельствует об успешной связи между контроллером и представлением.
УПРОЩЕНИЕ РАЗРАБОТКИ------------------------------------------------------------------
В процессе той разработки, которой мы только что занимались, уж е можно было кое-что
заметить. Когда к уж е работающ ему приложению добавлялся какой-нибудь код, его не при
ходилось перезапускать. Все необходимое делалось без нашего участия в фоновом режиме.
И каждое вносимое нами изменение становилось доступным, как только мы обращ ались
к приложению через браузер. Благодаря чему все это происходило?
Оказывается, благодаря достаточно разумному поведению диспетчера Rails. В режиме раз
работки (в отличие от режимов тестирования или эксплуатации) при поступлении нового
запроса диспетчер автоматически перезагруж ает исходные файлы приложения. Таким об
разом, при редактировании приложения диспетчер обеспечивает запуск самой последней
версии. Для режима разработки это очень хорош ее качество.
Тем не менее за такую гибкость поведения приходится платить — она приводит к неболь
шой паузе между вводом URL-адреса и откликом приложения, которая вызвана тем, что
диспетчер осущ ествляет перезагрузку. Для разработки это вполне разумная цена, но при
эксплуатации готового приложения такой режим будет неприемлем. Поэтому перед раз
вертыванием приложения этот режим отклю чается (см. главу 16 «Задача Л: развертывание
и эксплуатация»).
Обратимся еще раз к нашему уже испытанному браузеру, но теперь уже укажем
на новое действие, воспользовавшись URL http://localhost:3000/say/goodbye. Должна
появиться картинка, похожая на эту:
1о
Q Demo
О Л О lo c a l h o s t ☆ \]
До свиданья!
I О чень п р иятно, ч то вы нас навестили.
Теперь нам нужно связать эти два экрана. Мы поместим на страницу hello ссыл
ку, которая приведет нас на страницу goodbye, и наоборот. В настоящем приложении
для этого, скорее всего, понадобится создать соответствующие кнопки, но сейчас
мы воспользуемся простыми гиперссылками.
Нам уже известно, что в Rails используется соглашение о синтаксическом раз
боре URL-адреса с целью получения целевого контроллера и имеющиеся внутри
Глава 2 • Немедленное использование 45
<р>
С к а за ть <а href="/say/goodbye">flo с в и д а н ь ж / а > !
</р>
<Р>
С к а за ть <а href= "/say/hello"> npnBeT< /a> !
</р>
Конечно, все это будет работать, но сам по себе этот код слишком несовершенен.
Если придется перемещать наше приложение в другое место на веб-сервере, URL-
адреса уже не будут указывать на правильное место. К тому же в наш код также
закладываются предположения об используемом в Rails формате URL-адреса,
который в будущих версиях Rails может измениться.
К счастью, мы можем обойтись без всякого риска. Rails поставляется с целым
пакетом вспомогательных методов, которые могут использоваться в шаблонах
представлений. В данном случае мы воспользуемся вспомогательным методом
l i n k _ t o ( ), создающим гиперссылку на действие. (Метод l i n k _ t o ( ) способен на
большее, но пока ограничимся только этой возможностью.) При использовании
l i n k _ t o ( ), hello.html.erb приобретает следующий вид:
► Пора с к а з а т ь
► <%= lin k _ t o "до св и д а н и я ", say_goodbye_path %>!
►</р>
Начнем с того, что lin k _ to — это вызов метода. (В Rails методы, облегчающие
написание шаблонов, называются помощниками.) Если вам приходилось работать
46 Часть I • Начало
с такими языками, как Java, отсутствие обязательных круглых скобок вокруг ар
гументов метода может вызвать удивление. Но если они вам нравятся, их всегда
можно добавить.
Фрагмент s a y _ g o o d b y e _ p a t h является заранее вычисленным значением,
которое Rails делает доступным для представлений приложения. Этот фрагмент
превращается в путь /say/goodbye. Со временем вы увидите, что Rails предоставляет
возможность дать имена всем маршрутам, которые будут использоваться в вашем
приложении.
А теперь вернемся к приложению. Если направить браузер на открытие нашей
страницы hello, то на ней будет ссылка на страницу goodbye:
1.с 1 .ё ) № Э м (
О Demo
С ’ lo c a lh o s t & Л
Привет от Rails!
С ейч ас
rails31/work/demo5/app/views/say/goodbye.htm l. erb
<И1>До свиданья! </hl>
<P>
Очень приятно, что вы нас навестили.
</р>
Ликвидация последствий
Если вы, следуя за повествованием, набирали код, то, возможно, приложение все
еще работает на вашем компьютере. Когда приблизительно через десять страниц мы
приступим к программированию нашего следующего приложения, при его первом
запуске возникнет конфликт, поскольку для общения с браузером оно также по
пытается воспользоваться компьютерным портом 3000. Сейчас как раз настало
время остановить текущее приложение, нажав комбинацию клавиш Ctrl+C в том
окне, которое использовалось для его запуска.
А теперь перейдем к обзору Rails.
Архитектура Rails-
приложений
Основные темы:
> модели;
> представления;
> контроллеры .
I
3 Контроллер вы зы вает представление
База
П редставление Модель
данн ы х
Ruby on Rails также относится к среде MVC. Rails навязывает структуру для
вашего приложения — вы разрабатываете модели, представления и контроллеры
как отдельные функциональные блоки, a Rails при выполнении вашей программы
связывает их вместе. Изюминкой Rails является то, что процесс увязки базируется
на использовании разумных умолчаний, которые, как правило, избавляют вас от
написания каких-либо внешних конфигурационных метаданных, обеспечивающих
взаимную работу. Приоритет соглашения над конфигурацией является примером
философии Rails.
В Rails-приложении входящий запрос сначала посылается маршрутизатору,
который решает, в какое место приложения должен быть отправлен запрос и как
должен быть произведен синтаксический разбор этого запроса. В конечном итоге
на данном этапе где-то в коде контроллера идентифицируется конкретный метод
(называемый в Rails действием). Действие может искать запрошенные данные,
может взаимодействовать с моделью и может вызвать другое действие. В результате
выполнения действие подготавливает информацию для представления, которое
создает изображение для пользователя.
Rails обрабатывает входящие запросы по схеме, показанной на рис. 3.2. В дан
ном примере приложение уже вывело на экран страницу каталога товаров и
пользователь только что щелкнул на кнопке Добавить в корзину, расположенной
рядом с одним из товаров. Щелчок на этой кнопке приводит к отправке сообще
ния по адресу http://iocalhost:3000/!ine_item s?productjd=2, где l i n e _ i t e m s — это
ресурс в нашем приложении, а 2 — это внутренний идентификатор выбранного
товара.
О http://my.url/lineJtems?product_id=2
Маршрутизатор
ф Маршрутизатор находит контроллер
каталога товаров Lineltems
'3> Контроллер взаимодействует с моделью
I Контроллер
каталога
товаров
а Контроллер вызывает представление
(Ь) Представление отображает следующий
экран браузера
Представление
каталога
товаров
Объектно-реляционное отображение
Отображением таблиц баз данных на классы занимаются библиотеки O R M . Если
в базе данных есть таблица под названием o r d e r s (заказы), у нашей програм
мы будет класс по имени O r d e r . Сроки в этой таблице соответствуют объектам
класса — конкретный заказ представлен объектом класса O r d e r. Для получения
и установки значений отдельных столбцов используются свойства этого объекта.
У нашего объекта O r d e r есть методы получения и установки количества товара,
размера налога с продаж и т. д.
Кроме этого Rails-классы, поглотившие таблицы нашей базы данных, предо
ставляют набор методов на уровне класса, которые выполняют операции на уровне
всей таблицы. Например, может потребоваться найти заказ с конкретным иденти
фикатором. Этот поиск реализуется в виде метода класса, возвращающего соот
ветствующий объект O r d e r. В коде Ruby это может иметь следующий вид:
o rd er = O rd e r.fin d (l)
puts "Клиент # {o rd e r .c u s to m e r_ id }, ко л и чество = $ # {о ^ ег. amount}"
Active Record
Active Record — это ORM -вставка, предоставляемая Rails. Она строго следует
стандартам ORM-модели: таблицы отображаются на классы, строки —на объекты,
а столбцы — на свойства объекта. От большинства других ORM-библиотек она от
личается способом конфигурирования. Основываясь на соглашении и приступая
к работе с оптимальными настройками по умолчанию, Active Record сводит к ми
нимуму объем настроек, выполняемых разработчиками.
Чтобы проиллюстрировать эту особенность, рассмотрим программу, исполь
зующую Active Record для поглощения нашей таблицы заказов:
re q u ire ' a c t iv e _ r e c o r d '
o rd er = O rd e r.fin d (l)
o rd e r.p a y _ ty p e = "Зака з товара"
o rd e r.s a v e
компонентом. Все как раз наоборот, Rails предоставляет вам разделение, необ
ходимое для создания приложений с четко разграниченным кодом для логики
управления и представления.
Поддержка представления
Представление в Rails отвечает за создание полного или частичного ответа,
отображаемого в браузере, обработанного приложением или посланного в виде
электронной почты. В простейшем виде представление является фрагментом
HTML-кода, отображающего какой-нибудь неизменный текст. Но чаще всего вам
потребуется включить динамическое содержимое, созданное методом действия
в контроллере.
Динамическое содержимое в Rails генерируется шаблонами трех видов. В са
мой распространенной схеме создания шаблонов, которая называется встроенным
Ruby —Embedded Ruby (ERb), фрагменты кода Ruby вставляются в представляе
мый документ, что во многом похоже на способ, применяемый в других веб-средах,
например в РН Р или в JSP. При всей гибкости данного подхода, некоторые спе
циалисты озабочены тем, что он нарушает сам смысл МУС. Вставляя код в пред
ставление, мы рискуем заложить в него логику, которая должна быть в модели или
в контроллере. Как и во всем остальном, разумная умеренность будет полезной,
а злоупотребление может превратиться в серьезную проблему. Соблюдение четкого
разделения интересов является частью труда разработчика. (HTML-шаблоны будут
рассмотрены в разделе 25.2 «Генерация HTML с ERb».)
ERb можно также использовать для конструирования на сервере JavaScript-
фрагментов, выполняемых в браузере. Эта технология отлично подходит для
создания динамичных AJAX-интерфейсов. К этому вопросу мы еще вернемся
в разделе 11.2.
В Rails также имеется такой инструмент, как XML Builder, позволяющий кон
струировать XML-документы, использующие код Ruby, —структура генерируемого
XML будет автоматически следовать за структурой кода. Шаблоны x m l . b u i l d e r
будут рассмотрены, начиная с раздела 25.1.
И наконец, контроллер!
Контроллер Rails является логическим центром вашего приложения. Он коорди
нирует взаимодействие между пользователем, представлениями и моделью. Но с
большинством этих взаимодействий Rails справляется без вашего участия, а со
здаваемый вами код концентрируется на функциональности прикладного уровня.
Это существенно упрощает разработку и поддержку кода Rails-контроллера.
Контроллер также является местом для нескольких важных вспомогательных
служб:
О Он отвечает за перенаправление внешних запросов внутренним действиям.
Он отлично справляется с удобными для человеческого восприятия URL-
адресами.
Глава 3 • Архитектура Rails-приложений 55
О сн овн ы е тем ы :
Для многих, кто только начинает знакомиться с Rails, также в новинку и Ruby. При
знакомстве с такими языками, как Java, JavaScript, РНР, Perl или Python, освоить
Ruby будет нетрудно.
Эта глава не является полноценным введением в Ruby. В ней не рассматрива
ются такие темы, как порядок выполнения действий (как и в большинстве других
языков программирования, в Ruby 1+2*3==7). Она предназначена для объяснения
Ruby только в том объеме, который позволяет сделать приводимые в книге при
меры более понятными.
Эта глава во многом повторяет материал главы 2 книги «Programming Ruby»1.
Если у вас есть потребность в более основательном изучении языка Ruby, то мы,
рискуя быть обвиненными в преследовании личных интересов, все же хотели бы
предложить лучший способ изучения Ruby и лучший справочник по классам, мо
дулям и библиотекам этого языка —книгу «Programming Ruby» (известную также
как «PickAxe»). Добро пожаловать в сообщество Ruby!
' David Thomas, Chad Fowler, and Andrew Hunt. Programming Ruby: The Pragmatic
Programmer’s Guide. The Pragmatic Bookshelf, Raleigh, NC and Dallas, TX, Third,
2008.
Глава 4 • Введение в Ruby 57
Методы
Давайте напишем метод, возвращающий теплое персональное пожелание. А затем
мы этот метод пару раз вызовем:
d ef say_goodnight(nam e)
r e s u lt = 'Спокойной ночи, ' + name
re tu rn r e s u lt
# Пора с п а т ь . . .
puts s a y _ g o o d n ig h t(’ Мэри-Эллен' )
puts s a y _ g o o d n ig h t(' крошка Джон')
Строки
В предыдущем примере был, кроме всего прочего, показан ряд строковых объектов.
Один из способов создания строкового объекта заключается в использовании стро
ковых литералов, представляющих собой последовательности символов внутри
одинарных или двойных кавычек. Разница между двумя формами состоит в объеме
той обработки, которой подвергается строка со стороны Ruby при создании лите
рала. В случае использования одинарных кавычек Ruby практически ничего не
делает. За некоторыми исключениями все, что набрано в строковом литерале, за
ключенном в одинарные кавычки, становится значением строки.
В случае использования двойных кавычек Ruby проводит более серьезную
работу. Во-первых, он ищет подстановки — последовательности, начинающиеся
с символа обратного слэша, — и заменяет их неким двоичным значением. Самой
распространенной подстановкой является пара символов \п, которая заменяется
кодом символа новой строки. При записи строки, содержащей эту подстановку на
консоль, \п приводит к переносу строки.
Во-вторых, в строках, заключенных в двойные кавычки, Ruby выполняет встав
ку результата вычисления выражения. Имеющаяся в строке последовательность
#{ выражение } заменяется значением выражения. Этим можно воспользоваться,
чтобы переписать наш предыдущий метод:
def say_goodnight(nam e)
"Спокойной ночи, #{name. c a p i t a l i z e } "
end
puts s a y _ g o o d n ig h t(' p a ' )
Когда Ruby создает этот строковый объект, он смотрит на текущее значение name
и подставляет его в строку. В конструкции #{...} допускаются выражения произволь
ной сложности. В данном случае вызывается метод c a p i t a l i z e ( ), определенный для
всех строк, чтобы выдать наш аргумент с первой буквой в верхнем регистре.
Строки являются довольно простым типом данных, содержащим упорядочен
ную совокупность байтов или символов. Ruby также предоставляет средства для
определения коллекций произвольных объектов с помощью массивов и хэшей.
Массивы и хэши
Массивы и хэши в Ruby являются индексированными коллекциями. В них хранят
ся совокупности объектов, доступных при использовании ключа. В массивах ключ
является целым числом, а кэши в качестве ключа поддерживают любой объект.
60 Часть I • Начало
уникальны —не может быть двух записей с ключом : барабан. В качестве ключей
и значений хэша могут использоваться объекты любого типа — могут быть хэши,
значения которых представлены массивами, другими хэшами и т. д. Как правило,
в Rails в качестве ключей для хэшей используются обозначения. Многие хэши в
Rails подверглись незначительной модификации, поэтому в качестве ключей при
вставке и просмотре значений в равной степени могут использоваться как строки,
так и обозначения.
Использование обозначений в качестве ключей хэшей стало настолько при
вычным, что, начиная с Ruby 1.9, для этого используется специальный синтаксис,
более простой для набора и более наглядный:
in s t _ s e c t io n = {
виолончель: 'струнные инструменты' ,
кларнет: 'деревянные духовые инструменты' ,
барабан: 'ударные инструменты' ,
гобой: 'деревянные духовые инструменты' ,
труба: 'медные духовые инструменты’ ,
скрипка: 'струнные инструменты'
>
Не правда ли, смотрится намного лучше?
Можно использовать любой понравившийся вам синтаксис. Можно даже сме
шивать в одном выражении разные варианты. Разумеется, если в качестве ключей
используются не обозначения или если используется Ruby 1.8.7, следует придер
живаться синтаксиса, применяющего «стрелки». Но похоже, что многие разработ
чики отдают предпочтение новому синтаксису, и Rails будет даже генерировать
временные платформы (scaffolds), используя новый синтаксис, если обнаружит,
что у вас запущен Rails 1.9.2.
При индексном доступе к элементам хэша используются те же квадратные
скобки, что и в массивах:
i n s t _ s e c t i o n [ : гобой] #=> 'деревянные духовые инструменты'
i n s t _ s e c t i o n [ : виолончель] #=> 'струнные инструменты'
i n s t _ s e c t i o n [ :фагот] #=> n i l
Есть еще один тип данных, вполне застуживающий внимания, —это регулярные
выражения.
62 Часть I • Начало
Регулярные выражения
Регулярные выражения позволяют указать шаблон символов для его сравнения
со строкой. В Rubv регулярное выражение обычно создается в формате / шаблон/
или %г {шаблон}.
Например, используя выражение / P e r l | Python/, можно создать шаблон, соот
ветствующий строке, в которой содержится текст «Perl» или текст «Python».
Шаблон ограничивается знаками прямых слэшей и содержит два сравниваемых
фрагмента, разделенные вертикальной линией ( | ). Символ вертикальной черты
означает «либо то, что слева, либо то, что справа», в данном случае — либо Perl,
либо Python. В шаблонах, как и в арифметических выражениях, можно исполь
зовать скобки, поэтому данный шаблон можно написать следующим образом:
/Р(ег11 y th o n )/. В программах для сравнения строк с регулярными выражениями
используется оператор сравнения =~:
if li n e =~ / Р (е г 1 |yth o n )/
puts “ Похоже, здесь используется другой язык сценариев"
4.3. Логика
Вызовы методов являются инструкциями. В Ruby также предоставляется ряд спо
собов принятия решения, влияющих на повторения и на тот порядок, в котором
вызываются методы.
Управляющие структуры
В Ruby имеются все стандартные управляющие структуры, например инструкции
i f и циклы w hile. Программисты, работавшие на Java, С и Perl, могут заметить,
Глава 4 • Введение в Ruby 63
что вокруг тел этих операторов отсутствуют фигурные скобки. Вместо них для
обозначения окончания тела в Ruby используется ключевое слово end:
if count > 10
puts "Попробуйте еще раз"
e l s i f t r i e s == 3
puts "Вы проиграли"
Блоки и итераторы
Блоки кода —это фрагменты программного кода, заключенные в фигурные скобки
или помещенные между ключевыми словами d o . . , end. По общепринятому согла
шению, фигурные скобки используются для однострочных блоков, а конструкция
do/end —для многострочных:
{ puts "Привет" } # это блок
do ###
c lu b .e n r o ll(p e r s o n ) # и это тоже блок
p e rso n . s o c ia liz e #
end ###
Чтобы передать блок методу, его нужно поместить после аргументов метода
(если таковые имеются). Иными словами, начало блока нужно поместить в конец
64 Часть I • Начало
Исключения
Исключения являются объектами (класса E x c e p tio n или его подклассов). Для
выдачи исключения используется метод r a is e . Тем самым прерывается нормаль
ная последовательность выполнения программного кода, и Ruby просматривает
в обратном порядке стек вызовов в поиске кода, который сообщит о возможности
обработки этого исключения.
Соответствующие классы исключений перехватываются с помощью выраже
ний re sc u e как методами, так и блоками кода, помещенными между ключевыми
словами begin и end.
Глава 4 • Введение в Ruby 65
begin
co n ten t = lo a d _b lo g _d ata(file_n am e)
rescue BlogDataNotFound
STDERR. puts "Файл #{file_nam e} не найден"
rescue Blog D ataForm atErro r
STDERR.puts "В файле #{file_nam e} отсутствую т данные блога"
rescue Ex cep tio n => exc
STDERR.puts " Общая ошибка загрузки # {file _n a m e }: # {e x c.m essa g e }"
end
Классы
Определение класса имеет в Ruby следующий вид:
Строка
1 c la s s Order < A c tiv e R e c o rd : : Base
has_many :lin e _ ite m s
d e f s e l f ,fin d _a ll_u n p a id
5 s e l f . w h e r e ( ' paid = 0 ' )
end
def to t a l
sum = 0
10li n e _ it e m s . each { | l i | sum += l i . t o t a l }
sum
end
end
d e f name
(Sname
end
def name=(new_name)
(Sname = new_name
end
g = G r e e t e r .п еш ("Бзрни")
puts g.name #=> Барни
g.name = "Б е тти "
puts g.name #=> Бетти
Для создания этих методов доступа Ruby предоставляет свои, очень удобные
методы (это должно понравиться тем, кто уже устал от написания всех этих из-
влекателей и установщиков значений свойств):
c la s s G re e te r
a t tr _ a c c e s s o r :name # создание методов чтения и записи
a t t r _ r e a d e r : g re e tin g # создание только метода чтения
a t t r _ w r i t e r :age # создание только метода записи
p ro te cte d
p r iv a te
Модули
Модули похожи на классы тем, что они содержат коллекцию методов, констант,
а также определений других модулей и классов. В отличие от классов, создавать
объекты на основе модулей невозможно.
Модули служат двум целям. Во-первых, они работают как пространства имен,
позволяя определять методы, чьи имена не будут конфликтовать с именами ме
тодов, определенных в каком-нибудь другом месте. Во-вторых, они позволяют
иметь для классов общие функциональные возможности — если класс смешива
ется с модулем, методы экземпляра модуля становятся доступны, как будто они
были определены в самом классе. С одним и тем же модулем могут смешиваться
несколько классов, вместе используя функциональные возможности модуля без
привлечения механизма наследования. Можно также смешать с отдельным классом
сразу несколько модулей.
Примером использования модулей в Rails могут послужить вспомогательные
методы. Rails автоматически смешивает вспомогательные модули с соответствую
щими шаблонами представления. Например, если нужно написать вспомогатель
ный метод, который может быть вызван из представления, вызванного контролле
ром s t o r e , можно в файле store_helper.rb каталога app/helpers определить следующий
модуль:
module Sto re H e lp e r
d ef c a p it a liz e _ w o r d s (s t r in g )
s t r i n g . s p l i t ( ' ').m ap {|w o rd | word. c a p i t a l i z e } . j o i n ( ' ')
end
YAML
YAML1 — это рекурсивный акроним, означающий YAML Ain’t Markup Language
(YAML — не язык разметки). Применительно к Rails YAML используется как
удобный способ определения конфигураций баз данных, тестовых данных и пре
образований. Например:
development:
a d a p te r: s q lit e 3
database: d b /d evelo p m en t.sq lite3
pool: 5
tim e o u t: 5000
1 http://www.yaml.org/
Глава 4 • Введение в Ruby 69
Затем будет дан краткий обзор характерных особенностей, которые помогут про
граммированию в среде Rails.
t.tim estam p s
end
end
Даже не зная языка Ruby, можно понять, что этот код создает таблицу по имени
p r o d u c t s . При создании этой таблицы определяются поля t i t l e , d e s c r i p t i o n ,
i m a g e _ u r l и p r i c e , а также несколько временных меток, t im e s t a m p s (они будут
рассмотрены в разделе 23.1).
Давайте посмотрим на этот пример с точки зрения Ruby. В нем определяется
класс по имени C r e a t e P r o d u c t s , который наследуется из класса M i g r a t i o n модуля
A c t iv e R e c o r d . Определяется один метод по имени c h a n g e ( ). Этот метод вызывает
единственный метод класса (определенный в A c t i v e R e c o r d : : M i g r a t i o n ) , пере
давая ему имя таблицы в форме обозначения.
Вызову c r e a t e _ t a b l e ( ) также передается блок, который должен быть вы
числен до создания таблицы. При вызове этого блока ему передается объект по
имени t , который используется для сбора списка полей. Для этого объекта Rails
определяет ряд методов, которые носят названия стандартных типов данных.
При вызове эти методы просто добавляют определения полей к накапливаемому
набору имен.
Определение d e c i m a l также принимает ряд необязательных параметров, пред
ставленных хэшем.
Тем, кто не знаком с Ruby, кажется, что для решения такой простой проблемы
привлекается слишком много сложных механизмов, а для тех, кто знаком с этим
языком, ни один из этих механизмов не кажется особенно сложным. В любом случае
Rails широко использует средства, предоставляемые Ruby, для того, чтобы сделать
операции определения (например, миграционных задач) как можно проще и опи-
сательнее. Даже такие незначительные особенности языка, как необязательные
70 Часть I • Начало
О сн о в н ы е тем ы :
Примеры использования
Пример использования — это обыкновенное утверждение о том, как кто-нибудь
использует систему. Консультанты изобретают подобные фразы, чтобы навесить
ярлыки на вполне очевидные понятия, и то, что необычные слова всегда ценят
ся выше, чем обычные, которые на самом деле куда значимее, — это издержки
бизнеса.
Примеры использования приложения Depot довольно просты (что для не
которых прозвучит трагически). Начнем с определения двух разных ролей или
действующих субъектов: покупателя и продавца.
76 Часть II • Создание приложения
Последовательность страниц
В любом случае нам нужно выработать замысел устройства основных страниц на
шего приложения и составить примерное представление о том, как пользователи
будут переходить со страницы на страницу. На ранней стадии разработки эта по
следовательность страниц, скорее всего, будет неполной, но она все же поможет
нам сфокусироваться на том, что должно быть сделано, и определить последова
тельность действий.
Возможно, кому-то нравится создавать макеты страниц веб-приложения
в Photoshop, Word или (как ни странно) с помощью HTML. А мне нравится ра
ботать с карандашом и бумагой. Так быстрее, и клиент также может включиться
в работу, взяв карандаш и набросав варианты на бумаге.
Первый набросок последовательности страниц для покупателя показан на
рис. 5.1. В нем нет ничего особенного. Покупатель видит страницу каталога, на ко
торой он поштучно выбирает товар. Каждый выбранный товар попадает в корзину,
которая демонстрируется после каждого выбора. Покупатель может продолжить
покупки, используя страницы каталога, или подтвердить содержимое корзины
и купить находящийся в ней товар. В процессе подтверждения мы получаем кон
тактные и платежные сведения, а затем выставляем счет. Мы еще не знаем, как
будем оформлять платеж, поэтому его детали в этой последовательности не имеют
ясных очертаний.
Последовательность страниц продавца показана на рис. 5.2. Она также не от
личается особой сложностью. После входа в программу продавец видит меню, по
зволяющее ему создать запись о товаре или просмотреть существующую запись,
а также отправить существующие заказы. При просмотре товара продавец может
выборочно отредактировать информацию о товаре или вовсе удалить касающуюся
его запись.
Глава 5 • Интернет-магазин 77
Агеев В и к то р Иванович
г. С а н к т-П е те р б у р г
1 хкни га руб. —
2 х карандаша руб.
Способ оплаты:
Д оставить
| О тлож и ть /
( пока )
к о то ра я используется П оказать
для ввода то в а р а следующий
Данные
И, наконец, нужно подумать о данных, с которыми мы собираемся работать.
Заметьте, что здесь мы не употребляем такие слова, как «схемы» или «классы».
Мы также не говорим о базах данных, таблицах, ключах и тому подобных вещах.
Речь идет только о данных. На этой стадии разработки мы еще не знаем, будут ли
использоваться базы данных.
Похоже, что на основании примеров использования и последовательности
страниц мы будем иметь дело с теми данными, которые изображены на рис. 5.3.
На мой взгляд, и здесь намного проще будет использовать карандаш и бумагу, а не
какой-нибудь мудреный инструмент, но вы можете воспользоваться тем, что вам
больше по душе.
Подробности заказа
Д е т а л и платеж а
С о сто я н и е доставки
ных действий по возвращ ению на правильный курс. А общ ие советы на этот счет даются
прямо сейчас.
Перезапускать сервер нужно будет только в нескольких случаях, когда это действие кон
кретно оговорено в данной книге. Но, когда вы попадете в полный тупик, можно будет
попробовать перезапустить сервер.
«Волшебная» команда rake d b :m igrate:redo, о сущ ествовании которой вам нужно знать,
подробно рассмотрена в части III. Она выполняет отмену и повторное использование по
следней миграции.
Если ваш сервер не примет какие-нибудь введенные в ф орму данные, обновите форму на
своем браузере и повторите отправку данных.
Задача А:
создание приложения
Основные темы:
> создани е нового прилож ения;
> конф игурирование базы данны х;
> создани е м оделей и контроллеров;
> добавлен ие табли ц стилей;
> обновление разметки и представления.
Создание Rails-приложения
В главе 2 мы уже видели, как создается новое Rails-приложение. Здесь мы сделаем
то же самое. Перейдите в окно командной строки и наберите команду r a i l s new,
указав после нее имя нашего проекта. В данном случае наш проект называется
d e p o t, поэтому убедитесь в том, что вы не находитесь в каталоге уже существую
щего приложения, и наберите следующую команду:
work> r a i l s new depot
Мы увидим, как по экрану побегут строки вывода. Когда этот процесс завер
шится, мы обнаружим новый каталог по имени depot. Именно с ним мы и будем
работать.
work> cd depot
depot> d ir /w
[■] [•■] .g itig n o r e [app] [config]
config.ru [db] [doc] Gemfile Gemfile. lo ck
[lib ] [lo g ] [ p u b lic ] Rakefile README
[s c r ip t ] [te s t] [tmp] [ven d o r]
1 http://guides.rubyonrails.Org/getting_started.htm l#configuring-a-database
Глава б • Задача А: создание приложения 83
Генератор создает целый пакет файлов. Нас в первую очередь будет интересо
вать файл миграции, а именно 20110711000001_create_products.rb.
Миграция представляет изменение, которое нужно внести в данные, выражен
ное в исходном файле в терминах, независимых от применяемой базы данных. Та
кие изменения могут обновить как схему базы данных, так и данные в ее таблицах.
Эти миграции применяются для обновления нашей базы данных, и их применение
можно отменить, чтобы база данных вернулась к прежнему состоянию. Миграциям
будет посвящена целая глава 23, а сейчас мы будем просто пользоваться ими без
излишних комментариев.
У миграций имеется префикс с отметкой времени в формате UTC (20110711000001),
имя (create_products) и расширение имени файла ( . rb, поскольку это R u b y -
программа).
Тот префикс с отметкой времени, который вы увидите, будет отличаться от это
го. В действительности отметки времени, используемые в данной книге, не имеют
ничего общего с настоящими. Обычно ваши отметки времени не будут выстроены
в четкой последовательности, и они будут отражать время создания миграции.
Применение миграций
Хотя мы уже сообщили Rails об основных типах данных каждого свойства, давайте
пойдем дальше и уточним определение цены ( p r i c e ) , чтобы у нее было восемь
цифр в значимой части и две цифры после десятичного знака.
rails31/depot_a/db/migrate/201107110e0001_create_products. rb
c la s s C re ate Pro d ucts < A c tiv e R e c o rd ::M ig ra tio n
def change
c r e a t e _ ta b le :p rod u cts do |t|
t.s t r in g : t i t l e
t . t e x t d e s c rip tio n
t . s t r i n g :im a g e_u rl
► t.d e c im a l : p r ic e , p r e c is io n : 8, s c a le : 2
t.tim estam p s
end
end
end
Вот и все. R ake находит миграции, которые еще не применялись к базе дан
ных, и применяет их. В нашем случае к базе данных, определенной в разделе
d e v e lo p m e n t файла database.yml, добавляется таблица p r o d u c t s .
Основа заложена. Мы установили наше приложение Depot в виде проекта Rails.
Мы создали разработочную базу данных и сконфигурировали наше приложение
на подключение к этой базе данных. Мы создали контроллер p r o d u c t s и модель
P ro d u c t и воспользовались миграцией для создания соответствующей таблицы
p ro d u c ts . И для нас были созданы несколько представлений. Настала пора по
смотреть, как все это работает.
, (t) Depot
4- С й lo ca l host A" ☆ A
Listing products
T itle D e s c r ip t io n I m a g e uri P ric e
(3
f С A lo c a lh o s t - :Я * Л,
>
(-;.i
New product
T itle
j D e s c r ip tio n
<ul>
Глава б • Задача А: создание приложения 87
4- С Я O tocalhw t p ro d u c ts /n e w a, w* A.
New product
I T itle
CoffeeScript
1 D e s c r ip tio n
< p > C o f f e e S c r i p t i s J a v a S c r i p t done *
r i g h t . I t p rovides a l l of J a v a S c r i p t 's
f u n c t i o n a l i t y w ra ppe d i n a c l e a n e r , more “ i,
s u c c i n c t s y n t a x . I n t h e f i r s t bo ok on
t h i s e x c i t i n g new l a n g u a g e , C o f f e e S c r i p t »
g u r u T r e v o r Burnham shows you how t o
:
j Im a g e url
1 cs.jpg
I P ric e
36.00
Create Product
* • -> С Л lo c o lh o s f у p io d u iB * is \ j
Listing products
Im age „ .
T itle D e s c r ip tio n , P ric e
url
< p > C o ffe e S c rip t is J a v a S c r ip t d o n e rig h t. It
p ro v id e s all o f J a v a S c r ip t 's f u n c t io n a lit y w ra p p e d
in a c le a n e r, m o re s u c c in c t s y n t a x . In th e firs t
C o f f e e S c r ip t b o o k o n t h is e x c itin g n e w la n g u a g e , C o f f e e S c r ip t c s .jp g 3 6 ,0 S h o w E d it С
g u ru T r e v o r B u rn h a m s h o w s y o u h o w to h o ld o n t о
ail t h e p o w e r a n d fle x ib ility o f J a v a S c r ip t w h ile
w ritin g c le a re r , c le a n e r, a n d s a f e r c o d e ,< /p >
New P ro d u c t
подробно этот вопрос будет рассмотрен в разделе 7.2 «Шаг Б2: Блочное тестиро
вание моделей».
Следует учесть, что при использовании базы данных, отличной от SQLite3,
тестирование может не пройти. Проверьте содержимое своего файла database.yml
и изучите материал главы 24.
1 http://media.pragprog.com/titles/rails4/code/depot_b/db/seeds.rb
2 http://media.pragprog.com/titles/rails4/code/rails31/depot_b/app/assets/images/
90 Часть II • Создание приложения
rails31/depot_b/db/seeds. rb
P ro d u c t. d e l e t e _ a l l
# . . .
P ro d u c t. c r e a t e ( t i t l e : 'Programming Ruby 1 . 9 ' ,
d e s c r ip t io n :
%{<p>
Ruby is th e f a s t e s t growing and most e x c it in g dynamic language
out th e re . I f you need to get working programs d e liv e re d f a s t ,
you should add Ruby to your to o lb o x .
</p>b
im ag e _u rl: 'r u b y . jp g ',
p r ic e : 49.95)
# . . .
rails31/depot_b/app/assets/stylesheets/products. c s s . scss
// Сюда помещаются все определения стилей для контроллера P ro d u cts.
// Они будут автоматически включены в файл a p p lic a t io n . c s s .
// Что такое Sass (S C S S ), можно узн ать здесь: h ttp :// sa ss- lan g .co m /
.p ro d u cts {
t a b le {
b o rd e r- c o lla p s e : c o lla p s e ;
}
ta b le t r td {
padding: 5px;
v e r t i c a l - a l i g n : to p ;
}
.lis t_ im a g e {
w id th : 60px;
h e ig h t: 70px;
}
Глава 6 • Задача А: создание приложения 91
. li s t _ d e s c r i p t i o n {
w id th : 60%;
dl {
m argin: 0;
}
dt {
c o lo r : #244;
fo n t- w e ig h t: bold;
fo n t - s iz e : la r g e r ;
}
dd {
m argin: 0;
}
}
. lis t _ a c t io n s {
fo n t - s iz e : x -sm all;
t e x t - a lig n : r ig h t ;
p a d d in g - le ft: lem;
}
. lis t _ li n e _ e v e n {
background: # e0f8f8;
}
. lis t _ lin e _ o d d {
background: #f8b0f8;
}
Если внимательно изучить эту таблицу стилей, можно заметить, что CSS-
правила вложены друг в друга и правило для d l определено внутри правила для
. l i s t _ d e s c n i p t i o n , которое, в свою очередь, определено внутри правила для
products. Тем самым правила избавляются от лишних повторений, их становится
легче читать, понимать и обслуживать.
Пока вы были знакомы только с тем фактом, что в файлах, заканчивающихся на
erb. происходит предварительная обработка встроенных выражений и инструкций
Ruby. А эти файлы, если вы заметили, заканчиваются на scss, и вы, наверное, уже
догадались, что данные файлы, перед тем как обслуживаться в качестве файлов
css, проходят предварительную обработку в качестве продукта Sassy CSS1. И вы
абсолютно правы!
Как и в случае с ERb, SCSS не конфликтует с написанным по всем правилам
кодом CSS. Роль SCSS заключается в предоставлении дополнительного синтаксиса,
позволяющего упростить разработку и обслуживание таблиц стилей. В ваших же
интересах SCSS конвертирует все это в стандартный CSS, который понимает ваш
браузер.
http://sass-lang.com /
92 Часть II • Создание приложения
- </body>
- </html>
Поскольку мы собрались загрузить все таблицы стилей сразу, нам нужно со
глашение для ограничения распространения правил, указанных для контроллера,
только на те страницы, которые связаны с этим контроллером. Выполнить эту зада
чу можно простым указанием в качестве имени класса значения controller_nam e,
что мы здесь и сделали.
Теперь, когда все таблицы стилей находятся на своем месте, мы воспользуемся
простым табличным шаблоном, отредактировав файл index.htmi.erb в каталоге арр/
views/products и заменив тем самым представление, сгенерированное при создании
временной платформы:
<table>
<% @ products.each do |product| %>
<tr class="<%= c y c l e ( ' li s t _ l i n e _ o d d ' , ' l i s t _ l i n e _ e v e n ' ) %>">
<td>
<%= im a g e _ta g (p ro d u ct. im ag e_u rl, c la s s : ' lis t _ im a g e ' ) %>
</td>
<br />
L is tin g p ro d u c t s
C c if ie a S c iip t
CofteeScnpt is JavaScript 1ooe rgh? !t provides Ш
all of JavaScript. ШПШ
rails31/depot_b/. g itig n o re
. bundle
d b /*. s q lit e 3
lo g / * .lo g
tmp/
. sass-cache/
Из-за того, что имя этого файла начинается с точки, операционные системы
на основе Unix не показывают его в листингах каталогов. Чтобы увидеть
этот файл, нужно воспользоваться командой I s -а. Теперь все настройки
завершены, и осталось только инициализировать репозиторий, добавив все
файлы и передав их с сообщением о передаче:
depot> g it i n i t
depot> g it add .
depot> g it commit -m "Depot S c a ffo ld "
Основные темы:
> выполнение проверки приемлемости данны х и вывод сообщ ений об ошибках;
> блочное тестирование.
На данный момент у нас уже есть исходная модель для товара, а также завершенное
приложение для ведения перечня товара, предоставленное нам временной платфор
мой Rails. В этой главе мы собираемся сконцентрировать внимание на повышении
надежности модели, то есть, перед тем как в следующих главах перейти к другим
аспектам приложения Depot, преградить ошибкам, допущенным при вводе данных,
путь в базу данных.
New product
3 errors prohibited this product from being saved:
Depot
New product
■ P rice is n o t a n u m b e r
T itle
Program ming Ruby 1.9
D e s c r ip tio n
<p> Ruby i s t h e f a s t e s t g r o w in g and most
e x c i t i n g dyna mic la n g u a g e o u t t h e r e . I f
you n e e d t o g e t w o rk in g pro g r am s d e l i v e r e d
f a s t , you s h o u l d a dd Ruby t o y o u r t o o l b o x .
</p>
Im a g e url
ruby jpg
]
Create Product
B ack
Может быть, позже нам захочется изменить эту форму, дав пользователю воз
можность выбора из перечня доступных изображений, но проверка приемлемости
все равно будет сохранена, чтобы злоумышленники не смогли отправить неверные
данные непосредственно из этого поля.
Итак, всего за пару минут мы добавили следующие виды проверок:
О поля названия, описания и URL-адреса изображения не оставлены пустыми;
О цена является допустимым числом, значение которого не меньше 0.01;
О название имеет уникальное значение среди всех товаров;
О URL-адрес изображения выглядит вполне приемлемо.
Обновленная модель P r o d u c t должна приобрести следующий вид:
rails31/depot_b/app/models/product. rb
c la s s Product < A c tiv e R e c o rd : : Base
v a lid a t e s : t i t l e , d e s c r i p t i o n , :im a g e _u rl, presen ce: tr u e
v a lid a t e s :p r ic e , n u m e r ic a lit y : {g r e a t e r _ th a n _ o r _ e q u a l_ to : 0 .0 1 }
v a lid a t e s : t i t l e , uniqueness: tr u e
Глава 7 • Задача Б: проверка приемлемости данных и блочное тестирование 101
end
a s s e r t _ r e d ir e c te d _ to p ro d u c t_ p a th (a s s ig n s (: p ro d u c t))
end
# ...
t e s t "should update p rod uct" do
► put :update, id : ^ product,to _param , p ro d uct: @update
a s s e r t _ r e d ir e c te d _ to p ro d u c t_ p a th (a s s ig n s (: p ro d u c t))
end
# ...
end
product_ test.rb является файлом, который Rails создает, чтобы хранить блочные
тесты для модели, ранее созданной с помощью сценария generate. Неплохо для на
чала, но это все, чем может помочь Rails.
Посмотрим, что полезного для тестирования Rails сгенерировала внутри файла
test/unit/product_test.rb при создании данной модели:
P ro d u c tT e s t:
PASS product a t t r ib u t e s must not be empty (0 .2 3 s )
1 te s ts , 5 a s s e r t io n s , 0 f a i l u r e s , 0 e r r o r s , 0 sk ip s
В этом коде мы создаем новый товар, а затем пытаемся установить для него
цену -1 ,0 и +1, каждый раз проверяя его при этом. Если наша модель работает, то
первые две проверки должны провалиться и мы проверяем появление ожидаемого
нами сообщения об ошибке, связанного с ценой. Поскольку список сообщений об
ошибках является массивом, для объединения каждого сообщения мы воспользу
емся подходящим методом j o i n 1 и выразим утверждение таким образом, чтобы
проверить, что есть только одно такое сообщение.
Последняя цена является приемлемой, поэтому мы утверждаем, что теперь
модель допустима. (Возможно, кто-то поместил бы эти три теста в три отдельных
тестовых метода, и это было бы вполне разумно.)
Затем протестируем проверку окончания URL-адресов изображений одним из
допустимых расширений: .gif, .jpg или .png:
t e s t "image u r l " do
# u r l изображения
ok = %w{ f r e d . g i f f r e d .jp g fre d .p n g FRED.1PG FRED.Dpg
h tt p :/ / a .b .c / x / y / z / f r e d .g if }
bad = %w{ fre d .d o c fr e d .g if/ m o re fr e d .g if.m o r e }
ok.each do |name|
a s s e rt n e w _p ro d u c t(n a m e ).v a lid ?, "#{nam e} s h o u ld n 't be in v a l id "
# не должно быть неприемлемым
end
bad.each do |name|
a s s e r t new _product(nam e). in v a l i d ? , "#{nam e} s h o u ld n 't be v a li d "
# не должно быть приемлемым
end
Испытательные стенды
В мире испытаний стенды являются той средой, в которой можно запустить те
стирование. Если, к примеру, тестируется монтажная плата, можно установить ее
на испытательном стенде, который подает на нее питание и входные сигналы, не
обходимые для управления тестируемой функции.
В мире Rails испытательный стенд — это просто спецификация исходного со
держимого тестируемой модели (или моделей). Если, к примеру, мы хотим обе
спечить, чтобы перед каждым блочным тестированием в начале таблицы товаров
products хранились известные нам данные, мы можем определить это содержимое
в испытательном стенде, a Rails позаботится обо всем остальном.
Данные стенда определяются в файлах каталога test/fixtures. В этих файлах дан
ные содержатся либо в формате CSV' (Comma-Separated Value, то есть значения,
разделенные запятыми), либо в формате YAML. Для наших тестов предпочтитель
нее формат YAML. Каждый стендовый файл формата YAML содержит данные для
одной модели. Имя стендового файла должно совпадать с именем таблицы базы
данных. Поскольку нам нужны некоторые данные для модели Product, размещен
ные в таблице товаров, мы впишем их в файл, названный products.yml.
Rails уже создала этот стендовый файл одновременно с созданием модели:
rails31/depot_b/test/fixtures/products. yml
# Read about fixtures a t h t t p : / / a p i. r u b y o n r a ils . o r g / c la s s e s / F ix t u r e s . html
t i t l e : M yStrin g
d e s c r ip tio n : MyText
im ag e _u rl: M yStrin g
p r ic e : 9.99
two:
t i t l e : M yStrin g
d e s c r ip tio n : MyText
im ag e _u rl: M yStrin g
p r ic e : 9.99
Стендовый файл содержит запись для каждой строки, которую нужно вста
вить в базу данных. Каждой строке дается имя. В стенде, сгенерированном Rails,
строки названы one и two. Для базы данных эти имена ничего не значат — они
не вставляются в строки данных. Но вскоре будет показано, что имена дают нам
удобный способ ссылки на тестовые данные внутри кода нашего теста. Эти имена
Глава 7 • Задача Б: проверка приемлемости данных и блочное тестирование 107
rails31/depot_c/test/fixtures/products.ym l
ruby:
t i t l e : Programming Ruby 1.9
d e s c r ip t io n :
Ruby i s th e f a s t e s t growing and most e x c itin g dynamic
language out th e r e . I f you need to get working programs
d e liv e re d f a s t , you should add Ruby to your to o lb o x ,
p r ic e : 49.50
im ag e _u rl: ruby.png
При наличии стендового файла нужно, чтобы при запуске блочного теста среда
Rails загружала тестовые данные в таблицу p ro d u c ts . И Rails уже так и делает
(тут опять для успеха всей нашей работы соглашение превалирует над настройкой
108 Часть И • Создание приложения
В тесте предполагается, что база данных уже включает строку для книги по
Ruby. Он берет название ( t i t l e ) из этой уже существующей строки, используя
следующий код:
p ro d u c ts (: r u b y ) . t i t l e
Теперь мы можем быть уверены, что наш код проверки приемлемости не только
работает, но и будет работать в дальнейшем. У нашего товара теперь есть модель,
набор представлений, контроллер и набор блочных тестов. Это послужит хорошей
основой, на которой можно будет построить все остальное приложение.
Как только это будет сделано, мы можем делать что угодно, зная, что в случае
отказа все в любой момент можно будет вернуть к сохраненному состоянию,
используя всего лишь одну команду g i t checkout ..
О Ключ проверки приемлемости : le n g th проверяет длину свойства модели.
Добавьте эту проверку приемлемости к модели Product, чтобы проверить,
что в названии товара присутствует не менее 10 символов.
О Измените сообщение об ошибке, связанное с одной из ваших проверок при
емлемости данных.
(П одсказки можно найти по адресу http://www.pragprog.com/wikis/wiki/
RailsPlayTime.)
Задача В: отображение
каталога товаров
Основные темы:
> создание своих собственных представлений;
> использование элементов разметки для улучшения внешнего вида страниц;
> включение CSS;
> использование помощников;
> создание функциональных тестов.
Пока в основном все у нас шло успешно. Были собраны начальные требования
заказчика, отображены на бумаге основные потоки данных, выработан первый
подход к необходимым данным и собрана страница для ведения перечня товаров
интернет-магазина. У нас даже есть небольшой, но наращиваемый набор тестов.
И мы с воодушевлением переходим к решению следующей задачи. При об
суждении приоритетов с заказчиком он высказал пожелание увидеть приложение
глазами покупателя. Поэтому следующей задачей будет создание простого ото
бражения каталога товаров.
Мы также считаем это желание вполне обоснованным. Раз у нас уже есть това
ры, помещенные в базу данных после соответствующей проверки, вывести их на
экран особого труда не составит. Тем самым будут заложены основы для дальней
шей разработки кода корзины покупателя.
Нужно будет также воспользоваться уже проделанной работой по ведению
перечня товаров, ведь, по сути дела, отображение каталога товаров — не что иное,
как вывод их привлекательно оформленного перечня.
И наконец, нам также нужно будет дополнить наши блочные тесты для модели
несколькими функциональными тестами для контроллера.
Глава 8 • Задача В: отображение каталога товаров 113
re so u rce s :products
# ...
# You can have th e root o f your s i t e routed w ith "r o o t"
# (Корневой маршрут к вашему сайту можно получить с помощью " r o o t " )
# j u s t remember to d e le te p u b lic / in d e x . h tm l.
# (не забудьте удалить файл p u b lic / in d e x .h tm l.)
► ro o t to : ' s to re # in d e x ', as: 's t o r e '
# . . .
end
Вряд ли от этого мы станем богаче, но, по крайней мере, мы узнали, что все
связано друг с другом правильно. Страница просто сообщает о том, где найти файл
шаблона, который ее рисует.
Начнем с отображения простого перечня всех товаров, имеющихся в базе дан
ных. Мы знаем, что в конечном счете понадобится более сложный подход с раз
биением товаров на категории, к чему мы и будем стремиться.
Нам нужно извлечь перечень товаров из базы данных и сделать его доступным
для кода представления, который и отобразит таблиц)'. Это означает, что мы долж
ны внести изменения в метод in d ex () в файле store_controller.rb. Мы хотим програм
мировать на соответствующем уровне абстракции, поэтому просто предположим,
что мы можем запросить у модели перечень продаваемых товаров:
end
► .s to re {
► hi {
m argin: 0;
padding-bottom: 0.5em;
fo n t : 150% s a n s - s e r if ;
c o lo r : #226;
border-bottom : 3px dotted #77d;
}
/* Запись в каталоге s to re */
.e n t r y {
overflow: auto;
m argin-top: lem;
border-bottom : lp x dotted #77d;
m in-height: 100px;
img {
w id th : 80px;
m a rg in - rig h t: 5px;
margin-bottom: 5px;
p o s itio n : a b s o lu te ;
}
h3 {
fo n t - s iz e : 120%;
fo n t- fa m ily : s a n s - s e r if ;
m a rg in - le ft: 100px;
m argin-top: 0;
margin-bottom: 2px;
c o lo r : #227;
}
p, d i v . p r i c e _ l i n e {
m a rg in - le ft: 100px;
m argin-top: 0.5em;
margin-bottom: 0.8em;
}
.p r ic e {
c o lo r : #44a;
fo n t- w e ig h t: bold;
m a rg in - rig h t: 3em;
}
}
locathost ☆i \
Your Pragmatic Catalog
C offe e S crip t
Co ffe e Scrip t is Ja va S crip t done right. 51 provides all of
J a va S c rip t's functionality wrapped in a cleaner more
su c c in c t syntax. In the first hook on this exciting new
language. CoffeeScrip t guru Trevor Burnham show s you
how to hold onto ail the power and flexibility of Ja va S crip t
w hile writing clearer cleaner, and safer code.
36.0
<ul>
< lix a h re f= "http://www .. .. ">Home</ax/li>
< lix a h re f= "h t t p : //www . . . ./ fa q "> Q u e stio n s< / a x / li>
< lix a h ref= ” h t t p : //www . . . ./news">New s</ax/li>
20 < lix a hnef= "h t t p : //www . . . ./ c o n ta c t"> C o n ta c t< / a x / li>
</ul>
</div>
<div id="main">
<%= y i e ld %>
25 </div>
</div>
- </body>
- </html>
Кроме обычного HTML-кода этот макет содержит 3 характерных для Rails эле
мента. Как уже ранее упоминалось, в строке 5 используется вспомогательный метод
Rails, предназначенный для генерирования тега <link>, ссылающегося на таблицу
стилей нашего приложения. Точно так же в строке 6 генерируется тег <link>, ссы
лающийся на сценарии нашего приложения. И наконец, в строке 7 устанавливаются
все закулисные данные, необходимые для предотвращения атак путем подделки
кросс-сайтовых запросов, которые выйдут на первый план, как только мы добавим
формы в главе 12 «Задача Ж: оформление покупки».
В строке 12 для заголовка страницы установлено значение переменной экзем
пляра @ p a g e _ title . Но настоящие чудеса начинаются в строке 24. При вызове
действия y ie ld Rails автоматически подставляет содержимое, специфическое для
текущей страницы, то есть все, что сгенерировано представлением, вызванным
данным запросом. В данном случае это будет страница каталога, сгенерированная
с использованием файла index.htmi.erb.
Чтобы все это заработало, сначала переименуем файл application.css в application,
css.scss, а затем добавим к нему следующее содержимое:
r a i ls 3 1 / d e p o t _ e / a p p / a s s e t s / s t y l e s h e e t s / a p p l i c a t i o n . c s s .s c s s
/*
* T h is i s a m an ifest file t h a t ' l l a u to m a tic a lly in c lu d e a l l the
* s t y le s h e e ts a v a ila b le in t h i s d ir e c t o r y and any s u b - d ire c to r ie s .
* You’ re f r e e to add a p p lic a tio n - w id e s t y le s to t h i s file and t h e y ' l l
* appear a t th e top o f th e compiled file , but i t ' s g e n e r a lly b e tte r
* to c re a te a new file per s t y le scope.
*= r e q u ir e _ s e lf
*= r e q u ir e jt r e e .
*/
#banner {
background: #9c9;
padding: 10px;
border-bottom : 2px s o lid ;
fo n t : sm all-caps 40px/40px ’’Times Mew Roman” , s e r i f ;
c o lo r : #282;
t e x t - a lig n : c e n te r;
img {
Глава 8 • Задача В: отображение каталога товаров 119
flo at: l e f t ;
}
}
# n o tic e {
c o lo r : #000 lim p o rta n t;
b ord er: 2px s o lid red;
padding: lem;
m argin-bottom: 2em;
b ackground-color: # f0 f0 f0 ;
fo n t : bold s m a lle r s a n s - s e r if ;
#columns {
background: #141;
#main {
m a rg in - le ft: 17em;
padding: lem;
background: w h ite ;
}
# sid e {
flo at: l e f t ;
padding: lem 2em;
w id th : 13em;
background: #141;
ul {
padding: 0;
H {
lis t - s ty le : none;
a {
c o lo r : #bfb;
fo n t - s iz e : sm a ll;
}
}
}
}
}
io c a ih o s t ☆ Л
P r a g m a t ic B o o k s h e l f
Your Pragmatic Catalog
CoffeeScript
C offeeScrip t is Ja va S crip t done right, ft provides all of
J a va S c rip t's functionality wrapped in a cleaner, more
su c c in c t syntax. In the first book on this exciting пел?
language. C offeeScript guru Trevor Burnham show s you
how to hold onto ail the power and flexibility of
J a va S c rip t w hile writing clearer, cleaner, and safer code.
36.0
на следующий:
Я уверен, что после щелчка на кнопке Обновить (Refresh) появится красиво от
форматированная цена, показанная на рис, 8.3.
у- \ lo c a ih o s t 3000
P r a g m a t ic B o o k s h e l f
Your Pragmatic Catalog
Q uestions
News
ШШ C o ffe e S c rip t
Contact CoffeeScrip t is Ja v a S c rip t done right, it provides all of
J a va S c rip t's functionality wrapped in a cleaner more
su c c in c t syntax. In the first hook on this exciting new
language. C o ffeeScrip t guru Trevor Burnham show s you
how to hold onto all the power and flexibility of
Ja v a S c rip t while writing clearer, cleaner and safer code.
$ 36.00
о нашем опыте работы после добавления к нашей модели логики проверки при
емлемости данных, мы с чувством тревоги опять запустили наши тесты:
depot> rake test
На сей раз все прошло успешно. Несмотря на довольно многочисленные до
бавления, мы ничего не испортили. Можно облегченно вздохнуть, но наша работа
на этом не закончена, нам все равно нужны тесты для всего, что только что было
добавлено.
Ранее проведенное блочное тестирование моделей представлялось вполне до
статочным. Мы вызывали метод и сравнивали возвращенные им данные с тем, что
ожидали от него получить. Но теперь мы имеем дело с сервером, обрабатывающим
запросы, и с пользователем, который просматривает ответы в браузере. Нам нужны
функциональные тесты, проверяющие совместную работу модели, представления
и контроллера. Но нам не о чем особо беспокоиться —среда Rails и здесь облегчила
нашу задачу.
Сначала посмотрим на тот код, который Rails для нас сгенерировала:
Тест «should get index» получает индекс и утверждает, что ожидается успешный
ответ. Все это выглядит предельно просто. Для начала неплохо, но нам еще нужно
проверить, содержит ли ответ наш макет, нашу информацию о товаре и наше чис
ловое форматирование. Посмотрим, как это выглядит в виде программного кода:
rails31/depot_e/test/fixtures/products. yml
# Read a b o ut f i x t u r e s a t h t t p : / / a p i . r u b y o n r a i l s . o r g / c l a s s e s / F i x t u r e s . h tm l
t i t l e : M yS trin g
d e s c r i p t i o n : MyText
i m a g e _ u r l: M y S t r i n g
p r i c e : 9 .9 9
tw o:
t i t l e : M y S trin g
d e s c r i p t i o n : MyText
i m a g e _ u r l: M y S t r i n g
p r ic e : 9.99
ru by:
t i t l e : Programming Ruby 1 . 9
d e s c rip tio n :
Ruby i s t h e f a s t e s t g r o w in g and most e x c i t i n g dynamic
la n g u a g e o u t t h e r e . I f you need t o g e t w o r k in g pr ogr am s
d e l i v e r e d f a s t , you s h o u ld add Ruby t o y o u r t o o l b o x ,
p r i c e : 4 9 .5 0
im ag e _ u rl: ru by.png
Такие данные не вызовут проблем, и не будут обнаружены до тех пор, пока такие
записи не будут изменены или сохранены.
Мы коснулись лишь нескольких возможностей метода a s s e r t _ s e l e c t ( ).
Более подробную информацию можно найти в онлайн-документации1. Итак, мы
получили массу проверок, добавив всего лишь несколько строк кода. Убедиться
в работоспособности этого кода можно, перезапустив одни лишь функциональные
тесты (ведь, по сути, только их мы и изменяли):
depot> r a k e t e s t :f u n c t i o n a l s
1 http://api.rubyonrails.org/classes/ActionDispatch/Assertions/SelectorAssertions.html
Глава 8 • Задача В: отображение каталога товаров 125
О сн о в н ы е тем ы :
Для контроллера Rails делает текущую сессию похожей на хэш, поэтому мы со
храним идентификатор корзины в сессии путем индексирования его обозначением
:c a rt_ id .
p r iv a te
d e f c u r r e n t_ c a r t
C a rt .fin d (s e s s io n [ : c a r t _ i d ] )
rescu e A c t iv e R e c o r d :: RecordNotFound
c a r t = C a r t .c r e a t e
s e s s i o n f : c a r t _ id ] = c a r t . i d
c a rt
end
end
Теперь у базы данных есть место для хранения взаимоотношений между то
варными позициями ( l i n e item ), корзинами ( c a r t ) и товарами (p ro d u ct). А вот
в приложении Rails эти взаимоотношения не фигурируют. Нам нужно добавить
к нашей модели ряд инструкций, указывающих на их взаимосвязь.
Откройте только что созданный файл cart.rb в каталоге app/models и добавьте
вызов метода has_many():
rails31/depot_f/app/m odels/cart. rb
c la s s C art < A c t iv e R e c o r d :: Base
► has_many :lin e _ it e m s , dependent: :d e s tro y
end
Часть инструкции has_many : lin e _ ite m s говорит сама за себя: в корзине по
тенциально имеется множество (has many) связанных с ней товарных позиций.
Они привязаны к корзине, потому что каждая товарная позиция содержит ссыл
ку на идентификатор этой корзины. Часть инструкции d e p e n d e n t: : d e s tr o y
показывает, что существование товарной позиции зависит от существования кор
зины. Если мы уничтожим корзину, удалив ее из базы данных, нам нужно, чтобы
Rails также уничтожила все товарные позиции, связанные с этой корзиной.
Затем мы определим связи в обратном направлении, от товарной позиции
к таблицам корзин ( c a r t s ) и товаров (p ro d u c ts). Для этого в файле linejtem.rb
мы дважды воспользуемся инструкцией b e lo n g s _ to ( ):
rails31/depot_f/app/m odels/line_item . rb
c la s s Lin e lte m < A c tiv e R e c o rd : : Base
► b elo ng s_to : product
► belo ng s_to :c a r t
end
rails31/depot_f/app/m odels/product. rb
c la s s Produ ct < A c tiv e R e c o rd : : Base
► has_many :lin e _ ite m s
# ...
► p r iv a te
<h3><%= p r o d u c t . t i t le %></h3>
<%= s a n it iz e ( p r o d u c t .d e s c r ip t io n ) %>
Глава 9 • Задача Г: создание корзины покупателя 131
form, d iv {
d is p la y : in lin e ;
}
) (ocalhost 3000 ☆ л
P r a g m a t ic B o o k s h e l f
Your Pragmatic Catalog
S36.00 A dd to C o t
Рис. 9.1. Теперь на странице есть кнопка Добавить в корзину (Add to Cart)
132 Часть II • Создание приложения
rails31/depot_f/app/controllers/line_item s_controller.rb
d ef c re a te
► @ cart = c u r r e n t_ c a r t
► product = Product.find(param s[ :p r o d u c t _ id ])
► @ lin e _ite m = @ c a rt. lin e _ it e m s . b u ild (p r o d u c t: prod uct)
i f @ lin e _ ite m .s a v e
► fo rm a t.h tm l { r e d ir e c t _ t o @ lin e _ it e m .c a r t,
n o tic e : 'L in e item was s u c c e s s f u lly c r e a t e d .' }
ъЗ й й [ О locathost : 'Ч
P r a g m a t ic B o o k s h e l f
Итак, когда все это скомпоновано, щелкнем на кнопке Обновить (Refresh) своего
браузера и увидим отображение нашего простого представления:
P r a g m a t ic B o o k s h e l f
Q uestions
fvews
Cornet
О сн о в н ы е тем ы :
Исходя из имени миграции, Rails может сообщить, что вы добавляете один или
несколько столбцов к таблице l i n e _ i t e m s , и может взять имена и типы данных для
каждого столбца из последнего аргумента. Rails ищет соответствие двум шаблонам:
add_ X X X _ to_ T A B L E и re m o v e _ X X X _ f rom_TABLE, где значение ХХХ игнорируется.
Значение имеет список имен столбцов и типов данных, появляющийся после имени
миграции.
Rails не может только сообщить, какое приемлемое значение по умолчанию
может быть выбрано для столбца. Во многих случаях подойдет нулевое значение,
но давайте перед применением миграции внесем в нее изменение и сделаем это
значение для существующих корзин равным 1:
rails31/depot_g/db/migrate/20110711000004_add_quantity_to_line_items.rb
c la s s A ddQ uantityToLineltem s < A c tiv e R e c o rd : m ig r a tio n
d ef change
► add_column :lin e _ it e m s , :q u a n t it y , :in t e g e r , d e f a u lt : 1
end
end
В этом коде используется небольшой хитрый трюк Active Record. Как види
те, в первой строке метода вызывается метод f i n d _ b y _ p r o d u c t _ i d (). Но метод
с таким именем мы не определяли. Тем не менее Active Record замечает вызов
неопределенного метода и опознает, что его имя начинается со строкового зна
чения fin d_ by (искать по) и заканчивается именем столбца. Затем он в динами
ческом режиме создает для нас поисковый метод, добавляя его к нашему классу.
Более подробно динамически создаваемые поисковые методы будут рассмотрены
в разделе 19.3.
Чтобы воспользоваться этим методом, нам понадобится также внести изменения
в контроллер товарных позиций:
138 Часть И • Создание приложения
rails31/depot_g/app/controllers/line_item s_controller. rb
d ef c re a te
(Scart = c u r r e n t_ c a r t
product = P ro d u c t.fin d (p a ra m s [:p ro d u c t_ id ])
► @ lin e _ ite m = @ c a rt.a d d _ p ro d u c t(p ro d u c t.id )
respond_to do |form at|
i f @ lin e _ ite m .s a v e
fo rm a t.h tm l { r e d ir e c t _ t o @ lin e _ it e m .c a r t,
n o tic e : 'L in e item was s u c c e s s f u lly c r e a t e d .' }
fo rm a t.js o n { render js o n : @ lin e _ite m ,
s t a tu s : : c re a te d , lo c a t io n : @ lin e _ite m }
e ls e
fo rm a t.h tm l { render a c tio n : "new" }
fo rm a t.js o n { render js o n : @ lin e _ it e m .e r r o r s ,
s t a tu s : : u n p ro c e s s a b le _ e n tity }
end
end
end
rails31/depot_g/db/migrate/20110711000005_coinbine_items_in_cart.rb
d ef up
# замена нескольких записей для одного и того же товара в корзине одной записью
C a r t . a l l. e a c h do |c a r t|
# подсчет количества каждого товара в корзине
sums = c a r t . lin e _ it e m s . g ro u p (: p r o d u c t_ id ). sum (: q u a n t ity )
rails3X/depot_g/db/migrate/20110711000005_combine_items_in_cart.rb
d ef down
# разбиение записей с q u a n tity > l на несколько записей
L in e lte m .w h e re ("q u a n tity > l” ) .each do |lin e _ ite m |
# add in d iv id u a l item s
lin e _ it e m .q u a n t it y .t im e s do
L in e lte m .c re a te c a r t _ id : lin e _ it e m . c a r t _ id ,
p ro d u c t_id : lin e _ it e m .p r o d u c t_ id , q u a n t ity : 1
end
PRAliMATH. -B o o k s h e l f
l'
показанную на рис. 10.3. На ней показано слишком много информации о нашем при
ложении, а это считается признаком непрофессионализма разработчиков. Поэтому
наш следующий шаг мы затратим на то, чтобы сделать приложение более стойким.
'i# Й [ Q lo c athost
P r a g m a t ic B o o ksh elf
Home
Y o u r P ra g m a tic C art
Q uestions
News • 1 x CoffeeScript
Contact • 1 * Program m ing Ruby 1.9
• 1 * Program m ing Ruby 1.9
• 1 * Program m ing Ruby 1.9
Рис. 10.2. Вид корзины после того, как миграция была отменена
jV ...
Action Controller: Excep
w' ьм ! o tocalhost
И
R a i l s . r o o t : /home/rubys/ 5v n / r a i l s 4 / B o o k / u til/ w o rk - 192 /depot
1 У вас может быть другой номер строки. В наших исходных файлах форматирование
иногда обусловлено размерами книжной страницы.
142 Часть И • Создание приложения
1 http://guides.ruby0nrails.0rg/debugging_rails_applicati0ns.htm l#the-l0gger
Глава 10 • Задача Д: усовершенствованная корзина 143
, ;■ lo c a lh o s t
P r a g m a t ic B o o ksh elf
Coffe e Script
CoffeeScrip t is Ja va S crip t clone right. It provides
all of Ja v a S c rip t's functionality wrapped in a
cleaner, more su c c in c t syntax. In the first book
on this exciting new language. C o ffeeScrip t gum
Trevor Burnham show s yo u how to hold onto all
the power and flexibility of Ja va S crip t while
writing clearer, cleaner and safer code
rails31/depot_h/app/views/carts/show.htm l.erb
<% i f n o tic e %>
<p id="notice"><%= n o tic e %></p>
<% end %>
r a i l s 3 1 / d e p o t _ i / a p p / c o n t r o l l e r s / l i n e _ i t e m s _ c o n t r o l l e r . rb
d ef c re a te
@ cart = c u rre n t_ c a r t
product = Produ ct .fin d (p aram s[: p r o d u c t_ id ])
@ lin e _ ite m = (fflcart. a d d _p ro d u ct(p ro d u ct. id )
respond_to do |form at|
i f @ lin e _ ite m .s a v e
► fo rm a t.h tm l { r e d ir e c t _ t o @ lin e _ it e m .c a r t }
fo rm a t.js o n { render js o n : (S lin e it e m ,
s t a tu s : : c re a te d , lo c a t io n : @ lin e _ite m }
e ls e
fo rm a t.h tm l { render a c tio n : "new" }
fo rm a t.js o n { render js o n : @ lin e _ it e m .e r r o r s ,
s t a tu s : : u n p ro c e s s a b le _ e n tity }
end
end
end
Чтобы все это заработало, нам нужно к обеим моделям, L in e lte m и C a rt,
добавить метод, возвращающий общую стоимость, соответственно, для каждой
товарной позиции и для всей корзины. Сначала займемся товарной позицией, где
понадобится только простое умножение:
rails31/depot_i/app/m odels/cart. rb
def t o t a l _ p r ic e
lin e _ it e m s . t o _ a . sum { |item | it e m .t o t a l_ p r ic e }
Затем нам нужно будет добавить небольшой фрагмент к нашей таблице стилей
carts.css.scss:
ra ils3 1 / d e p o t_ i/ a p p / a sse ts/ s ty le sh e e ts/ ca rts.cs s.sc ss
// P la c e a l l th e s t y le s r e la te d to th e C a rts c o n t r o lle r h ere.
// They w i l l a u to m a tic a lly be in clu d ed in a p p lic a t io n . c s s .
// You can use Sass (SC SS) h ere: h ttp :// sa ss- lan g .co m /
.c a r t s {
.c a r t_ title {
fo n t : 120% bold;
}
148 Часть II • Создание приложения
. ite m _ p ric e , . t o t a l _ l i n e {
t e x t - a lig n : r ig h t ;
}
.t o ta l_ lin e .t o t a l_ c e ll {
fo n t- w e ig h t: bold;
b ord er-top: lp x s o lid #595;
}
P r a g m a t ic B o o ksh elf
Your Can
Home
2* CoffeeScrip t $72.00
Q uestions
Nev/s 1* Program m ing Ruby 1 9 $49.95
Em pty cart
Основные темы:
> и спользование п арциальны х ш аблонов;
> п ерем ещ ение корзины в макет страницы;
> д и нам и ческое обновлен ие стран иц с п ом ощ ью АЗАХ и JavaScript;
> вы деление изм енений с п ом ощ ью jQ u ery UI;
> сокры тие и отображ ен и е D O M -элементов;
> тестирован и е AJAX-обновлений.
Парциальные шаблоны
Языки программирования предоставляют возможность определения методов. Ме
тод представляет собой поименованный фрагмент кода: стоит только вызвать метод
по имени, как тут же будет запущен соответствующий фрагмент кода. И, конечно
же, методам можно передавать аргументы, позволяющие создавать один фрагмент
кода, который может быть использован во многих различных обстоятельствах.
Парциальные шаблоны (или, для краткости, парциалы) Rails можно считать не
кой разновидностью методов для представлений. Парциал —это просто фрагмент
представления, находящийся в своем собственном отдельном файле. Парциал
можно вызвать (отобразить) из другого шаблона или из контроллера, и он ото
бразит самого себя и вернет результаты этого отображения. И, как и при работе
с методами, парциалу можно передать аргументы, поэтому один и тот же парциал
может отображать разные результаты.
В этом шаге парциалы будут применены дважды. Для начала взглянем на ото
бражение самой корзины:
rails31/depot_i/app/view s/carts/show .htm l. erb
<% i f n o tic e %>
<p id="notice"><%= n o tic e %></p>
<% end %>
Здесь создается список строк таблицы, по одной для каждой записи в корзи
не. Когда сталкиваешься с подобного рода повторениями, возникает вопрос, а не
многовато ли логики в шаблоне? Оказывается, абстрагироваться от цикла можно
с помощью парпи&тов (вскоре станет ясно, что тем самым мы подготовимся и к бу
дущему применению магии AJAX).
Для этого воспользуемся возможностью передачи коллекции методу, отобра
жающему парциальные шаблоны, и этот метод автоматически вызовет парциал по
одному разу для каждого элемента коллекции. Давайте перепишем отображение
корзины, чтобы воспользоваться этой возможностью:
</table>
</table>
<ul>
< l i x a h r e f= "h tt p : / /www.. . . " >Home</ax/li>
< l i x a h re f= "h t t p : //www. . . ,/ fa q "> Q u e s tio n s < / a x / li>
< l i x a h re f= "http://w w w . .. ,/news"> N ew s< /ax/li>
< l i x a h re f= "h t t p : //www. .. ./ c o n ta c t"> C o n ta c t< / a x / li>
</ul>
</div>
<div id="main">
<%= y i e ld %>
</div>
</div>
</body>
</html>
► form, d iv {
► d is p la y : in lin e ;
► }
►
► in p u t {
► fo n t - s iz e : sm a ll;
► }
156 Часть II • Создание приложения
# c a rt {
fo n t - s iz e : s m a lle r;
c o lo r : w h ite ;
t a b le {
b ord er-top: lp x dotted #595;
border-bottom : lp x dotted #595;
margin-bottom: 10px;
}
}
ul {
padding: 0;
И {
lis t - s t y le : none;
a {
c o lo r : #bfb;
fo n t - s iz e : s m a ll;
}
}
}
ЙййГ (_ lo c a lh o s t
P ra g m л т к : B o o k sh elf
Your Cart Your Pragmatic Catalog
2* CoffeeScript $72.00
^ Programming
$99.90 CoffeeScript
Ruby 1.9
CoffeeScript is Ja va S crip t done right. It provides all of Ja va S crip t’s
Total 5 1 7 1 .9 0
functionality wrapped in a cleaner, more succinct syntax. In the first
book an this exciting new language CoffeeScript guru Trevor
Burnham show s you how to hold onto all the power and flexibility of
JavaScrip t while writing clearer, cleaner, and safer code.
Home
Questions S36.00 Add to Cart
News
Смена направления
Теперь, когда корзина отображается на боковой панели, мы можем изменить
характер работы кнопки Добавить в корзину (Add to Cart). Вместо отображения
Глава 11 • Задача Е: добавление AJAX 157
отдельной страницы корзины ей нужно будет всего лишь обновить главную страни
цу каталога. Изменить ее функции несложно: в конце действия c r e a te мы просто
перенаправим браузер обратно на каталог:
rails31/depot_k/app/controllers/line_item s_controller. rb
d ef c re a te
g c a r t = c u r r e n t_ c a r t
product = P ro d u c t.fin d (p a ra m s [:p ro d u c t_ id ])
@ lin e _ite m = @ c a rt.a d d _ p ro d u c t(p ro d u c t.id )
respond_to do |form at|
i f @ lin e _ ite m .s a v e
► fo rm a t.h tm l { r e d ir e c t _ t o s t o r e _ u r l }
fo rm a t.js o n { render jso n : @ lin e _ite m ,
s t a tu s : : c re a te d , lo c a t io n : @ lin e _ite m }
e ls e
fo rm a t.h tm l { render a c tio n : "new" }
fo rm a t.js o n { render jso n : @ lin e _ it e m .e r r o r s ,
s t a tu s : : u n p ro c e s s a b le _ e n tity }
end
end
end
Итак, теперь у нас есть магазин с корзиной на боковой панели. Когда мы щел
кнем на кнопке добавления товара к содержимому корзины, страница перезагру-
зится с отображением обновленной корзины. Тем не менее, если каталог слишком
объемный, эта перезагрузка займет довольно много времени. При этом будут за
действованы ресурсы интернет-канала и сервера. Улучшить создавшуюся ситуацию
позволит технология AJAX.
1 http://api.jquery.com/html/
160 Часть II • Создание приложения
Возможные проблемы
Удивительная простота использования AJAX в Rails не защищает от неверных ша
гов. Поскольку мы имеем дело со свободной интеграцией ряда технологий, понять
причины неработоспособности применяемых элементов AJAX-технологии будет
непросто. Поэтому на каждом этапе мы добавляем к приложению не более одной
функции AJAX.
Если приложение Depot отказывается от демонстрации магии AJAX, можно
посоветовать выяснить следующее:
О Не нужны ли вашему браузеру какие-нибудь специальные настройки, что
бы заставить его перезагрузить все, что есть на странице? Иногда браузеры
сохраняют в локальной кэш-памяти версию содержимого страницы, мешая
тем самым нашему тестированию. Может быть, стоит сделать полную пере
загрузку страницы.
О Не было ли каких-нибудь сообщений об ошибках? Загляните в файл
development.log в каталоге logs. Загляните также в окно сервера Rails, по
скольку некоторые сообщения об ошибках выводятся именно туда.
О Не видно ли при изучении регистрационного журнала входящих запросов
к действию c r e a t e ? Если их нет, значит, ваш браузер не выдает AJAX-
запросов. Если были загружены библиотеки JavaScript (при использовании
в браузере функции просмотра кода страницы будет показан код HTML), то,
может быть, в браузере отключено выполнение JavaScript?
О Некоторые читатели сообщили, что для работы корзины на основе AJAX им
пришлось остановить и снова запустить их приложения.
О Если вы пользуетесь браузером Internet Explorer, он может быть запущен
в режиме quirks mode, который обеспечивает обратную совместимость со
старыми версиями IE, но является таким же урезанным, как и они. Для
переключения Internet Explorer в стандартный режим, который работает
с компонентами AJAX гораздо лучше, в первой строке загружаемой страни
цы должен быть соответствующий заголовок DOCTYPE. В нашем макете
используется следующий заголовок:
<!DOCTYPE html>
В результате этих двух незначительных изменений элемент < tr > той товарной
позиции, которая подверглась в корзине самым последним изменениям, будет по
мечен атрибутом id = "c u rre n t_ ite m ". Теперь осталось только заставить JavaScript
изменить фоновый цвет на тот, который сразу бросался бы в глаза, а затем посте
пенно вернуть его к прежнему состоянию.
Это будет сделано в уже существующем шаблоне create.js.erb:
<table>
<%= r e n d e r (c a r t .lin e _ it e m s ) %>
rails31/depot_n/app/views/line_items/create. j s . erb
► if ( $ ( ’# c a rt t r ') . l e n g t h == 1) { $ ( ' # c a r t ' ) . show (' b l i n d ' , 1000); }
Нам также нужно устроить все так, чтобы скрыть корзину, когда она опустеет.
Это можно сделать двумя основными способами. Один из них проиллюстрирован
кодом в начале этого раздела и заключается в том, чтобы вообще не генерировать
никакого кода HTML. К сожалению, если мы так и сделаем, то при добавлении
в корзину какого-нибудь товара и внезапного создания HTML-кода корзины мы
будем наблюдать, как корзина на миг появится в браузере при первоначальном
отображении, а затем исчезнет, чтобы медленно появиться снова под воздействием
эффекта b lin d .
Лучше решить эту проблему, создав HTML-код корзины и установив при этом
CSS-стиль d is p la y : none, если корзина еще пуста. Для этого нам нужно внести
Глава 11 • Задача Е: добавление AJAX 165
Вспомогательные методы
Когда мы хотим удалить какой-то процесс обработки из представления (из любого
вида представления), нам нужно написать вспомогательный метод.
Если вы взглянете на каталог арр, вы увидите в нем шесть подкаталогов.
depot> d i r app /w
[.] [..] [a s s e t s ] [ c o n t r o ll e r s ] [h e lp e r s ]
[m a ile r s ] [m odels] [v ie w s ]
F in is h e d in 1.048274 seconds.
23 t e s t s , 26 a s s e r t io n s , 1 f a i l u r e s , 9 e r r o r s , 0 sk ip s
Надо же, отказы и ошибки. Нехорошо. Видимо, нужно снова обратиться к те
стированию. Этим мы сейчас и займемся.
НВВВВННВНН^^^Н
¥ Action Controller: Excep 1
^ 'jfg [ © localhost:3000/products
После внесения этого изменения наши тесты опять стали успешно проходить.
Только представьте себе, что могло бы произойти. Изменение в одной части прило
жения в целях поддержки новых требований нарушает функцию, ранее реализован
ную в другой части приложения. При невнимательной работе это может произойти
в таком небольшом приложении, как Depot. Но даже при весьма аккуратной работе
такое может случиться и в большом приложении.
Но на этом наша работа еще не завершена. Мы не протестировали ни одно
из наших AJAX-добавлений, к примеру, не проверили, что произойдет, когда мы
щелкнем на кнопке Добавить в корзину (Add to Cart). Rails и здесь облегчает нам
задачу
Глава 11 • Задача Е: добавление АЗАХ 171
У нас уже есть тест для создания товарной позиции should c r e a te l i n e item ,
поэтому давайте добавим еще один тест для создания товарной позиции с помощью
AJAX: should c r e a te l i n e item v ia ajax:
a s se rt_ re sp o n s e : success
a s s e r t _ s e le c t _ jq u e r y :h tm l, '# c a r t ' do
a s s e r t _ s e le c t ' tr # c u rre n t_ ite m t d ' , /Programming Ruby 1.9/
end
end
своей разработки. Некоторые из них заходят в этом деле настолько далеко, что
сначала пишут тесты, а потом уже пишут первую строку своего кода.
Основные темы:
> связы вание табли ц внеш ним и клю чами;
> и спользование объявлений b e lo n g s _ t o , has_m any и :th ro u g h ;
> со здание ф орм на основе моделей ( fo rm _ fo r) ;
> связы ван и е ф орм, моделей и представлений;
> генерация RSS-канала с пом ощ ью метода a to m _ h e lp e r, прим ененного
в отнош ении объектов моделей.
Поскольку в базе данных нет записей для этих двух новых миграций в таблице
s c h e m a _ m ig ra tio n s , задача d b : m ig r a t e применяет к базе данных обе миграции.
Разумеется, мы можем применить их по отдельности, запустив задачу миграции
после создания отдельных миграций.
</table>
►<%= b u tto n _to "C h eck o ut", new _order_path, method: :g e t %>
<%= b u tto n _to 'Empty c a r t ' , c a r t , method: : d e le te ,
confirm: 'A re you s u r e ? ' %>
176 Часть И • Создание приложения
Сначала нужно проверить, есть ли что-то в корзине. Если в корзине ничего нет,
мы отправим пользователя назад в каталог, оповестим о своих действиях и немед
ленно вернемся на исходную позицию. Тем самым пользователи не смогут непо
средственно перейти к оформлению покупки и к созданию пустых заказов. Здесь
важную роль играет инструкция re tu r n , без нее будет получена ошибка двойного
вывода double render error, поскольку ваш контроллер попытается осуществить
и перенаправление и вывод.
@order = Order.new
respond_to do |form at|
fo rm a t.h tm l # n ew .h tm l.erb
fo rm a t.js o n { render jso n : @order }
end
get :new
as se rt_re sp o n s e : success
end
В данном коде есть две интересные особенности. Начнем с того, что помощник
f o r m _ f o r () в первой строке формирует стандартную HTML-форму. Но он делает
не только это. Первый аргумент, (S order, сообщает методу о переменной экзем
пляра, которую нужно использовать при создании имен нолей и при организации
передачи значений этих полей обратно контроллеру.
Как видите, fo r m _ fo r открывает Ruby-блок (который заканчивается на шестой
строке). В этот блок можно поместить обычное содержимое шаблона (к примеру,
178 Часть И • Создание приложения
тег <р>). Но для ссылки на содержимое формы можно также использовать параметр
блока (в данном случае f ). Мы воспользовались этим в четвертой строке, чтобы
добавить в форму текстовое поле. Поскольку текстовое поле создано в контексте
fo r m _ f on, оно автоматически связывается с данными объекта (fflonden.
В этих взаимосвязях нетрудно запутаться. Важно запомнить, что для свя
зи с моделью Rails нужно знать и имена полей, и их значения. Эту информацию
ей предоставляет комбинация fo n m _ fo n и различных помощников, предназна
ченных для работы с нолями (таких, как t e x t _ f ie ld ) . Этот процесс показан на
рис. 12.1.
<ul>
<% (S o rd e r .e rro r s .fu ll_ m e s s a g e s . each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div c la s s = "fie ld ">
<%= f . l a b e l :name %><br />
► <%= f. t e x t _ f ie ld :name, s iz e : 40 %>
</div>
<div c la s s = "fie ld ” >
<%= f . l a b e l :address %><br />
► <%= f . t e x t _ a r e a : address, rows: 3, c o ls : 40 %>
</div>
<div class= "field ">
<%= f . l a b e l :e m a il %><br />
► <%= f.e m a il_ fie ld :e m a il, s iz e : 40 %>
</div>
<div class= "field ">
<%= f . l a b e l :p ay_typ e %><br />
► <%= f . s e l e c t : p ay_typ e, Order::PAYMENT_TYPES,
► prompt: 'S e l e c t a payment method' %>
</div>
<div c la s s = "a c tio n s ">
► <%= f.s u b m it 'P la c e O rd er' %>
</div>
<% end %>
rails31/depot_o/app/m odels/order. rb
c la s s Order < A c tiv e R e c o rd : : Base
► PAYMENTTYPES = [ "C h eck ", " C r e d it c a rd ", "P u rch ase o rd e r" ]
end
legend {
c o lo r : #dfd;
background: #141;
f o n t - fa m ily : s a n s - s e r if ;
padding: 0.2em lem;
}
>
form {
la b e l {
w id th : 5em;
flo at: l e f t ;
t e x t - a lig n : r ig h t ;
padding-top: 0.2em;
m a rg in - rig h t: 0.1em;
d is p la y : b lo ck ;
}
s e le c t , te x t a r e a , in p u t {
m a rg in - le ft: 0.5em;
}
.subm it {
m a rg in - le ft: 4em;
}
br {
d is p la y : none
}
}
Теперь все готово для испытания нашей формы в деле. Добавьте что-нибудь
в свою корзину и щелкните на кнопке Оформить заказ (Checkout). Вы должны
увидеть что-нибудь похожее на окно, показанное на рис. 12.2.
ЗШШЙШШШШВШЕННВ
8оЫ«Onfcno'i
•vis;. P r a g m a t ic B o o k sh e l f
2' S?,1 00 I
кошиякзя
Ыяпи* Г
. Hrvyrartnn;r»g
Rtiti* v 9
■ CIwnkjUI 1 Dr®!
T<.feri *171.90 1
L
pa.' r,:- defect тЛ 1nettod*
' РМиэд ОпШ !
rails31/depot_o/app/m odels/order.rb
c la s s Order < A c tiv e R e c o rd : : Base
# . ..
► v a lid a t e s :name, :a d d re ss , :e m a il, presence: tru e
► v a lid a t e s :p a y_typ e , in c lu s io n : PAYMENT_TYPES
end
one:
► name: Dave Thomas
ad d ress: MyText
► e m a il: dave@example.org
► p ay_typ e : Check
two:
name: M yStrin g
ad d ress: MyText
e m a il: M yStrin g
p ay_typ e: M yStrin g
Кроме того, для создаваемого заказа нужно, чтобы в корзине был товар, поэтому
нам требуется изменить также и стенд для тестирования товарных позиций:
one:
p ro d uct: ruby
► o rd e r: one
tw o :
p ro d uct: ruby
c a r t : one
182 Часть II • Создание приложения
Затем определим связь заказа с товарной позицией, еще раз показав, что все
товарные позиции, принадлежащие заказу, должны быть уничтожены при уни-
чтожени и заказа:
rails31/depot_o/app/m odels/order. rb
c la s s Order < A c tiv e R e c o rd : : Base
► has_many :lin e _ it e m s j dependent: : d estro y
# ...
end
rails31/depot._o/app/cont r o ll e r s/orders_cont r o l l e r , rb
d ef c re a te
@order = O rd e r.n e w (p a ra m s [:o rd e r])
► (Border. a d d _ lin e _ ite m s _ fro m _ c a rt( c u r r e n t _ c a r t )
i f (Border, save
► C a r t . d e s t r o y ( s e s s io n [ : c a r t _ id ] )
► s e s s io n [ :c a r t _ id ] = n i l
► fo rm a t.h tm l { r e d ir e c t _ t o s t o r e _ u r l, n o tic e :
► 'Thank you f o r your o r d e r .' }
fo rm a t.js o n { render js o n : (Border, s ta tu s : :c re a te d ,
lo c a t io n : (Border }
e ls e
► (Bcart = c u r r e n t_ c a r t
fo rm a t.h tm l { render a c tio n : "new" }
fo rm a t.js o n { render js o n : (B o rd e r.e rro rs ,
s t a tu s : : u n p ro c e s s a b le _ e n tity }
end
end
end
товарных позиций для ссылки на только что созданную строку заказа. Rails создает
эту связь для нас, используя объявления has_many () и b e lo n g s _ to ( ), добавлен
ные нами к моделям O rder и L ineltem . Добавление каждой новой товарной по
зиции к коллекции lin e _ ite m s возлагает ответственность за управление ключами
на Rails.
£r A
PRAf j Ma t ic B o o k s h e l f
Yonri art P iea sc £m ef Your Delate
2*CoffeeSorpi $7233 4 errors prohibited this order from being saved:
2<Prnowmwiu ш к
Rubyt 9 NamecsmoecHarrt*:
To:a;$17190 Aodress can’t be blank
Email cant Be Man*
[ cnoc<om Д fcmpty Pay typ« is not inciudeti n me tisc
ШШ
-1 а
P r a g m a i к, В(ЮЩ1И.<!
Y.. UI : pwasei ь1««и и
■Г.ПрГ Ш по’ ЧЭПе TJftvPTtWnp-Г
v Pruyidr unnng
sv:- ос
•<1Г / 9 АМЯЯ5Ц23 Mai-ГsT
~ 'A a 1171 90
Etna. x!iStorrer§p’agprog.c
P - - Cft«*
Piat**Order
Y o u r P ra g m a tic C a t a lo g
■“ CoffeeScript
cnfTeescfspt is JavaScript oonp ngr* *? р т м в е з mi or
Jav a S crip t* functionality m a p p e d in a d e a n e i. m ots
soccmc? syntax i '1tne nrat ooo* on ?n« cxoirmg riov
Idfjguaoe. CoffeeScript q u u Trevt* B untfw ii чЬ^гл you
txw, to rvoet onto a s ролег ana p?xtt>ttty or
JavaS cript аПйе writing clearer. c l e a r » . <*ld safe; code
534.00 Ac-:- to C an
rails31/depot_p/app/views/products/who_bought.atom.builder
atom_feed do |feed|
f e e d . t i t l e "Who bought # {| 8 p r o d u c t .t it le }"
x h tm l.ta b le do
x h tm l.tr do
x h tm l.th ’ P r o d u c t1
x h tm l.th ’Q u a n tity '
x h tm l.th 'T o t a l P r ic e '
end
x h tm l.tr do
x h tm l.th ' t o t a l ' , co lsp an : 2
x h tm l.th num ber_to_currency \
o r d e r . lin e _ it e m s .m a p ( & : t o t a l_ p r ic e ) . sum
end
end
На общем уровне канала нам нужно предоставить лишь два блока информа
ции: заголовок и самую последнюю дату обновления. Если заказов нет, значение
u p d a te d _ a t будет нулевым и Rails предоставит вместо этого значения текущее
время.
Затем осуществляется последовательный перебор каждого заказа, связанного
с данным товаром. Следует заметить, что прямой связи между этими двумя моде
лями не существует. Фактически эта связь является опосредованной. У товаров
имеется много товарных позиций, а товарные позиции принадлежат заказу. Мы
можем осуществить итерацию и проследить связи, но простым объявлением о су
ществовании связи между товарами (p ro d u c ts) и заказами (o rd e rs ) через связь
с товарными позициями ( lin e _ ite m s ) мы можем упростить наш код:
rails31/depot_p/app/m odels/product. rb
c la s s Produ ct < A c tiv e R e c o rd : : Base
has_many :lin e _ ite m s
► has_many : o rd e rs , through: :lin e _ ite m s
# .. .
end
rails31/depot_p/config/routes. rb
Depot: A p p l i c a t i o n . r o u t e s . draw do
re so u rce s :o rd ers
re so u rce s :c a r t s
► re so u rce s : products do
► get :who_bought, on: :member
► end
# ...
# You can have th e ro o t o f your s i t e routed w ith "r o o t"
# j u s t remember to d e le te p u b lic / in d e x . h tm l.
# ro o t :t o => ’welcome#index'
ro o t to : ’ s to re # in d e x ' , as: 's t o r e '
# . ..
end
190 Часть II • Создание приложения
</table>
<p>Paid by check</p>
</div>
</summary>
<author>
<name>Dave Thomas</name>
<email>customer(S)pragprog. com</email >
</author>
</entry>
</feed>
rails31/depot_q/Gemfile
source ' h ttp :// ru b yg e m s.o rg '
# To use debugger
# gem ' ruby-debugl9' , :r e q u ir e => 'ruby-debug'
group : t e s t do
# P r e t t y p rin te d t e s t output
gem 't u r n ' , : re q u ire => f a ls e
end
►gem 'w i l l _ p a g i n a t e ', '~> 3 .0 '
Этот код создаст 100 заказов без товарных позиций. Если нужно, вы можете
изменить сценарий, добавив товарные позиции. Учтите, что этот код проделывает
всю эту работу в одной транзакции. Это не является каким-то строгим требованием
для данной деятельности, но зато существенно ускоряет обработку.
Обратите внимание на отсутствие каких-либо инструкций r e q u ir e или ини
циализаций для открытия или закрытия базы данных. Мы позволим Rails поза
ботиться об этом за нас:
r a ils runner sc rip t/ lo a d _ _ o rd e rs . rb
<table>
<tr>
<th>Name</th>
<th>Address</th>
Глава 12 • Задача Ж: оформление покупки 193
<th>Email</th>
<th>Pay type</th>
< th x/th>
< th x/th >
< th x/th>
</tr>
<br />
P r a g m a t ic B o ok shelf
Qrestioas
Listing orders
Чжпе Afldre*» Tmftil Prty type
Cus'.omor iuo loe Main street xstomsr iOQCpexarpfo con c^*ock §ngw bdx occupy
Cosier*»* <>? 99 Main Street custonm*99$4mample cam Check Slum Fdl >-s!•w
Products Cusiomor Vo Jt маг stroot customer 38(0«xampte com Chock it»»» fcai L&Eiroy
C u s lotiw- 97 97 Млл Street cu*tam«r-97@wi*mp(e cam Check Show Ftil Pest toy
. ; • V.." Customer 'Jfe Man Street customer SfcSpexampie com Check Snow Ldi Aairoy
Cu»to»n#( 9 : 9*>Man Street cum CJteck S'kw £iLi
Customer 9* iM Main street ctstomcr -J4iecxamptc.com Check iinow Ldl L&siroy
Cusiomw 93 93 MJ'. Street сикЮтм-ЭЗ&ехапдо com Check S 'w Edit 0>r>;4jv
О is'rane' 9? 92 M«n Street customer-92 mple com Check Show fd l Ou
1
Custuinwf 01 91 Mji<> Stiucl ОЮСШЮГ-Э Д UHUiIIpiw.com Oieck SMu-yvgait PubHtfy
NewOf0Qf
- p i l o u s • 2 3 * 5 £ Z f l 3 Iflllfalcat..-.
Основные темы:
> электронная почта;
> ком плексное тестирование.
Как и при всех других изменениях конфигурации, если были внесены изме
нения в любой из файлов среды окружения, вам потребуется перезапустить ваше
приложение.
rails31/depot_q/app/m ailers/order_notifier. rb
c la s s O rd erN o tifier < A c t io n M a ile r :: Base
► d e fa u lt from: 'Sam Ruby <depot@example.com>'
Если вам показалось, что это похоже на контроллер, то в принципе так оно
и есть. Каждому действию соответствует один метод. Вместо вызова метода ото
бражения вызывается метод отправки электронной почты. Этот метод принимает
ряд аргументов, включая :to (как показано в листинге), :сс, :from и : s u b je c t,
каждый из которых вызывает выполнение намного большего количества полез
ных действий, чем вы могли бы ожидать. Значения, общие для всех вызовов m ail
в почтовом классе, могут быть установлены как значения по умолчанию, как это
сделано для : from в самом начале определения этого класса. Вы можете перекроить
этот класс под свои собственные нужды.
Комментарии, имеющиеся в классе также свидетельствуют о том, что стро
ки темы (subject) уже готовы к переводу, который будет рассмотрен в главе 15
«Задача К: локализация». А теперь мы просто будем использовать параметр
: s u b je c t.
Как и при работе с контроллерами, шаблоны содержат отправляемый текст, а ко-
троллеры и почтовые классы могут предоставлять значения, вс тавляемые в такие
шаблоны посредством переменных экземпляра.
Глава 13 • Задача 3: отправка электронной почты 199
Когда Ваш заказ будет отправлен, мы пошлем Вам по электронной почте отдельное
сообщение.
Нам также нужно обновить метод shipped () точно так же, как это было сделано
с методом re c e iv e d ():
rails31/depot_r/app/m ailers/order_notifier. rb
d ef s h ip p e d (o rd e r)
(Border = ord er
На данный момент у нас есть достаточная база для размещения заказа и от
правки самому себе обычного сообщения электронной почты — если предполо
жить, что вы не отключили отправку сообщений в режиме разработки. А теперь
давайте приправим сообщение электронной почты небольшой порцией форма
тирования.
<table>
< t r x t h colspan="2"> K-BO< /thxth> 0nHcaHne</thx/tr>
<%= render (Slo rd er.lin e_item s %>
</table>
t e s t "sh ip p e d " do
► m ail = O rd e rN o tifie r.s h ip p e d (o rd e rs (:o n e ))
► a s s e rt_ e q u a l "З ака з из Pragm atic S to re отправлен", m a il.s u b je c t
► a s s e rt_ e q u a l [ " dave@exam ple.org" ] , m a il.t o
► a s s e rt_ e q u a l [ " depot@example.com" ] , m ail.fro m
► a s s e r t jn a t c h /<td>l×<\/td>\s*<td>Programming Ruby 1.9<\/td>/,
► m ail.body.encoded
end
end
Все обычные технологии перехвата входящих сообщ ений сводятся к запуску команды
с передачей ей содержимого сообщения в качестве стандартного ввода. Если мы делаем
Rails-сценарий runner командой, вызываемой при каждом поступлении электронной почты,
мы можем настроить ее на передачу этой электронной почты коду нашего приложения, за
нимающ емуся обработкой электронной почты. Например, используя перехват, основанный
на использовании утилиты procm ail, мы можем написать правило, похожее на приведен
ный далее пример. Используя загадочный синтаксис procmail, это правило копирует любое
входящее почтовое сообщение, чье поле темы (subject) содерж ит «Bug Report», через наш
сценарий runner:
R U BY= /opt/local/bin/ruby
TICKET_APP_DIR=/Users/dave/Work/depot
HANDLER=' in co m in g T ic k e tH a n d le r. r e c e iv e (S T D IN .r e a d )'
:0 с
* AS u b je c t : . *Bug R e p o rt.*
| cd $TICKET_APP_DIR && $RUBY runner $HANDLER
Метод класса receive() доступен всем классам Action Mailer. Он берет текст сообщения элек
тронной почты, переводит его в объект TMail, создает новый экземпляр класса получателя
и передает объект TM ail методу экземпляра receiveQ, имею щемуся в этом классе.
Теперь создадим тест по имени «покупка товара». Мы знаем, что к концу теста
нам нужно, чтобы заказ был добавлен к таблице заказов o rd e rs , а товарная пози
ция была добавлена к таблице lin e _ ite m s , поэтому сначала давайте очистим эти
таблицы от данных. И, поскольку мы часто использовали стендовые данные о книге
по Ruby, давайте загрузим эти данные в локальную переменную:
c a r t = C a rt.fin d (s e s s io n [ : c a r t _ i d ] )
a s s e rt_ e q u a l 1, c a r t . lin e _ it e m s . s iz e
a s s e rt_ e q u a l ruby_book, c a r t . li n e _ it e m s [ 0 ] . product
a s s e rt_ e q u a l 1, o r d e r . li n e _ it e m s . s iz e
lin e _ it e m = o r d e r . lin e _ it e m s [0 ]
a s s e rt_ e q u a l ruby_book, lin e _ ite m .p ro d u c t
Ну вот и все.
А вот полный исходный код комплексного теста:
c a r t = C a r t .f in d (s e s s io n [: c a r t _ i d ] )
a s s e rt_ e q u a l 1, c a r t . li n e _ it e m s . s iz e
a s s e rt_ e q u a l ruby_book, c a r t . lin e _ it e m s [ 0 ] .product
p o s t _ v ia _ r e d ir e c t "/ o r d e r s ",
o rd e r: { name: "Dave Thomas",
a d d re ss: "123 The S t r e e t " ,
e m a il: " dave@example.com"
p a y _ ty p e : "Check" }
a s se rt_ re sp o n s e : success
a s s e rt_ te m p la te "in d e x "
c a r t = C a rt.fin d (s e s s io n [ : c a r t _ i d ] )
a s s e rt_ e q u a l 0, c a r t . lin e _ it e m s . s iz e
o rd ers = O r d e r . a ll
a s s e rt_ e q u a l 1, o r d e r s .s iz e
ord er = o rd e rs [0 ]
a s s e rt_ e q u a l 1, o r d e r .lin e _ it e m s . s iz e
lin e _ it e m = o r d e r . Iin e _ it e m s [0 ]
a s s e rt_ e q u a l ruby_book, lin e _ ite m .p ro d u c t
m ail = A c t io n M a ile r : : B a s e . d e li v e r i e s . la s t
a s s e rt_ e q u a l ["daveiaexam ple.com "], m a il.t o
a s s e rt_ e q u a l 'Sam Ruby <depot@example.com> ', m a il[:f r o m ] .v a lu e
a s s e rt_ e q u a l "Prag m atic S to re Order Confirm ation", m a il, su b je c t
end
О сн о в н ы е тем ы :
rails31/depot_r/app/m odels/user. rb
c la s s User < A c tiv e R e c o rd : : Base
v a lid a t e s :name, presence: t r u e , uniqueness: tru e
has_secure_password
end
Администрирование пользователей
В дополнение к настроенной модели и таблице у нас уже есть временная платфор
ма, сгенерированная для администрирования модели. Но эта временная платформа
нуждается в некоторых настройках, позволяющих использовать только что опреде
ленные поля пароля.
<table>
<tr>
<th>Name</th>
< th x/th>
< th x/th>
< th x/th>
</tr>
<br />
И наконец, нам нужно обновить форму, используемую как для создания нового
пользователя, так и для обновления сведений об уже имеющемся пользователе. Сна
чала заменим оцифрованный пароль полями пароля и подтверждения пароля. Затем
добавим теги legend и fieldset. И, в завершение, поместим вывод в тег <div> с атри
бутом c la s s , значение которого мы ранее определили в нашей таблице стилей.
</ul>
</div>
<% end %>
<fieldset>
<legend>Bвeдитe сведения о пoльзoвaтeлe</legend>
<div>
<%= f . l a b e l :name %>:
<%= f . t e x t _ f ie ld :name, s iz e : 40 %>
</div>
<div>
<%= f . l a b e l
:password, 'Пароль' %>:
<%= f . password_field :passw ord, s iz e : 40 %>
</div>
<div>
<%= f . l a b e l : password_confirm ation, 'Подтверждение' %>:
<%= f . password_field : passw ord con firm atio n , s iz e : 40 %>
</div>
<div>
<%= f.s u b m it %>
</div>
</fieldset>
<% end %>
</div>
jjjjj | Q lot»lhost:3000/users/new ф! ^
яПЕЯйш P ra g m a tic B o o k s h e lf
Hom e
Q uestions
New user
News Enter U se r Details
Contact
Nam e; j
| Password; f
Ij _____
Confirm ; f~
_I
Create U se r
Back
Так же как и раньше, нам необходимо обновить наши тесты, чтобы они отвечали
внесенным изменениям в проверке и перенаправлении:
► a s s e r t _ r e d ir e c t e a _ t o u sers_path
end
# .. .
t e s t "should update u s e r" do
► put : update, id : (8user.to_param , u ser: @ ir,p u t_ a ttrib u te s
► a s s e r t _ r e d ir e c te d _ to u sers_path
end
end
r9 ^ 3 1 / ( | t e p o t _ r / a p p / c o n t r o lle r s / s e s s io n s _ c o iT t ^ H M H H H H H H H H H I
def c re a te
► u ser = User.find_by_nam e(param s[: name])
► i f u ser and u s e r . a u th e n tic a te (p a ra m s [: passw ord])
► s e s s io n [ : u s e r _ id ] = u s e r .id
► r e d ir e c t _ t o adm in_url
► e ls e
► r e d ir e c t _ t o lo g in _ u r l, a l e r t : "Неверная комбинация имени и пароля"
► end
end
<div>
<%= la b e l_ t a g :name, ’ И м я:' %>
<%= te x t_ fie ld _ ta g :name, params[ : name] %>
</div>
<div>
<%= la b e l_ t a g :password, 'П ар о л ь:' %>
216 Часть II • Создание приложения
<div>
<%= subm it_tag "L o g in " %>
</div>
</fieldset>
<% end %>
</div>
Ш Ш Ш Ш Ж
def login _________ .. -'
name =ф a ra ms[:name f)
end
ra ils3 1 /d ep ot_ r/a p p /co n tro llers/ ad m in _ con tro lle r.rb
c la s s A d m in C o n tro ller < A p p lic a tio n C o n t r o lle r
d e f index
► @ to ta l_ o rd e rs = O rd er.co u n t
end
end
Перед тем как этим воспользоваться, нам нужно выполнить еще одну задачу.
Если прежде мы зависели от генератора временной платформы, который создавал
нашу модель и маршруты для нас, то на этот раз мы просто сгенерируем контрол
лер, поскольку для этого контроллера нет модели, основанной на базе данных. К со
жалению, если не следовать соглашению, применяемому при генерации временных
218 Часть И • Создание приложения
платформ, Rails не может узнать, какие действия для этого контроллера отвечают
за GET-запросы, а какие отвечают за POST-запросы и т. д.
Нам нужно предоставить эту информацию путем редактирования нашего файла
config/routes, rb:
rails31/depot_r/config/routes. rb
D epot: A p p l i c a t i o n . ro u tes .draw do
► get 'adm in' => ' adm in#index'
► c o n t r o lle r :s e s s io n s do
► get 'l o g i n ' => :new
► post 'l o g i n ' => : c re a te
► d e le te 'lo g o u t' => rd estro y
► end
re so u rce s : users
reso u rces :o rd e rs
re so u rce s :lin e _ ite m s
re so u rce s : c a rts
► t e s t "should lo g in " do
► dave = u s e r s (:o n e )
► post :c r e a t e , name: dave.name, password: 's e c r e t '
► a s s e r t _ r e d ir e c te d _ to adm in_url
► a s s e rt_ e q u a l d a v e .id , s e s s io n [ : u s e r _ id ]
► end
► t e s t "should f a i l lo g in " do
► dave = u s e r s (:o n e )
► post :c r e a te , name: dave.name, password: 'wrong'
► a s s e r t _ r e d ir e c te d _ to lo g in _ u r l
► end
► t e s t "should lo g o u t" do
► d e le te : d e stro y
► a s s e r t _ r e d ir e c te d _ to s t o r e _ u r l
► end
end
name: dave
p assw o rd_d igest: <%= B C ry p t: : Passw ord. c r e a t e ( ' s e c r e t ' ) %>
two:
name: M yStrin g
p assw o rd _d ig est: M yStrin g
1 h ttp s://g ith u b .co m /ra ils/ra ils/b lo b /3 -l-stab le /active m o d e l/lib /a ctiv e_ m o d el/se cu re _ p ass-w o rd .rb
Глава 14 • Задача И: вход в административную область 221
► d e f a u th o riz e
► u n less U s e r .fin d _ b y _ id (s e s s io n [: u s e r _ id ] )
► r e d ir e c t _ t o lo g in _ u r l, n o tic e : "Пожалуйста, пройдите авторизацию”
► end
► end
end
Но это еще не все. Теперь нам нужно разрешить создавать, обновлять и удалять
корзины:
d ef logout
s e s s io n . d e le te :u s e r_ id
end
d ef setup
lo g in _ a s :one i f defined? session
end
<% i f s e s s io n [ :u s e r _ id ] %>
<ul>
< lix % = lin k _ t o 'O r d e r s ', o rd ers_p ath % x / li >
< lix % = lin k _ t o 'P r o d u c t s ', prod ucts_path % x / li >
< lix % = li n k to 'U s e r s ’ , users_p ath % x / l.i>
</ul>
<%= b u tto n _to 'L o g o u t', lo g o u t p a t h , method: : d e le te %>
<% end %>
</div>
<div id="main">
<%= y i e ld %>
</div>
</div>
</body>
</html>
lo c a lh o s t
P r a g m a t ic B o o ksh elf
Questtonb
Listing users
New s Nam e
Contact
dave Show Edit Destroy
New U ser
rails31/depot_s/app/m odels/user. rb
a f te r _ d e s tr o y :ensure_an_adm in_rem ains
p r iv a t e
d e f ensure_an_adm in_rem ains
i f U s e r.c o u n t.z e ro ?
r a is e "Последний пользователь не может быть удален"
end
end
О Посмотрите на метод a u th e n t ic a te _ o r _ r e q u e s t_ w it h _ h tt p _ b a s ic ()
и примените его в своем фильтре : a u th o r iz e , если r e q u e s t . fo rm a t не
относится к Mime: :HTML. Проверьте его работоспособность путем доступа
к RSS-каналу Atom:
c u r l - - s ile n t --user d a v e :s e c r e t \
h t t p :/ / lo c a lh o s t : 3000/products/2/who_bought.xml
О сн о в н ы е тем ы :
Уже проще, но задача все равно нелегкая. Нам надо предоставить способ, по
зволяющий пользователю выбрать язык, предоставить сам перевод, а также внести
изменения и в представления, чтобы воспользоваться этим переводом. Мы готовы
к выполнению задачи, вооружены скромными познаниями в испанском на уровне
средней школы — пора действовать!
LANGUAGES = [
[ 'E n g lis h ', 'e n '] ,
[ " E s p a & n t ild e ;o l" . h tm l_s a fe , ' e s ']
]
Этот код выполняет две задачи.
Прежде всего, он приводит к использованию модуля 118п для установки ре
гиона по умолчанию. 118п выглядит забавно, но зато обеспечивает неизменно пра
вильный набор термина, эквивалентного слову internationalization (локализация).
Ведь слово «internationalization» начинается на «i» и заканчивается на «п», и имеет
восемнадцать букв между ними.
Затем определяется список связей между отображаемыми и локальными име
нами. К сожалению, на данный момент мы располагаем только лишь клавиатурой
с раскладкой, предназначенной для США, а в слове espanol имеется символ, ко
торый невозможно ввести с нашей клавиатуры напрямую. Разные операционные
системы обладают разными способами решения этой проблемы, и зачастую про
ще всего скопировать и вставить правильный текст с веб-сайта. При этом нужно
убедиться в том, что ваш редактор настроен для работы с UTF-8. Тем временем
мы решили использовать HTML-эквивалент испанского символа «п с тильдой».
Если не сделать ничего другого, будет показана сама разметка. Но за счет вызова
метода h tm l_ sa f е мы информируем Rails о том, что строка может быть безопасно
интерпретирована как содержащая HTML.
Чтобы Rails восприняла эти изменения конфигурации, нужно перезапустить
сервер.
Поскольку каждая страница, подвергаемая переводу, будет иметь еп- и e s -
версию (на данный момент многое еще предстоит добавить), есть смысл включить
это в URL-адрес. Давайте спланируем поместить указание на регион авансом, сде
лать его необязательным и по умолчанию использовать текущий регион, который,
в свою очередь, будет по умолчанию настроен на английский.
230 Часть II • Создание приложения
Для реализации этого хитрого плана давайте начнем с изменения файла config/
routes, rb:
rails31/depot_s/config/ ro u te s. rb
D e p o t:A p p lic a t io n .r o u te s .d r a w do
get 'adm in' => ' adm in#index'
c o n t r o lle r : sessio n s do
get 'l o g i n ' => :new
post 'l o g in ' => : c re a te
d e le te 'lo g o u t' => : d estro y
end
► scope ' ( : l o c a l e ) ' do
re so u rces : users
re so u rces : orders
re so u rces :lin e _ ite m s
re so u rces : c a r t s
re so u rces : products do
get :who_bought, on: : member
end
ro o t t o : ' s to re # in d e x ' , a s : 's t o r e '
► end
end
# перевод недоступен
lo g g e r .e r r o r fla s h .n o w [:n o tic e ]
end
end
end
d e f d e f a u lt _ u r l_ o p t io n s
{ lo c a le : I1 8 n .lo c a le }
end
end
Рис. 15.1. А н г л и й с к а я в е р с и я п е р в о й с т р а н и ц ы
foTocathost зооо/es________________________________ tf Л
P r a g m a t ic B o o k s h e l f
C offe e S crip t
C offeeScrip t is J a va S c rip t done right It provides all of
Ja v a S c rip t’s functionality w rapped in a cleaner, more
su c c in c t syntax. In the first book on this exciting new
language, CoffeeScrip t guru Trevor Burnham show s
y o u how to hold onto all the power and flexibility of
J a va S crip t while writing clearer, cleaner, and safer
code.
$ 3 6 .0 0 A d d to Cart
<ul>
< l i x a h r e f = " h t t p : / / w w w . . . ."><%= t ( ' . h o m e ' ) % > < / a x / l i >
< l i x a h r e f = " h t t p : / / w w w . . . ,/faq"><%= t ( ' . q u e s t i o n s ' ) % > < / a x / l i >
< l i x a h r e f = " h t t p : / / w w w . . . ./ n e w s "x % = t ( ' . n e w s ' ) % x / a x / l i >
< l i x a h re f= "http://w w w . .. ,/ c o n ta c t" x % = t ( ' . c o n t a c t ') % x / a x / l i >
</ul >
<% i f s e s s i o n [ : u s e r _ i d ] %>
<ul>
< lix % = l i n k _ t o ' O r d e r s ' л o rd e rs_ p a th % x / l i >
< lix % = l i n k _ t o ' P r o d u c t s ' л pro d u cts_ p ath % x / l i >
< lix % = l i n k _ t o ' U s e r s ' , u sers_ path % x / l i >
</ul>
<%= b u t t o n _ t o ' L o g o u t ' , l o g o u t _ p a t h , method: :d e l e t e %>
<% end %>
</div>
< d iv id = "m ain ">
<%= y i e l d %>
</div>
</div>
</body>
</html>
rails31/depot_s/config/locales/en.ym l
en:
la y o u ts :
a p p lic a tio n :
title : "Prag m a tic B o o k s h e lf"
home: "Home"
q u e s tio n s : "Q u e stio n s "
news: "News"
co n tact: "Contact"
rails31/depot_s/config/locales/es.ym l
es:
la y o u ts :
234 Часть II • Создание приложения
a p p lic a t io n :
title : "P u b lic a c io n e s de P ra g m a tic ”
home: " In ic io "
q u e s tio n s : "P re g u n ta s"
news: " N o t ic ia s "
c o n ta c t: "C o n ta cto "
$ 3 6 .0 0 A d d to Cart
<div c la s s = ” entry">
<%= im a g e _ta g (p ro d u ct.im a g e _u rl) %>
<h3><%= p r o d u c t . t i t le %></h3>
<%= s a n it iz e (p r o d u c t .d e s c r ip t io n ) %>
<div c la s s = "p r ic e _ lin e " >
<span class= "price"> < %= n u m b e r_to _c u rre n c y (p ro d u c t.p ric e ) %></span>
► <%= b u tto n _to t ( ' .a d d _ h tm l') , lin e _ it e m s _ p a t h (p r o d u c t_ id : p ro d u c t),
remote: tr u e %>
</div>
</div>
<% end %>
sto re :
index:
t i t l e _ h t m l: "Your Pragm atic C a ta lo g "
add_htm l: "Add to C a rt"
а затем на испанском:
rails31/depot_s/config/locales/es. yml
es:
s to re :
index:
title _ h tm l: "Su C at& aa cu te;lo g o de Prag m atic"
add_html: "A & n tild e ; a d ir a l C a r r it o "
<td colspan="2">Total</td>
<td c la s s = "to ta l_ c e ll"> < % = n u m b e r _ to _ c u r r e n c y (c a r t.to ta l_ p r ic e ) %></td>
</tr>
</table>
P u b l ic a c io m e s d e P r a g m a t ic
Su Catalogo de Pragmatic
C o ffe e Script
C offeeScript is J a va S c rip t done right. It provides all of
J a va S c rip t's functionality wrapped in a cleaner, more
su ccin ct syntax. In the first book on this exciting new
language. C o ffe e Scrip t guru Trevor Burnham show s
you how to hold onto ail the power and flexibility of
Ja va S crip t while writing clearer, cleaner, and safer
code
И опять переводы:
rails31/depot_s/config/locales/en.ym l
en:
ca rts:
ca rt:
title : "Your C a rt"
em pty: "Empty c a r t "
ch eck o u t: "Checkout"
rails31/depot_s/config/locales/es.ym l
ca rts:
ca rt:
title : "C a r r it o de la Compra"
empty: "V a c ia r C a r r it o "
ch eck o u t: "Comprar"
Глава 15 • Задача К: локализация 237
localhost
P u b l ic a c io n e s d e P r a g m a t ic
rails31/depot_s/config/locales/en.yml
en:
number:
c u rre n c y :
fo rm a t:
u n it :
p re c is io n
se p a ra to r
d e lim it e r
fo rm a t:
238 Часть II • Создание приложения
rails31/depot_s/config/locales/es. yml
number:
cu rre n c y:
fo rm a t:
u n it : "$US"
p r e c is io n : 2
s e p a ra to r:
d e lim it e r :
fo rm at: "%n %u"
P u b l ic a c io n e s de Pragm a t ic
<ul>
<% b o rd e r.erro rs.fu ll_m essa g e s. each do |msg[ %>
<li><%= msg %></li>
<% end %>
</ ul>
</div>
<% end %>
<div class="field">
<%= f . la b e l :name %><br />
<%= f.te x t_ fie ld :name, siz e: 40 %>
</div>
<div class="field">
► <%= f . la b e l :address, t ( ' . address_html' ) %><br />
<%= f.te x t_ a re a :address, rows: 3, c o ls : 40 %>
</div>
<div class="field">
<%= f . la b e l :em ail %><br />
<%= f.em a il_fie ld : em ail, siz e : 40 %>
</div>
<div class="field">
<%= f . la b e l :pay_type %><br />
<%= f . s e le c t : pay_type, Order::PAYMENT_TYPES,
► prompt: t ( ' . pay_prompt_html' ) %>
</div>
<div class= "actions">
► <%= f.subm it t ( ‘ .su b m it') %>
</div>
<% end %>
Обратите внимание: нам, как правило, не нужно вызывать функции 118п в от
ношении надписей в явном виде, пока не возникнет потребность в каких-нибудь
особенных действиях вроде разрешения HTML-объектов. А вот как выглядят со
ответствующие определения локализации:
rails31/depot_s/config/locales/en. yml
o rd e rs:
new:
240 Часть II • Создание приложения
rails31/depot_s/config/locales/es.ym l
o rd e rs:
new:
le g e n d : "P o r fa v o r , in tro d u z ca sus d ato s"
form:
name: "Nombre"
add ress_htm l: "D ire c c i& o a c u te ;n "
e m a il: "E - m a il"
p a y _ ty p e : "Forma de pago"
pay_prom pt_htm l: "S e le c c io n e un m étodo de pago"
subm it: " R e a liz a r Pedido"
P u b l ic a c io n e s d e P r a g m a t ic
ошибке, создаваемое Active Record, также может быть переведено, нам нужно будет
только предоставить перевод:
rails31/depot_s/config/locales/es. yml
es:
a c t iv e r e c o r d :
e rro rs:
messages:
i n c lu s io n : "no e st& a a c u te ; in c lu id o en la l i s t a "
b la n k : "no puede quedar en bian co"
e rro rs:
te m p late :
body: "Hay problemas con lo s s ig u ie n te s campos:"
he ad er:
one: "1 e r r o r ha impedido que e s te % {m odel} se guarde"
o th e r: "%{count> e rr o re s han impedido que e s te %{m odel} se guarde"
Обратите внимание на то, что сообщения с числами имеют, как правило, две
формы: e r r o r s . te m p la te . h e a d er. one —это сообщение, создаваемое при возникно
вении одной ошибки, и e r r o r s . te m p la te . header .o th e r —сообщение, создаваемое
во всех остальных случаях. Это позволяет переводчикам предоставить правильную
множественную форму существительных и соответствие глаголов существительным.
ШЯшшщи щ и и и и и и и м м
Pragprog Books Online \;*ШЪ t „ Ctf
j ^'Й^'В[ В Н Н ВВннВВВВВВН
0 locelhost'3000/es/orders
P u b l ic a c io n e s d e P r a g m a t ic
Garrito de la Compra P o r favor. introdusca su s datos
1* CoffeeScrip t 36.00 $ U S
4 e rro rs prohibited this order from being saved:
Total 36,00 SU S
Поскольку мы опять используем HTM L-объекты, нам нужно, чтобы эти со
общения об ошибках выводились как есть (в понятиях Rails — в необработанном
виде). Нам также нужно перевести сообщения об ошибках. Модифицируем форму
еще раз:
242 Часть II • Создание приложения
r a il s 3 1 / d e p o t _ t / a p p / v ie w s / o r d e r s / _ f o r m . h t m l. e r b
<%= fo rm _fo r(@ o rd e r) do | f | %>
<% i f (Sorder. e r r o r s . any? %>
<div id = "e r r o r _ e x p la n a tio n ">
► <h2x%=raw t ( ' e r r o r s .te m p la te , head er' , count: @ o rd e r.e rro rs .c o u n t,
► model: t ( ' a c tiv e r e c o r d .m o d e ls .o rd e r' ) ) %>.</h2>
► <p><%= t ( 'e r r o r s .t e m p la t e .b o d y ’ ) %></p>
<ul>
<% (So rd e r.e rro rs ,fu ll__m e s s a g e s . each do |msg| %>
► < lix % = ra w msg % x / li >
<% end %>
</ul>
</div>
<% end %>
<!-- ... -->
Обратите внимание на то, что мы вызову метода для перевода заголовка ша
блона ошибок передаем количество ошибок и имя модели (которая сама по себе
допускает возможность перевода).
После внесения этих изменений мы повторяем нашу попытку и видим улучше
ния, показанные на рис. 15.7.
a c t iv e r e c o r d :
m odels:
o rd e r: "ped id o"
a ttr ib u te s :
o rd e r:
Глава 15 • Задача К: локализация 243
Обратите внимание на то, что нам не нужно предоставлять для всего этого ан
глийские эквиваленты, потому что эти сообщения встроены в Rails.
Нам приятно видеть переведенные имена модели и свойств на рис. 15.8; мы за
полнили форму, отправили заказ и получили сообщение «Thank you for your order»
(«Спасибо за ваш заказ»).
Body Htm!
rails31/depot_t/config/locales/en.ym l
en:
th anks: "Thank you f o r your o rd e r"
rails31/depot_t/config/locales/es.ym l
es:
☆ Л,
P u b l ic a c io n e s de P r a g m a t ic
Su Catalogo de Pragmatic
C offe e Script
C offeeScript is Ja va S crip t done right. It provides all of
Ja va S c rip t's functionality wrapped in a cleaner more
s u c c in c t syntax. In the first book on this exciting new
language. C o ffeeScrip t guru Trevor Burnham show s you
how to hold onto all the power and flexibility of
Ja va S crip t w hile writing clearer, cleaner and safer code.
C o ffe e S c rip t
CoffeeScript is Ja v a S c rip t done right It provides all of
JavaScript's functionality' wrapped in a cleaner m ore su ccin ct
syntax, fn the first book on this exciting new language
C o ffeeScrip t guru T revor Burnham show s y o u how to hold onto
all the power and flexibility of J a va S c rip t w hile writing clearer,
cleaner, and safer code.
S3S.CK> A d d TO Cart
Теперь мы можем размещать заказы на двух языках, и наши мысли теперь на
правлены на реальное развертывание приложения. Но, поскольку у нас был весьма
нелегкий день, пора сложить инструменты и расслабиться. А с утра мы приступим
к развертыванию.
О сн о в н ы е тем ы :
Установка Passenger
Сначала нужно убедиться в том, что установлен и запущен веб-сервер Apache.
У тех, кто пользуется Mac OS X, он уже установлен вместе с операционной систе
мой, но его нужно включить, перейдя в System Preferences ►Sharing и включив Web
Sharing. Пользователи Linux уже должны были установить Apache в ходе изучения
раздела 1.3 «Установка под Linux».
Следующим шагом будет установка Passenger:
$ gem i n s t a l l passenger
$ pa ssenger-install-apache2-module
Как только все зависимости будут удовлетворены, эта команда заставит прове
сти компиляцию сразу нескольких источников и обновление конфигурационных
файлов. В течение этого процесса нам будут выдаваться запросы на обновление
конфигурации нашего АрасЬе-сервера. Первым будет запрос на включение ваших
только что созданных модулей, и он будет заключаться в добавлении показанных
далее строк к нашей АрасЬе-конфигурации.
П РИ М Е Ч А Н И Е----------------------------------------------------------------------------------------
Passenger подскажет, какие конкретно строки нужно скопировать и вставить в файл, поэто
му нужно использовать подсказанные им строки, а не те, что показаны здесь. Нам также
пришлось сократить описание пути в строке LoadModule, чтобы оно поместилось на стра
нице. Воспользуйтесь именно тем описанием пути, которое будет предоставлено Passenger.
rails31/depot_t/Gemfile
group : p ro d uction do
gem 'm ysql2'
end
Если вы выбрали другое имя базы данных, его следует запомнить, поскольку
вам понадобится настроить конфигурационный файл на соответствие выбранному
имени. Теперь давайте взглянем на конфигурационный файл.
1 http://www.sqlite.org/whentouse.html
2 http://dev.mysql.com/downloads/mysql/
254 Часть II • Создание приложения
Нужно будет изменить имя пользователя, пароль и поля базы данных, указав
необходимые значения.
При этом произойдет одно или два события. Если все настроено правильно, вы
увидите примерно такой вывод:
-- c r e a t e _ t a b l e ( " c a r t s " , { :fo rc e = > tru e })
-> 0 .1 7 2 2 s
-- c r e a t e _ t a b le ( " lin e _ it e m s " , { :fo rc e = > tru e })
-> 0 .1 2 5 5 s
-- c r e a t e _ t a b le (" o r d e r s " , { :fo rc e = > tru e })
-> 0 .1 1 7 1 s
-- c r e a t e _ t a b le (" p r o d u c t s " , { : fo rce = > tru e })
-> 0 .1 1 7 2 s
-- c r e a t e _ t a b le (" u s e r s ” , { : fo rce = > tru e })
-> 0 .1 2 5 5 s
- - in it ia liz e _ s c h e m a _ m ig r a t io n s _ t a b le ()
-> 0 .0 0 0 6 s
-- assum e_migrated_upto_version(20110711000008, "d b /m ig ra te ")
-> 0 .0 0 0 8 s
Следующее, что вам нужно знать: даже если SCM -сервер и ваш веб-сервер
являются одной и той же физической машиной, Capistrano будет обращаться к на
шему программному обеспечению SCM так, как будто оно находится на удаленной
машине. Мы можем сгладить это путем генерации открытого ключа (если у вас еще
нет такого ключа) с его последующим использованием для получения разрешения
на доступ к своему собственному серверу:
$ te s t -е ~/.ssh/id_dsa.pub || ssh-keygen -t dsa
$ cat ~/.ssh/id_dsa.pub >> ~/. ssh/authorized_keys2
Работа с сервером закончена! Впредь все будет делаться с вашей машины для
разработки.
rails31/depot_t/Gemfile
source ' h ttp :// ru b yg em s.o rg '
group p ro d u c tio n do
gem 'm ysql2'
end
# To use debugger
# gem ' ruby-debugl9 ' , .'re q u ire => 'ruby-debug'
group : t e s t do
# P r e t t y p rin te d t e s t output
gem ' t u r n ' , : re q u ire => f a ls e
end
gem 'w i l l _ p a g i n a t e ', '~> 3 .0 '
Благодаря этим нескольким шагам вы получили контроль над тем, что развер
тывается. Вы управляете тем, что относится к вашему локальному репозиторию. Вы
задаете момент, когда нужно выложить все на свой сервер. Кроме того, вы будете
управлять вводом этого кода в эксплуатацию.
rails31/depot_t/Capfile
load 'd e p lo y ' i f re s p o n d _ to ?(: namespace) # cap2 d i f f e r e n t i a t o r
rails31/depot_t/config/deploy.rb
# be sure to change these
s e t :u s e r, 'ru b y s '
s e t idomainj 'depot.pragprog .com '
260 Часть II • Создание приложения
# file paths
se t :r e p o s it o r y , "# {u s e r }@ # {d o m a in }:g it / # {a p p lic a t io n }.g it "
se t :d e p lo y _ to , "/h om e/#{user}/# {dom ain}"
namespace :d e p lo y do
desc "cause Passenger to i n i t i a t e a r e s t a r t "
ta s k : r e s t a r t do
run "touch # {c u r r e n t_ p a th }/ tm p / r e s t a r t .tx t "
end
a f t e r "d e p lo y :u p d a te _c o d e ", : b u n d le _ in s t a ll
desc " i n s t a l l th e n ecessary p r e r e q u is ite s "
ta s k : b u n d le _ in s t a ll, :r o le s => :app do
run "cd # {r e le a s e _ p a th } && bundle i n s t a l l "
end
Чтобы привести все в соответствие с нашим приложением, нам нужно будет от
редактировать несколько свойств. Нам, конечно, понадобится изменить свойства
Глава 16 • Задача Л: развертывание и эксплуатация 261
1 http://beginrescueend.com/integration/capistrano/
262 Часть II • Создание приложения
Иногда нам нужна информация низкого уровня, чтобы понять, что происходит
с данными в нашем приложении. Когда это происходит, настает время выйти на
сцену наиболее полезному отладочному средству работающего сервера.
Учтите, что в данном случае требуется явный запрос a c tiv e _ su p p o rt, посколь
ку эта инструкция обрабатывается на ранней стадии при инициализации вашего
приложения, до того как были включены библиотеки Active Support. Фактически,
одна из настроек конфигурации, предоставляемая Rails, заключается в полном от
казе от включения библиотек Active Support:
c o n fig .a c tiv e _ s u p p o rt.b a re = tr u e