topic external_id 필드에서 처리되지 않은 PG::UniqueViolation 오류

/posts.json 경로에 external_id 속성을 기존 토픽의 external_id 값으로 설정하여 API 요청을 보내면 다음과 유사한 오류가 발생합니다:

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 gem을 사용하여 요청을 보내면 응답은 단순히 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의 트랜잭션 블록에서 이를 구제(rescue)한 다음, posts 컨트롤러에서 처리해야 합니다.

제 관점에서 이상적인 해결책은 어떤 토픽이 해당 external 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에서 구제(rescue):

  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 컨트롤러에서 처리:

# 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

이것은 제 사용 사례에 너무 구체적이어서 올바른 해결책이 아닐 수 있습니다. 이는 여기에서 언급된 external id와 중복 토픽 문제와 관련이 있습니다: API topic's external_ID can't be reused after deleting a topic and creating a new one - #2 by simon. 이상적으로는 Discourse API에 게시하는 애플리케이션이 응답 메시지를 사용하여 이미 해당 external id를 사용 중이던 토픽을 삭제 해제(undelete)하고 업데이트할 수 있어야 합니다.

1개의 좋아요

네, 이 오류 메시지를 개선하는 PR 환영합니다!

좋아요! 해볼 수 있는 것을 찾아보겠습니다.

어제 제안했던 것만큼 구체적일 필요는 없을 것 같습니다:

rescue TopicCreator::DuplicateExternalIdError => e
   @errors.add(:base, "External ID must be unique. Existing topic ID: #{e.topic.id}")

어떤 제약 조건이 위반되었는지에 대한 세부 정보가 포함된 응답만 받아도 충분할 것입니다.