Джош Кауфман - Первые 20 часов. Как быстро научиться… чему угодно
1. Все символы строчные.
2. Никаких специальных символов — только буквы и цифры.
3. Никаких пробелов — все пробелы должны быть заменены на подчеркивание.
Для этого нужно создать метод, который берет заголовок страницы и форматирует его в слаг. Сложные замены выполняются при помощи метода программирования, получившего название регулярных выражений, который преобразует или находит текст в строках в соответствии с установленными правилами [44].
Регулярные выражения могут быть очень сложными, но применяются они широко, и я смог найти подходящий шаблон. Вот как выглядит мой метод:
# Converts page name into post slug
def slugify (content)
content.downcase.gsub (/ /, '-'). gsub (/ [^a-z0–9-] /, "). squeeze ('-')
end
Теперь я могу использовать метод slugify для преобразования строк типа «Заголовок Страницы» в «заголовок_страницы», которые можно использовать в составе интернет-адреса.
Кроме того, если мы храним слаг вместе с заголовком страницы, то можем использовать его для извлечения записи страницы при помощи параметра url.
Я добавил это поле к классу DataMapper:
property: slug, String
Теперь при создании страницы мы можем превратить ее заголовок в слаг, сохранить его в базе данных и использовать для демонстрации страницы, когда потребуется ее извлечь. Именно так приложение узнаёт, какую страницу показывать.
Создание страниц
Я начал разрабатывать список необходимых маршрутов. «Главный» маршрут прост: я перенаправляю его к слагу /home/, поскольку хочу, чтобы главная страница отображала содержимое соответствующей записи.
Маршрут «создать страницу» связан с небольшим полем ввода в верхней части бокового поля. Пользователь вводит заголовок страницы в поле ввода и щелкает по кнопке. Система считывает заголовок страницы, преобразует его в слаг, затем сохраняет в базе данных заголовок, слаг и время создания. После этого система посылает запрос GET, содержащий слаг, и отображает новую страницу.
В маршруте «создать страницу» есть маленькая, но очень важная деталь: что если страница уже существует? Я не хочу переписывать ее заново, если она содержит данные. Случайная потеря данных недопустима.
К счастью, DataMapper разрешает эту проблему при помощи встроенного метода. first_or_create. Если страница уже существует, DataMapper не переписывает ее, а Sinatra перенаправляет браузер к существующей странице. Проблема решена.
Маршрут «отобразить страницу» считывает слаг в URL, извлекает запись из базы данных, а затем отображает информацию в области основного содержания. Позже я добавлю красивое форматирование, но теперь мне просто нужно, чтобы программа работала.
Редактирование страниц
Редактирование страниц включает два отдельных маршрута. Первый маршрут запросом GET вызывает страницу, которую хочет отредактировать пользователь, а затем отображает содержимое соответствующей записи базы данных в форме, удобной для редактирования.
Для отображения страниц приложение использует шаблон ERB, который обычно состоит из команд HTML + Ruby. ERB позволяет программисту писать HTML, включающий изменяемые элементы. Поскольку ERB обрабатывает страницу до того, как она появляется перед пользователем, текст на странице можно менять при каждой загрузке, основываясь на командах Ruby шаблона.
Кнопка «Сохранить» на экране редактирования посылает запрос POST приложению, которое обновляет запись Page.
Удаление страниц
Удаление страниц требует осторожности: нельзя забывать, что случайная потеря данных неприемлема. Собираясь удалить страницу, вы должны быть абсолютно уверены, что пользователь хочет именно этого.
Неправильно было бы напрямую связать кнопку «Удалить» с маршрутом DELETE приложения. При таком подходе страница удалится даже при случайном щелчке по кнопке.
Предпочтительнее разбить процесс на два этапа. Щелчок по кнопке «Удалить» на экране отсылает пользователя к окну подтверждения, где отображается заголовок страницы, которую пользователь намерен удалить. Если все правильно, пользователь щелкает по кнопке подтверждения, посылая запрос DELETE. Если кнопка «Удалить» нажата случайно, можно щелкнуть по кнопке «Отмена» или кнопке браузера «Назад».
Список всех страниц
Кнопка «Показать список страниц» направляет пользователя к слагу /all/, который отличается от слагов обычных страниц.
Вместо того чтобы извлечь одну запись, DataMapper извлекает все записи Page базы данных. Шаблон ERB для Page содержит условный цикл, который создает список всех извлеченных страниц. Каждый элемент списка содержит заголовок страницы, отображаемый в виде ссылки на соответствующий странице слаг. Щелчок по ссылке направляет пользователя на конкретную страницу.
Первый запуск приложения
Итак, у меня есть основные функции, однако есть и проблема: во время тестирования приложения попытка перейти к главной странице вызывает сообщение об ошибке. Программа пытается найти запись Home в базе данных, но ее не существует, поскольку я запустил приложение в первый раз!
Выход — создать «однократный процесс управления» при помощи программы под названием Rake. Программы Rake хранятся в папке Rakefile, которая находится в корневом каталоге приложения.
Файлы Rake работают как обычные приложения Ruby, но с одним исключением: они существуют вне вашей основной программы, и вы должны запускать их вручную.
Это делает Rake очень удобным средством для таких действий, как добавление начальной информации в базу данных перед запуском реальной программы. Я копирую необходимые фрагменты application.rb в Rakefile, затем пишу команду, которая создает новую запись для главной страницы в базе данных. Остается лишь один раз запустить следующую команду:
$ rake setup
Rake создает запись «Главная страница», и мое приложение перестает выдавать ошибку при запуске. Когда я размещу это приложение на Heroku, то запущу команду Rake удаленно, чтобы настроить базу данных перед первой попыткой использовать приложение.
Теперь все основные функции на месте. Пора добавить что-нибудь интересное.
Добавление поддержки бокового поля
Я только что сообразил, что не включил в боковое поле функцию добавления страниц, как планировал изначально. Это логическая функция, поскольку может иметь только два значения: страницу предполагается отобразить в боковом поле или нет.
Я добавил новый класс в DataMapper:
property: sidebar, Boolean, default => false
Я также добавил кнопку-флажок к экрану «Редактировать» рядом с вопросом «Включить в боковое поле?» и связал эту кнопку с соответствующим полем в базе данных. Затем написал простой цикл, чтобы найти записи в базе данных, когда sidebar = true, и вывести их в виде списка, аналогичного странице «Список всех страниц».
Я снова запускаю приложение, редактирую запись, и приложение вылетает. Проклятье!
Я снова и снова пытаюсь найти ошибку, но безуспешно. Перерыв документацию DataMapper и обратившись к Stack Overflow, я выясняю, что логические переменные плохо сочетаются с формами HTML. Нужен другой подход:
property: sidebar, Enum [: yes, no], default =>: no
Это другой способ сделать примерно то же самое. Enum, что означает «перечислять», создает список опций, и форма устанавливает, какую опцию сохранить в базе данных.
Добавление поддержки Markdown
Теперь я хочу, чтобы мои страницы могли иметь сложное форматирование, например жирный шрифт, курсив, заголовки.
В качестве форматирующего синтаксиса я выбрал Markdown, популярный и очень полезный язык разметки, разработанный Джоном Грубером [45]. Я уже знаком с работой Markdown, поскольку его использовали некоторые приложения, которые установлены на моем компьютере. Теперь мне нужно выяснить, как заставить мою программу понимать этот язык.
Довольно быстро мне удается найти несколько общедоступных библиотек Markdown. Я выбираю библиотеку rdiscount и включаю в application.rb:
require 'rdiscount'
Rdiscount преобразует текст из формата Markdown в HTML, а браузер пользователя затем отображает должным образом отформатированный текст. Файлы Markdown представляют обычные текстовые файлы, написанные по определенным правилам.
Это значит, что мне не нужно преобразовывать информацию моей страницы в Markdown, прежде чем добавлять в базу данных. Это ведь обычный текст. Если я хочу отобразить отформатированный текст, мне нужно лишь вызвать rdiscount.
Команда, которую я добавил в шаблоны ERB, отвечающие за отображение страниц, выглядит следующим образом:
<% markdown (@page.content)%>
Метод берет содержимое поля содержания страницы, преобразует в HTML, а затем отображает конечный результат у пользователя. Все просто.
Меры безопасности
А как насчет регистрации пользователей? Если я размещу приложение на Heroku, не запрашивая имени пользователя и пароля, то все смогут увидеть, что я помещаю в базу данных.