воскресенье, 28 марта 2010 г.

Призрак RoR

В прошлый раз я рассказывал о небольшом разделении логики и представления в RESTAS, однако, стоило немного обобщить решение, как это разделение стало вполне ощутимым и, как мне кажется, весьма мощным. Итак, я в прошлый раз я рассказал, что теперь для маршрутов (routes) можно указывать :render-method: функцию, которая будет отвечать за генерацию контента из данных, и что можно задать :default-render-method для целого модуля. Немного поразмышляв в этом направлении я вдруг понял, что в качестве :render-method имеет смысл разрешать задавать не только функцию, но вообще произвольный объект. Я определил следующий generic-метод:
(defgeneric restas:render-object (designer object)
(:documentation "Render object via designer"))
который теперь всегда вызывается для обработки данных, возвращаемых обработчиками маршрутов (задаваемых через define-route). Здесь видно, что для генерации контента используется два объекта: designer и данные, так что можно говорить о полноценном разделении логики и представления, ну а мультиметоды в CL, как мне кажется, делают её значительной более мощной, чем механизм разделения логики и представления в тех же RoR или Django. Дефолтовый designer указывается в переменной модуля |module-name|:*default-render-method* и по-умолчанию установлен в nil. В RESTAS определены дефолтовые реализации restas:render-object, которые в качестве данных могу принимать
  • строку или массив octets - данные просто отдаются клиенту без какой-либо дополнительной обработки
  • pathname - файл отдаётся клиенту с помощью hunchentoot:handle-static-file
  • integer - результат интерпретируется как HTTP-статус и клиенту отдаётся соответствующая специальная страница
  • В прочих случаях сообщается об ошибке
Так же, определенна специализации, когда в качестве designer передаётся функция:
  • pathname или integer - вызывается дефолтовый обработчик
  • В прочих случаях с помощью funcall вызывается указанная функция для обработки переданных данных и результат отдаётся клиенту
Кроме этого, "из коробки" поддерживается возможность указать в качестве designer произвольный package. Собственно, из-за этой возможности я и назвал заметку "Призрак RoR". В RoR можно создать элемент модели, создать соответствующий шаблон и приложение вдруг начинает работать, хотя логики в нём ноль, что выглядит как небольшое волшебство. Работает это из-за наличия стандартного механизма сопоставления элементов модели элементам представления. В RESTAS всё, конечно, не так, но
  • Каждый маршрут является именованным и связан с символом
  • Почти всегда для логики представления я использую cl-closure-template, которая компилирует шаблоны в функции, для которых создаётся отдельный пакет
Если следовать небольшому соглашению и именовать шаблоны так же, как и маршруты, то можно в качестве *default-render-method* указать пакет, содержащий в себе функции шаблонов, и RESTAS будет автоматически вызывать для обработки данных, генерированных в обработчике маршрута, функцию-шаблон с именем (symbol-name) совпадающим с именем маршрута. Вот такое небольшое волшебство, реализованное весьма простым и понятным образом. Поскольку, технически это всего лишь специализация restas:render-object, то её можно использовать и другими способами, например, сейчас в restas-colorize (аналог pastebin, в работе его можно посмотреть на lisper.ru), для генерации разметки используется следующий код:
(restas:define-default-render-method (obj)
(closure-template.standard:xhtml-strict-frame
(list :title (getf obj :title)
:body (restas.colorize.view:with-main-menu
(list :href-all (restas:genurl 'list-pastes)
:href-create (restas:genurl 'create-paste)
:body (restas:render-object (find-package '#:restas.colorize.view)
obj)))
:css (iter (for item in '("style.css" "colorize.css"))
(collect (restas:genurl 'css :file item))))))
Здесь задаётся *default-render-method* в виде функции (для упрощения и наглядности используется специальный макрос), которая с помощью "пакета с шаблонами" генерирует содержательную часть страницы:
(restas:render-object (find-package '#:restas.colorize.view) obj)
которую затем использует для генерации законченного html-кода.

Ну и конечно, разработчик может определить собственные специализации restas:render-object для своего типа designer. Например, в restas-wiki нельзя решить проблему генерации разметки только за счёт шаблонов (ибо нужно конвертировать сложный формат в html). Поэтому, я сейчас рассматриваю возможность разработки отдельного класса wiki-designer, который будет отвечать за генерацию разметки и будет производить основную обработку с помощью generic-методов. Это даёт возможность приложению, использующему модуль restas-wiki (например, lisper.ru), определить собственный designer, наследующий от wiki-designer и затем с помощью динамического связывания в restas:define-submodule (почитать об этом можно здесь) указать свой способ отображения wiki-страниц: т.е. можно будет полностью изменить способ отображения wiki-страниц не изменив при этом ни строчки оригинального кода. И это как раз тот уровень модульности, к которому я стремлюсь при разработке RESTAS.

10 комментариев:

  1. А почему designer называется designer, а не какой-нибудь renderer?

    ОтветитьУдалить
  2. Ну, слово designer более романтичное что-ли :) А есть причины, почему оно не очень хорошо подходит?

    ОтветитьУдалить
  3. designer я воспринимаю как что-то позволяющее и редактировать и отображать данные. А, например, renderer только отображает.

    Речь идет в основном про отображение, да и метод называется restas:render-object; этот объект может хранится в переменной |module-name|:*default-render-method*. Поэтому designer неожиданно чуть-чуть.

    Кстати, *default-render-method* мог бы быть *default-renderer*.

    Но это дело вкуса.

    ОтветитьУдалить
  4. Слово renderer мне не нравится, как-то коряво...

    ОтветитьУдалить
  5. Народ пользуется таким словом, например в java server faces: http://dsc.sun.com/docs/jscreator/apis/jsf/javax/faces/render/Renderer.html

    ОтветитьУдалить
  6. Не, ну там для URL, я бы тоже не стал использовать designer, но тут таки идёт генерация целых web-страниц, так что designer кажется мне нормальным словом, и выглядит красиво :)

    ОтветитьУдалить
  7. А по-моему renderer (по-русски "отрисовщик") лучше. Designer у меня больше с вёб-дизайном ассоциируется.

    Если нужен перевод уроков по Restas на английский, напишите мне на почту.

    ОтветитьУдалить
  8. > Designer у меня больше с вёб-дизайном ассоциируется.

    Ну так он как бы и имеет самое непосредственное отношение к веб-дизайну.

    > Если нужен перевод уроков по Restas на английский, напишите мне на почту

    Хм, очень интересное предложение! Я имею план в обозримом будущем собрать всю инфу о RESTAS, которой сейчас владею (гы) в неком мануале. Он, конечно (ибо мой английский того, не звучит), будет на русском. Вот перевести это дело на english было бы очень интересно. Но только я не понял куда писать, профиль закрыт :(

    ОтветитьУдалить
  9. valeriy.fedotov на гмыле
    Или laser123 в lisp@

    Странно, я думал, что в профиле видно...

    ОтветитьУдалить
  10. @Valeriy Fedotov
    Ok. Что бы было видно почту надо профиль открыть на настройках акаунта Google.

    ОтветитьУдалить