topic external_id フィールドでUnhandled 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:\"\u003c!DOCTYPE html\u003e\\n\u003chtml lang=\\\"en\\\"\u003e\\n\u003chead\u003e\\n \u003cmeta charset=\\\"utf-8\\\" /\u003e\\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 に投稿するアプリケーションは、レスポンスメッセージを使用して、既に外部 ID を使用していたトピックを復元および更新できるはずです。

「いいね!」 1

はい、このエラーメッセージを改善するPRで問題ありません!

素晴らしい!やってみます。

昨日提案していたほど具体的である必要はないでしょう。

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

どの制約が違反されたかの詳細がわかる応答が得られれば十分です。