未处理的 PG::UniqueViolation,涉及话题 external_id 字段

使用 external_id 属性设置为现有主题的 external_id/posts.json 路由发出 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:"

...`

如果以其他方式发出请求,例如使用 HTTParty,响应对象看起来像这样:

response = HTTParty.post(url, body:, headers:)
add_note_to_db(title, response)
# Try getting the `response` object from the response
p response.response

# outputs:
#<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 的事务块中捕获它,然后在 posts 控制器中处理它。

从我的角度来看,理想的解决方案是返回一个指示哪个主题正在使用外部 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
#...

# There's probably an existing error class that could handle this, but sticking this at the bottom of the topic_creator class works for me:

   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 # this is where the error is happening
          # ...
        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

这可能过于针对我的用例,不适合作为正确的解决方案。它与此处提到的关于外部 ID 和重复主题的问题有关:API topic's external_ID can't be reused after deleting a topic and creating a new one - #2 by simon Discourse API 的应用程序可以使用响应消息来取消删除并更新已在使用外部 ID 的主题。

1 个赞

是的,我很乐意收到一个改进此错误消息的 PR!

太好了!我会看看我能做什么。

可能不需要像我昨天提出的那样具体:

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

只需要一个关于违反了什么约束的详细信息的响应就足够了。