Выполнение API-запроса к маршруту /posts.json со свойством external_id, установленным в значение external_id существующей темы, вызывает ошибку, аналогичную следующей:
ActiveRecord::RecordNotUnique (PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_topics_on_external_id" DETAIL: Key (external_id)=(obCopying text to the system clipboard) already exists.)
Когда запрос выполняется с помощью библиотеки Discourse API, ответ представляет собой просто HTML-строку:
#<DiscourseApi::Error:"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n ...
Если запрос выполняется каким-либо другим способом, например с помощью HTTParty, объект ответа выглядит следующим образом:
response = HTTParty.post(url, body:, headers:)
add_note_to_db(title, response)
# Пытаемся получить объект `response` из ответа
p response.response
# Вывод:
#<Net::HTTPInternalServerError 500 Internal Server Error readbody=true>
С этим мало что можно сделать.
Ниже приведены вызовы, которые вызывают эту проблему:
lib/topic_creator.rb:237:in `save_topic'
lib/topic_creator.rb:58:in `create'
lib/post_creator.rb:490:in `create_topic'
lib/post_creator.rb:190:in `block in create'
lib/post_creator.rb:390:in `block in transaction'
lib/post_creator.rb:390:in `transaction'
lib/post_creator.rb:188:in `create'
lib/new_post_manager.rb:318:in `perform_create_post'
lib/new_post_manager.rb:252:in `perform'
app/controllers/posts_controller.rb:210:in `block in create'
lib/distributed_memoizer.rb:16:in `block in memoize'
lib/distributed_mutex.rb:53:in `block in synchronize'
lib/distributed_mutex.rb:49:in `synchronize'
lib/distributed_mutex.rb:49:in `synchronize'
lib/distributed_mutex.rb:34:in `synchronize'
lib/distributed_memoizer.rb:12:in `memoize'
app/controllers/posts_controller.rb:209:in `create'
Мне кажется, что в TopicCreator#create необходимо выбрасывать ошибку, перехватывать её в блоке транзакции в PostCreator#create, а затем обрабатывать в контроллере постов.
Идеальное решение с моей точки зрения — возвращать ошибку, указывающую, какая тема использует данный внешний ID.
Выбросить ошибку в TopicCreator#create:
def create
topic = Topic.new(setup_topic_params)
if topic.external_id.present? && Topic.with_deleted.exists?(external_id: topic.external_id)
existing_topic = Topic.with_deleted.find_by(external_id: topic.external_id)
raise DuplicateExternalIdError.new(existing_topic), "External ID must be unique. Existing topic ID: #{existing_topic.id}"
end
#...
# Вероятно, существует класс ошибки, который мог бы обработать это, но размещение этого в конце класса topic_creator работает для меня:
class DuplicateExternalIdError < StandardError
attr_reader :topic
def initialize(topic)
@topic = topic
end
end
Перехватить её в PostCreator#create:
def create
if valid?
begin
transaction do
build_post_stats
create_topic # именно здесь происходит ошибка
# ...
end
rescue TopicCreator::DuplicateExternalIdError => e
@errors.add(:base, "External ID must be unique. Existing topic ID: #{e.topic.id}")
end
end
Обработать её в контроллере постов:
# posts_controller.rb
def handle_duplicate_external_id_error(exception)
render json: { error: "External ID must be unique. Existing topic ID: #{exception.topic.id}" }, status: :unprocessable_entity
end
Возможно, это решение слишком специфично для моего случая использования. Оно связано с проблемой внешних ID и дублирующихся тем, о которой упоминается здесь: API topic's external_ID can't be reused after deleting a topic and creating a new one - #2 by simon. В идеале приложение, отправляющее данные через API Discourse, могло бы использовать сообщение об ошибке для восстановления (undelete) и обновления темы, которая уже использует этот внешний ID.