litbaza книги онлайнРазная литератураCrystal Programming. Введение на основе проекта в создание эффективных, безопасных и читаемых веб-приложений и приложений CLI - Джордж Дитрих

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 43 44 45 46 47 48 49 50 51 ... 75
Перейти на страницу:
абстракций, которые помогут будущему росту. Как упоминалось ранее в этой главе, если действия контроллера напрямую возвращают объект, это может помочь в обработке нескольких форматов ответов. Например, предположим, что мы хотели расширить наше приложение, разрешив ему возвращать статью как в формате HTML, так и в формате JSON, в зависимости от заголовка принятия запроса.

Чтобы справиться с генерацией HTML, мы могли бы использовать встроенную функцию Crystal (ECR), которая по сути похожа на шаблонизацию во время компиляции. Однако было бы полезно иметь что-то более гибкое, похожее на PHP Twig, Python Jinja или Embedded Ruby (ERB). На самом деле существует кристальный порт Джинджи под названием Crinja, который мы можем использовать. Итак, сначала добавьте следующее в качестве зависимости к вашему shard.yml, обязательно запустив shards install и потребовав ее в src/blog.cr:

crinja:

  github: straight-shoota/crinja

  version: ~> 0.8.0

В Crinja есть модуль Crinja::Object, который можно включить, чтобы обеспечить доступ к определенным свойствам/методам этого типа в шаблоне. Он также имеет подмодуль Auto, который работает во многом аналогично JSON::Serializable. Поскольку это модуль, он также позволит нам проверить, доступен ли конкретный объект для визуализации, чтобы мы могли обработать случай ошибки при попытке отобразить объект, который невозможно отобразить.

План установки такой:

1. Настройте согласование содержимого, чтобы конечная точка GET /article/{id} отображалась как в формате JSON, так и в формате HTML.

2. Включите и настройте Crinja::Object::Auto в нашей сущности статьи.

3. Создайте HTML-шаблон, который будет использовать данные статьи.

4. Определите собственный модуль визуализации для HTML, чтобы связать все воедино.

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

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

ACF.configuration_annotation Blog::Annotations::Template, name

  : String

Вскоре мы вернемся к использованию этой аннотации, но сначала нам нужно разобраться с другими пунктами нашего списка дел. Прежде всего, настройте согласование содержимого. Добавьте следующий код в файл src/config.cr:

def ATH::Config::ContentNegotiation.conРисунок :

  ATH::Config::ContentNegotiation?

  new(

    Rule.new(path: /^/article/d+$/, priorities: ["json",

    "html"],

      methods: ["GET"], fallback_format: "json"),

    Rule.new(priorities: ["json"], fallback_format: "json")

  )

end

Подобно тому, как мы настроили прослушиватель CORS, мы можем сделать то же самое для функции согласования контента. Однако в этом случае он настраивается путем предоставления ряда экземпляров правил, которые позволяют точно настроить согласование.

Аргумент path принимает регулярное выражение, благодаря которому это правило будет применяться только к конечным точкам, соответствующим шаблону. Учитывая, что нам нужна только одна конечная точка, поддерживающая оба формата, мы настраиваем регулярное выражение для сопоставления с его путем.

Аргументы priorities управляют форматами, которые следует учитывать. В данном случае мы хотим поддерживать JSON и HTML, поэтому у нас установлены эти значения. Порядок значений имеет значение. В случае, когда заголовок принятия допускает оба формата, будет использоваться первый соответствующий формат в массиве, которым в данном случае будет JSON.

Наше второе правило не содержит пути, поэтому оно применяется ко всем маршрутам и поддерживает только JSON. Мы также устанавливаем значение fallback_format для JSON таким образом, что JSON все равно будет возвращен, даже если заголовок accept этого не разрешает. Резервный формат также может быть установлен на nil, чтобы попробовать следующее правило, или false, чтобы вызвать ATH::Exceptions::NotAcceptable , если нет обслуживаемого формата.

См. https://athenaframework.org/Framework/Config/ContentNegotiation/Rule/ для получения дополнительной информации о том, как можно настроить правила согласования.

Теперь, когда мы это настроили, мы можем перейти к настройке нашей сущности статьи, чтобы предоставить некоторые ее данные Crinja. Это так же просто, как добавить include Crinja::Object::Auto внутри класса, а затем добавить аннотацию @[Crinja::Attributes] к самому классу сущности.

Далее мы можем создать HTML-шаблон для представления статьи. Учитывая, что это только пример, выглядеть это будет некрасиво, но свою работу он выполнит. Давайте создадим src/views/article.html.j2 со следующим содержимым:

<h1>{{ data.title }}</h1>

<p>{{ data.body }}</p>

<i>Updated at: {{ data.updated_at }}</i>

Мы получаем доступ к значениям статьи в объекте данных, который будет представлять корневые данные, предоставленные при вызове рендеринга. Это позволит в будущем расширить представленные данные за пределы статьи.

Наконец, нам нужно создать экземпляр ATH::View::FormatHandlerInterface, который будет обрабатывать процесс подключения всего, чтобы возвращаемое значение действия контроллера отображалось через Crinja и возвращалось клиенту. Создайте src/services/html_format_handler.cr со следующим содержимым:

@[ADI::Register]

class HTMLFormatHandler

  include Athena::Framework::View::FormatHandlerInterface

  private CRINJA = Crinja.new loader: Crinja::Loader::

    FileSystem

    Loader.new "#{__DIR__}/../views"

  def call(view_handler : ATH::View::ViewHandlerInterface, view

    : ATH::ViewBase, request : ATH::Request, format : String) :

      ATH::Response

    ann_configs = request.action.annotation_configurations

    unless template_ann = ann_configs[Blog::Annotations::

      Template]?

      raise "Unable to determine the template for the

        '#{request.attributes.get "_route"}' route."

    end

    unless (data = view.data).is_a? Crinja::Object

      raise ATH::Exceptions::NotAcceptable.new "Cannot convert value of type '#{view.data.class}' to '#{format}'."

    end

    content = CRINJA.get_template(template_ann.name). render({data: view.data})

    ATH::Response.new content, headers: HTTP::Headers{"content- type" => "text/html"}

  end

  def format : String

    "html"

  end

end

Помимо выполнения некоторых вещей, с которыми мы уже должны быть знакомы, таких как регистрация службы и включение модуля интерфейса, мы также определяем метод #format, который возвращает формат, который обрабатывает этот тип. Мы также создали одноэлементный экземпляр Crinja, который будет загружать шаблоны из папки src/views. Crinja считывает шаблоны при каждом вызове #get_template, поэтому нет необходимости перезапускать сервер, если вы только внесли изменения в шаблон. Однако в его нынешнем виде для этого потребуется, чтобы путь существовал и был действительным как в среде разработки, так и в производственной среде. Рассмотрите возможность использования переменной среды для указания пути.

Наконец, мы определили метод #call, который имеет доступ к

1 ... 43 44 45 46 47 48 49 50 51 ... 75
Перейти на страницу:

Комментарии
Минимальная длина комментария - 20 знаков. Уважайте себя и других!
Комментариев еще нет. Хотите быть первым?