Effettuare una richiesta API alla rotta /posts.json con la proprietà external_id impostata su external_id di un argomento esistente genera un errore simile a questo:
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 la richiesta viene effettuata con la gemma Discourse API, la risposta è solo una stringa HTML:
#<DiscourseApi::Error:\"\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n \u003cmeta charset=\"utf-8\" /\u003e\n ...
Se la richiesta viene effettuata in altro modo, ad esempio con HTTParty, l’oggetto risposta appare così:
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>
Non c’è molto che si possa fare con questo.
Ecco le chiamate che generano il problema:
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'
PENSO che un errore debba essere sollevato in TopicCreator#create, gestito dal blocco transaction in PostCreator#create, quindi gestito nel controller dei post.
La soluzione ideale dal mio punto di vista sarebbe restituire un errore che indichi quale argomento stava utilizzando l’ID esterno.
Solleva un errore in 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
#...
# C'è probabilmente una classe di errore esistente che potrebbe gestire questo, ma inserirla in fondo alla classe topic_creator funziona per me:
class DuplicateExternalIdError < StandardError
attr_reader :topic
def initialize(topic)
@topic = topic
end
end
Gestiscilo in 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
Gestiscilo nel controller dei post:
# 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
Questa è forse troppo specifica per il mio caso d’uso per essere la soluzione giusta. È correlata al problema degli ID esterni e degli argomenti duplicati menzionato qui: API topic's external_ID can't be reused after deleting a topic and creating a new one - #2 by simon. Idealmente, un’applicazione che pubblica sull’API di Discourse potrebbe utilizzare il messaggio di risposta per ripristinare e aggiornare l’argomento che stava già utilizzando l’ID esterno.