id = request.attributes.get "id", Int64
unless model = @entity_manager.repository(T).find? Id
raise ATH::Exceptions::NotFound.new "An item with the provided ID could not be found."
end
request.attributes.set configuration.name, model, T
end
end
Как и в случае с предыдущим прослушивателем, нам нужно сделать прослушиватель сервисом с помощью аннотации ADI::Register. Фактическая логика включает в себя извлечение параметра пути идентификатора из атрибутов запроса, использование его для поиска связанного объекта, если таковой имеется, и установку объекта в атрибутах запроса.
Если объект с предоставленным идентификатором не найден, мы возвращаем ответ об ошибке 404.
Последняя ключевая часть того, как это работает, относится к ранее в главе, когда мы изучали, как Athena предоставляет аргументы для каждого действия контроллера. Одним из таких способов разрешения аргументов является использование атрибутов запроса, которые можно рассматривать как хранилище ключей/значений для произвольных данных, связанных с запросом, к которым автоматически добавляются параметры пути и запроса.
В контексте нашего конвертера метод configuration.name представляет имя параметра действия, к которому относится конвертер, на основе значения, указанного в аннотации. Мы используем это, чтобы установить имя атрибута, например, article, для разрешенного объекта. Затем Athena увидит, что это действие контроллера имеет параметр с именем article, проверит, существует ли атрибут с таким именем, и предоставит его действию, если он существует. Используя этот конвертер, мы можем обновить действие #article следующим образом:
@[ARTA::Get("/article/{id}")]
@[ATHA::ParamConverter("article", converter:
Blog::Converters::Database)]
def article(article : Blog::Entities::Article) :
Blog::Entities::Article
article
end
Та-да! Простой способ предоставления объектов базы данных непосредственно в качестве аргументов действия через их идентификаторы. Хотя на данный момент у нас уже довольно много конечных точек, связанных со статьями, нам все еще не хватает способа обновить или удалить статью. Давайте сначала сосредоточимся на том, как обновить статью.
Обновление статьи
На первый взгляд обновление записей базы данных может показаться простым, но на самом деле оно может быть довольно сложным из-за характера процесса. Например, чтобы обновить сущность, сначала необходимо получить ее текущий экземпляр, а затем применить к нему изменения. Изменения обычно представляются в виде тела запроса к конечной точке PUT с включенным идентификатором объекта, в отличие от конечной точки POST. Проблема заключается в том, как применить изменения из нового тела запроса к существующей сущности.
Сериализатор Athena имеет концепцию конструкторов объектов, которые управляют тем, как сначала инициализируется десериализуемый объект. По умолчанию они создаются обычным способом с помощью метода .new. Он предлагает возможность определять собственные объекты, что мы могли бы сделать, чтобы получить объект из базы данных на основе свойства ID в теле запроса. Затем мы применим остальную часть тела запроса к полученной записи. Это гарантирует правильную обработку скрытых значений базы данных, а также выполнение сложной части применения изменений к объекту.
Однако, поскольку это немного усложняет работу сериализатора Athena, а в нашей статье есть только два свойства, мы не собираемся это реализовывать. Если вам интересно, как это будет выглядеть, или вы хотите попробовать реализовать это самостоятельно, ознакомьтесь с рецептом кулинарной книги: https://athenaframework.org/cookbook/object_constructors/#db. Он использует Granite ORM, но переключить его на наш EntityManager должно быть довольно просто.
Вместо использования конструктора объекта мы просто собираемся вручную сопоставить значения из тела запроса и применить их к объекту, полученному из базы данных. Прежде чем мы сможем это сделать, нам сначала нужно обновить менеджер сущностей для обработки обновлений. Первым шагом является обновление #persist, чтобы проверить, установлен ли идентификатор с помощью следующего:
def persist(entity : DB::Serializable) : Nil
entity.before_save if entity.responds_to? :before_save
if entity.id?.nil?
entity.after_save self.save entity
else
self.update entity
end
Где метод #update выглядит следующим образом:
private def update(entity : Blog::Entities::Article) : Nil
@@connection.exec(
%(UPDATE "articles" SET "title" = $1, "body" = $2,
"updated_at" = $3, "deleted_at" = $4 WHERE "id" = $5;),