RoR: Разработка Чата

Сегодня мы с вами создадим приложение на Руби на Рельсах, он же Рейлс, он же РоР. (Ruby on Rails, RoR, Rails). Я для краткости буду употреблять выражения «рельсы», «рейлс» или «рор».

Приложение будет немного отличаться от большинства примеров для чайников, и будет сконцентрировано на демонстрации интергрированных в рельсы аяксовых библиотек для динамического обновления страницы и спецеэффектов. Что может быть для этого нагляднее, чем чат? Разве что биржевые котировки. Но мы всё же сделаем чат.

Хотя это не туториал, а демонстрация возможностей, вы вполне можете повторить всё это на вашей системе. Всё, что вам понадобится, это mysql-сервер, и рельсы версии как минимум 2.0. Единственное отличие от более ранних, в рамках этого примера, это суффиксы шаблонов (.rhtml в первом, .html.erb во втором).

Я выполнял это на Ubuntu Hardy 8.04, но это должно работать на любой платформе, в случае виндовс с очевидными отличиями в виде разделителя в путях.

С сожалением должен констатировать, что админы Хабра так и не потрудились обеспечить нормальный ввод неэкранированного ХТМЛ и СГМЛ-кода (да и экранированного тоже: попробуйте ввести «меньше»-!DOCTYPE...), поэтому весь код, который нужно занести в файлы проекта, я привожу в виде фоток, так что вам придётся перебивать код вручную, если пожелаете это повторить. С другой стороны, обещаю, что никакой файл не выходит за пределы стандартного терминала 80x25, так что работы не так много. Опять же перенабор полезен для освоения.

Перед началом всего я настоятельно рекомендую удостовериться, что ваш mysql-сервер создаёт базы в умолчательной кодировке utf-8, и её же использует при коннекте от клиентов. Если нет, придётся добавить ключ к команде mysqladmin ниже. Также я не люблю писать «-u root» с каждым вызовом mysql, поэтому в секции [client] файла my.cnf у меня значится user=root.

Приступим же:

rails chat; cd chat

Теперь база:

mysqladmin create chat_development

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

script/generate migration create_conversation

В db/migrate/001_create_conversation.rb после def self_up добавляем:

Рейлс забесплатно добавит нам колонки «id», «created_at» и «updated_at», и автоматически будет заботиться об их содержании. Запускаем миграцию, и таблица будет создана:

rake db:migrate

Для приличия надо что-то иногда выводить пользователю в броузер. Создадим общий шаблон app/views/layouts/chat.html.erb следующего вида:

Наиболее интересное место это предложение <%= yield -%>, собственно выводящее содержимое конкретного шаблона. Опишем их позже.

Теперь контроллер. Как и всё приложение, он крайне примитивен. Создадим
app/controllers/chat_controller.rb такого содержания:


Message.find выводит нам последние 30 сообщений (Select From), ориентруясь по «id». По хорошему надо бы по «created_at», но больно уж писать лень было.

Message.create сообщение добавляет (Insert Into), как легко понять.

В рамках этого развлечения систему управления пользователями мы себе позволить не можем, поэтому просто присваиваем посетителям цифровой ник. Если он им надоедает, они жмут "Новый ник", и мы им выдаём другой.

Нам нужны шаблоны. Шаблон это файл, который называется по умолчанию так же, как и акция контроллера. Иными словами, после отработки def say по умолчанию (а мы умолчания любим) будет вызван шаблон say.html.erb (в старом роре — say.rhtml). Создадим директорию шаблонов для нашего единственного контроллера chat.

mkdir app/views/chat

Центр этого проекта — шаблон вывода app/views/chat/index.html.erb:

Вот понаписано-то всякого! Разберём по кусочкам.

То, что :partial, это просто вставка фрагмента кода из другого файла, смотри ниже.

form_remote_tag создаст для нас обычную ХТМЛ-ную форму, но с обвесками в виде аяксовых вызовов из библиотек prototype и script.aculo.us, благодаря чему мы можем не выдавать обычный POST и не перерисовывать экран. Главный параметр вызова это :update, который указывает нам, элемент с каким id в нашей странице получит результат работы акции, указанной в :action. Контроллер по умолчанию тот же, "chat".

link_to_remote работает почти так же, мы используем его, чтобы заменить ник.

periodically_call_remote — это наш позор. Никогда не делайте так чаты. А делайте их с помощью плагина к монгрелу (или иному веб-серверу), который сможет обеспечивать server-push в случае появления новых сообщений. Но так как у нас всё очень примитивно, мы просто перезагружаем див с телом чата. Каждые 10 секунд (умолчание, да).

Фрагмент (partial) — это кусок шаблона, который можно использовать многократно, вроде вызова функции. Нам понадобится перерисовывать основное окно с содержимым чата в разных местах, поэтому создадим фрагмент app/views/chat/_conversation.html.erb:

UPD: по просьбам местных хакеров мы добавили буквально пару символов в этот фрагмент, чтобы зловреды не вставляли код в свои реплики.

В шаблоне index мы уже видели его вызов. Ещё он вызывается в шаблоне say. Создадим app/views/chat/say.html.erb:

При нажатии ввод после заполнения формы вызывается действие say. Найдите в коде контроллера def say.

И последнее. В последних версиях рора реализованы способы защиты от подмены содержимого форм в рамках сессии. По умолчанию они включены, но нам они будут мешать, так как мы можем выбросить сессию, а потом подать ТУ ЖЕ форму, за счёт использования аякса. Поэтому надо подправить app/controllers/application.rb. Найдите там строчку, начинающуюся на protect_from_forgery, и закомментируйте. Октоторпом в первой позици.

Проект готов. Запускаем сервер:

script/server

Посмотреть, что получилось: http://localhost:3000/chat.

Что же мы сделали? Код представляет из себя, как обычно в таких случаях, кровавое месиво из скриптового языка, ERB, ХТМЛ, CSS, эскуэля, и яваскрипта. Но благодаря усилиям разработчкиков рора, согласно общему мнению (с которым я вполне солидарен), архитектура и инструментарий рельсов даёт нам возможность сделать это месиво всё же менее кровавым, чем принято, и как можно большее количество кода записывать на наиболее эстетически предпочтительном языке, то есть на руби. SQL свёлся к огрызку "id DESC", а яваскрипт к прототайпному вызову $('sentence'), чтобы нам всегда возвращать фокус на окошко ввода. Прототайп (и рельсовая обёртка вокруг него) позволил нам обновлять фрагменты страницы в фоне, а скриптакулос зажигает поле ввода аццким жёлтым огнём после отправки реплики, а также создаёт эффект прилетания нового ника.

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

Вход для пользователей