# Violação única não tratada PG::UniqueViolation para o campo external\_id do tópico

**URL:** <https://meta.discourse.org/t/unhandled-pg-uniqueviolation-for-topic-external-id-field/311355>\
**Category:** Bug\
**Tags:** pr-welcome\
**Created:** [Junho 10, 2024, 7:59am UTC](https://meta.discourse.org/t/unhandled-pg-uniqueviolation-for-topic-external-id-field/311355 "2024-06-10T07:59:19Z")\
**Posts on this page:** 1\
**Showing post:** 1

<div class="post-metadata">

**Author:** ![simon](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/simon/32/339122_2.png) [@simon](https://meta.discourse.org/u/simon)\
**Post date:** [Junho 10, 2024, 7:59am UTC](https://meta.discourse.org/t/unhandled-pg-uniqueviolation-for-topic-external-id-field/311355/1 "2024-06-10T07:59:19Z")

</div>

Ao fazer uma solicitação de API para a rota `/posts.json` com a propriedade `external_id` definida para o `external_id` de um tópico existente, um erro semelhante a este é acionado:

`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.)`

Quando a solicitação é feita com a gema Discourse API, a resposta é apenas uma string HTML:

`#<DiscourseApi::Error:\"\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n \u003cmeta charset=\"utf-8\" /\u003e\n ...`

Se a solicitação for feita de outra forma, digamos com `HTTParty`, o objeto de resposta se parece com isto:

```ruby
response = HTTParty.post(url, body:, headers:)
add_note_to_db(title, response)
# Tenta obter o objeto `response` da resposta
p response.response

# saídas:
# #<Net::HTTPInternalServerError 500 Internal Server Error readbody=true>

```

Não há muito que possa ser feito com isso.

Aqui estão as chamadas que acionam o problema:

```plaintext
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'

```

Eu _acho_ que um erro precisa ser levantado em `TopicCreator#create`, resgatado do bloco de transação em `PostCreator#create`, e então tratado no controller de posts.  
A solução ideal do meu ponto de vista seria retornar um erro que indicasse qual tópico estava usando o ID externo.

Levante um erro em `TopicCreator#create`:

```ruby
  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
#...

# Provavelmente existe uma classe de erro existente que poderia lidar com isso, mas colocar isso no final da classe topic_creator funciona para mim:

   class DuplicateExternalIdError < StandardError
    attr_reader :topic

    def initialize(topic)
      @topic = topic
    end
  end

```

Resgate-o em `PostCreator#create`:

```ruby
  def create
    if valid?
      begin
        transaction do
          build_post_stats
          create_topic # é aqui que o erro está acontecendo
          # ...
        end
      rescue TopicCreator::DuplicateExternalIdError => e
        @errors.add(:base, "External ID must be unique. Existing topic ID: #{e.topic.id}")
      end
    end

```

Trate-o no controller de posts:

```ruby
# 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

```

Isso talvez seja muito específico para o meu caso de uso para ser a solução correta. Está relacionado ao problema com IDs externos e tópicos duplicados que é mencionado aqui: [API topic's external\_ID can't be reused after deleting a topic and creating a new one - #2 by simon](https://meta.discourse.org/t/api-topics-external-id-cant-be-reused-after-deleting-a-topic-and-creating-a-new-one/280835/2?u=simon). Idealmente, um aplicativo que está postando para a API do Discourse poderia usar a mensagem de resposta para desativar e atualizar o tópico que já estava usando o ID externo.

---

_[View the full topic](https://meta.discourse.org/t/unhandled-pg-uniqueviolation-for-topic-external-id-field/311355)._
