Шаг А4: улучшение внешнего вида перечня товаров
У нашего заказчика есть еще одно, последнее пожелание (кажется, у заказчиков
такое пожелание есть всегда). Перечень товаров имеет не слишком приятный вид.
Нельзя ли его «хоть как-то приукрасить»?
И раз уж мы этим занялись, то нельзя ли наряду с URL изображения показать
само изображение товара? Здесь мы сталкиваемся с дилеммой. Как разработчики,
мы учимся воспринимать подобные запросы, сделав резкий вдох, понимающе
кивнув головой и вкрадчиво спросив: «А что бы вы хотели увидеть?» В то же время
нам хочется продемонстрировать все, на что мы способны. В конце концов,
•а первый план выходит та легкость, с которой подобные изменения делаются
в Rails, и мы запускаем свой испытанный текстовый редактор.
Но затем мы сталкиваемся со второй дилеммой. До сих пор единственной созданной
нами строчкой кода, отвечавшей за отображение перечня товаров, была
scaffold :product
Да, с настройкой внешнего вида в ней не очень-то развернешься! Мы использовали
динамическую временную платформу (scaffold), которая самостоятельно
настраивается при каждом запросе. Если есть желание посмотреть на действующий
scaffold-код представления, нам нужно заставить Rails сгенерировать его
в явном виде, создав статическую временную платформу. Scaffold-генератор вос-
вринимает два параметра: имя модели и имя контроллера.
«epot> ruby script/generate scaffold product admin
exists app/controllers/
exists app/helpers/
exists app/views/admin
exists test/functional/
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
skip app/models/product.rb
identical test/unit/product_test.rb
identical test/fixtures/products.yml
create app/views/admin/_form.rhtml
create app/views/admin/list.rhtml
create app/views/admin/show.rhtml
create app/views/admin/new.rhtml
create app/views/admin/edit.rhtml
pterwrite app/controllers/admin_controller.rb? [Ynaqd] у
force app/controllers/admin_controller.rb
sverwrite test/functional/admin_controller_test.rb? [Ynaqd] у
force test/functional/admin_controller_test.rb
identical app/helpers/admin_helper.rb
create app/views/layouts/admin.rhtml
create public/stylesheets/scaffold.ess
Казалось бы, мы стали свидетелями массы событий! Тем не менее все доволь-
шо просто. Идет проверка на существование файла модели, а затем создаются все
файлы представления, необходимые для отображения экранов по ведению базы
товаров. Но, добравшись до контроллера, процесс останавливается. Заметив, что
файл admin_controller подвергался редактированию, программа запрашивает у нас
разрешение, перед тем как перезаписать его в новой версии. Единственным внесенным
ранее изменением было добавление строки scaffold : product, которая
нам больше не нужна, поэтому мы отвечаем утвердительно — Y. Запрос поступит
и перед перезаписью функционального теста контроллеров, и мы опять ответим
утвердительно.
Если вы обновите страницу браузера, то не заметите никаких перемен: код, добавленный
статической временной платформой, идентичен тому, который генерировался
на лету динамической платформой. Но теперь, имея код в своем распоряжении,
мы можем его редактировать.
Текущий перечень товаров создается Rails-представлением, которое находится
в файле app/views/admin/list.rhtml. Исходный код, созданный scaffold-генератором,
выглядит следующим образом:
Listing products
<% for column in Product
<%= column.human
<% end %>
<% for product in ©products %>
<% for column in Product.content_columns %>
<%=h product.send(column.name) %>
<% end %>
<%= link_to 'Show', :action => 'show', :id => product %>
<%= link_to ' E d i t ' , taction => ' e d i t ' , :id => product %>
<%= link_to 'Destroy', { :action => 'destroy', :id => product },
:confirm => 'Are you sure?',
:method => :post %>
<% end %>
<%= link_to 'Previous page',
{ :page => @product_pages.current.previous }
if @product_pages.current.previous %>
<%= link_to 'Next page',
{ :page => @product_pages.current.next }
if @product_pages.current.next %>
<%= link_to 'New product', :action => 'new' %>
Для последовательного перебора столбцов в модели Product представление
использует ERb. Для каждого товара в массиве ^products оно создает строку
таблицы. (Этот массив формируется в контроллере, в методе действия под названием
list. ) Строка содержит ячейки для каждого столбца в возвращаемом наборе
данных.
Динамический характер кода не вызывает сомнений, поскольку отображение
будет автоматически обновляться, чтобы вместить новые столбцы. Впрочем, это
приводит к тому, что отображение становится в некотором роде настраиваемым.
Итак, давайте переделаем этот код, чтобы улучшить внешний вид отображаемой
информации.
Перед тем как углубиться в эту задачу, неплохо все-таки обзавестись соответствующим
набором проверочных данных, с которыми можно будет работать. Для
этого можно воспользоваться интерфейсом, который генерируется временной
жлатформой, и набрать данные в окне браузера. Тем не менее, если мы так и сделаем,
то будущие разработчики, работающие с нашим программным кодом, будут
вынуждены делать то же самое. И если мы работаем над этим проектом не одни,
1 в составе общей команды, то каждому разработчику нужно будет вводить свои
собственные данные. Было бы лучше избрать для заполнения таблицы более
управляемый способ. Оказывается, такой способ есть. Нас спасут миграции!
Создадим такую миграцию, которая работает исключительно с данными. Метод
up будет использован для добавления трех строк, содержащих типовые дан-
вые, в нашу таблицу products. А метод down будет использован для очистки таблицы.
Сама миграция будет создана обычным способом:
iepot> ruby script/generate migration add_test_data
nists db/migrate
create db/migrate/003_add_test_data.rb
Затем мы добавим код, предназначенный для заполнения таблицы products.
Для этого нам понадобится метод create из модели Product. Приводимый ниже
н>д представляет собой выдержку из этого файла. (Вместо того чтобы набирать
содержимое файла миграции вручную, вы можете скопировать этот файл из интер-
яет-подборки примеров кода1. Скопируйте его в каталог db/migrate вашего приложения.
Раз уж вы зашли в Интернет, то скопируйте изображения2 и файл depot.css3
ш соответствующие места (в каталоги приложения public/images и public/stylesheets).
Лсгинг файла db/migrate/003_add_test_data.rb
class AddTestData 'Pragmatic Version Control' .
:description =>
%{
This book is a recipe-based approach to using Subversion
that will get you up and running quickly--and correctly .
A l l projects need version c o n t r o l : i t ' s a foundational
piece of any project's infrastructure. Yet half of all
project teams in the U.S. don't use any version control
at all. Many others don't use it well , and end up
experiencing time-consuming problems.
},
:image_url => '/images/svn.jpg' ,
: p r i c e => 28.50)
# . . .
end
def self.down
Product.delete_all
end
end
(Заметьте, что в коде используется структура %{. . .}. Она является альтернативой
синтаксису строковых литералов, заключенных в двойные кавычки, и удобна
при использовании длинных строк.) Запуск миграции приведет к заполнению
таблицы products проверочными данными:
depot> rake db:migrate
Ну а теперь улучшим внешний вид перечня товаров. Эта работа состоит из
двух частей. В конечном счете, мы напишем HTML-код, использующий каскадные
таблицы стилей — CSS — для определения стиля представления. Но чтобы
все это заработало, нам следует предписать браузеру, чтобы он обращался к таблице
стилей.
Нам нужно место, куда можно будет поместить наши стилевые определения
CSS. Все приложения, полученные в результате scaffold-генерации, используют
таблицу стилей scaffold.css, которая находится в каталоге public/stylesheets. Вместо
внесения изменений в этот файл мы создадим новую таблицу стилей приложения,
depot.css, и поместим ее в тот же каталог. Полный листинг этой таблицы стилей
приведен в приложении В.
И наконец, нам нужна ссылка на эту таблицу стилей внутри нашей HTML-
страницы. Если вы посмотрите на содержимое тех файлов с расширением .rhtml,
которые мы создавали до этого, то не найдете в них никаких ссылок на таблицы
стилей.
Вы не найдете в них даже HTML-раздела , в котором обычно находятся
эти ссылки. Вместо этого Rails содержит отдельный файл, используемый для создания
стандартной страничной среды для всех управляющих (admin) страниц.
Этот файл, названный admin.rhtml, относится к макету (layout) Rails и находится
в каталоге layouts.
Листинг файла app/views/layouts/admin.rhtml
Admin: <%= controller.action_name %>
<%= stylesheet_link_tag'scaffold'%>
<%= flash[rnotice] %>
<%=yield:layout %>
Код загрузки таблицы стилей находится в четвертой строке. Для создания
HTML-тега , который загружает стандартную таблицу стилей для временной
платформы, в ней используется метод stylesheet_link_tag. Мы просто
добавим сюда ссылку на наш файл depot.css (опустив расширение .ess). Сейчас не
стоит обращать внимание на остальную часть файла, мы рассмотрим ее чуть позже:
Раз уж мы занялись этим файлом, то добавим в его начало директиву < ! D0C -
TYPE. . . . Без этой строки Internet Explorer работает особым образом, который несовместим
с веб-стандартами. Теперь верхняя часть нашего форматного файла
выглядит так:
Admin: <%= controller.action_name %>
<%=stylesheet_link_tag'scaffold','depot' %>
Теперь, когда у нас с таблицами стилей все в порядке, мы отредактируем файл
IsLrhtml в каталоге app/views/admin и для замены динамического отображения
столбцов воспользуемся простым табличным шаблоном.
/Ысгинг файла app/views/admin/listrhtml
<п1>Перечень товаров
<% for product in ^products %>
"/ >
" />
<%= h(product.title) %>
<%= h (truncate(product.description,80)) %>
<%=link_to 'Показать',
<%=link_to 'Изменить',
<%= link_t o ' У д а л и т ь ' ,
<% end %>
@product_pages.current.previous })
end
%>
<%= if @product_pages.current.next
link_to("Следующая страница" ,
{ :page => @product_pages.current.next })
end
%>
<%=link_to 'Новый товар' , raction => 'new' %>
________________________________________________
ЧТО ТАКОЕ : METHOD => :POST?
Возможно, вы заметили, что сгенерированная при создании временной платформы ссылка
Destroy включает параметр : method => :post. Этот параметр был добавлен в Rails 1.2, и он предоставляет
нам беглое знакомство с будущим Rails.
Для общения с серверами браузеры используют протокол HTTP. Он определяет набор глаголов,
который может использоваться браузером, и определяет, когда каждый из них может быть использован.
К примеру, обычная гиперссылка использует HTTP-запрос GET. Этот запрос определен
протоколом HTTP для извлечения данных и не должен иметь никаких побочных эффектов.
В технической терминологии он равнодействующий — вы можете многократно использовать
один и тот же GET-запрос и каждый раз получать один и тот же результат.
Но если мы используем GET-запрос в качестве ссылки на действие Rails, которое удаляет товар,
он теряет равнодействие: он сработает лишь первый раз, но при последующих щелчках на
ссылке вызовет ошибку. Поэтому команда разработчиков Rails внесла изменения в генератор
кода временной платформы, чтобы ссылка использовала HTTP POST. Этому запросу разрешено
иметь побочные эффекты, поэтому он больше подходит для удаления.
Со временем ожидается, что Rails будет все более строгой в отношении корректного использования
протокола HTTP.
________________________________________________
Даже в этом простом шаблоне используется ряд встроенных свойств Rails:
Строки перечня выводятся на перемежающемся цветовом фоне. Такой эффект
достигается присвоением CSS-атрибуту class каждой строки либо значения
list-line-even , либо значения list-line-odd . Этим занимается вспомогательный
метод Rails под названием cycle, который автоматически переключает
два стилевых имени для последовательных строк.
Для нейтрализации HTML-чувствительных символов в наименовании и описании
товара используется метод п. Поэтому вы можете наблюдать разметку
в описании: вместо того чтобы подвергнуться интерпретации, она была нейтрализована
и отображена.
Для того чтобы отобразить лишь 80 первых символов описания, мы также воспользовались
помощником truncate.
Обратите внимание на то, что строка link_to ' Destroy' содержит параметр
: confirm => "Are you sure?". Если вы щелкнете на этой ссылке, Rails сделает
так, чтобы ваш браузер вывел всплывающее диалоговое окно с запросом подтверждения
перед тем, как пойти по ссылке и удалить товар. (Присмотритесь
также и к расположенной выше врезке, в которой приводятся некоторые подробности
этого действия.)
Итак, мы поместили в базу данных некоторые тестовые данные, переписали
файл list.rhtml, с помощью которого отображается перечень товаров, создали
таблицу стилей depot.css и связали эту таблицу с нашей страницей, отредактировав
файл формата admin.rhtml. Теперь запустите браузер, укажите в нем адрес
tocalhost:3000/admin/list, и в результате появится перечень товаров, который будет
иметь следующий вид.
Статическая временная платформа Rails предоставляет в наше распоряжение
свой исходный код в виде файлов, в которые мы можем внести свои поправки и
тут же увидеть результаты. Комбинация динамических и статических временных
платформ приносит нам ту самую гибкость, которая необходима при ускоренной
разработке приложений. Мы можем видоизменить конкретный исходный файл,
оставив в покое все остальные, — наряду с доступностью изменения носят локализованный
характер.
Итак, к немалому удовольствию заказчика, мы с гордостью демонстрируем
ему обновленный перечень товаров. Задача решена. Пора обедать.