entity.title,
entity.body,
entity.updated_at,
entity.deleted_at,
entity.id
)
end
Отсюда мы можем обновить нашу конечную точку #update_article, чтобы она выглядела следующим образом:
@[ARTA::Put("/article/{id}")] @[ATHA::ParamConverter("article_entity", converter:
Blog::Converters::Database)]
@[ATHA::ParamConverter("article", converter:
ATH::RequestBodyConverter)]
def update_article(article_entity : Blog::Entities::Article,
article : Blog::Entities::Article) : Blog::Entities::Article
article_entity.title = article.title
article_entity.body = article.body
@entity_manager.persist article_entity
article_entity
end
В этом примере мы используем два преобразователя параметров. Первый извлекает реальную сущность статьи из базы данных, а второй создает ее на основе тела запроса. Затем мы применяем статью тела запроса к сущности статьи и передаем ее в #persist. Допустим, мы делаем такой запрос:
curl --request PUT 'http://localhost:3000/article/1' \ --header 'Content-Type: application/json' \
--data-raw '{
"title": "New Title",
"body": "New Body",
"updated_at": "2022-04-09T05:13:30Z",
"created_at": "2022-04-09T04:47:09Z"
}'
Это приведет к такому ответу:
{
"id": 1, "title": "New Title",
"body": "New Body",
"updated_at": "2022-04-09T05:22:44Z",
"created_at": "2022-04-09T04:47:09Z"
}
Прекрасно! title, body, и updated_at были обновлены, как и ожидалось, тогда как временные метки id и create_at из базы данных не были изменены.
И последнее, но не менее важное: нам нужна возможность удалить статью.
Удаление статьи
Мы можем обрабатывать удаления, еще раз обновив наш менеджер сущностей, включив в него метод #remove, а также метод #on_remove для наших сущностей, который будет обрабатывать настройку свойства delete_at. Затем мы могли бы использовать преобразователь параметров базы данных на конечной точке DELETE и просто предоставить #remove разрешенному объекту.
Начните с добавления этого в менеджер сущностей:
def remove(entity : DB::Serializable) : Nil
entity.on_remove if entity.responds_to? :on_remove
self.update entity
end
А это к нашей статье:
protected def on_remove : Nil
@deleted_at = Time.utc
end
Наконец, действие контроллера будет выглядеть так:
@[ARTA::Delete("/article/{id}")]
@[ATHA::ParamConverter("article", converter:
Blog::Converters::Database)]
def delete_article(article : Blog::Entities::Article) : Nil
@entity_manager.remove article
end
Затем мы могли бы сделать запрос, например, curl --request DELETE 'http:// localhost:3000/article/1' и увидеть в базе данных, что столбец delete_at установлен. Потому что метод #find? также отфильтровывает удаленные элементы, поэтому попытка удалить ту же статью еще раз приведет к ошибке 404.
В некоторых случаях API может потребоваться поддержка возврата не только JSON. Athena предоставляет несколько способов улучшить согласование контента, обрабатывая несколько форматов ответов с помощью единственного возвращаемого значения из действия контроллера. Давайте взглянем.
Использование переговоров по содержанию
На данный момент наш блог действительно собирается вместе. Мы можем создавать, получать, обновлять и удалять статьи. У нас также есть несколько довольно надежных абстракций, которые помогут будущему росту. Как упоминалось ранее в этой главе, если действия контроллера напрямую возвращают объект, это может помочь в обработке нескольких форматов ответов. Например, предположим, что мы хотели расширить наше приложение, разрешив ему возвращать статью как в формате 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. Поскольку это модуль, он также позволит нам проверить, доступен ли конкретный объект для визуализации, чтобы мы могли обработать случай ошибки при попытке отобразить объект, который невозможно отобразить.