Effectuer une requête API à la route /posts.json avec la propriété external_id définie sur l’external_id d’un sujet existant déclenche une erreur similaire à celle-ci :
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.)
Lorsque la requête est effectuée avec la gem Discourse API, la réponse n’est qu’une chaîne HTML :
#<DiscourseApi::Error:\"\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n \u003cmeta charset=\"utf-8\" /\u003e\n ...
Si la requête est effectuée d’une autre manière, par exemple avec HTTParty, l’objet de réponse ressemble à ceci :
response = HTTParty.post(url, body:, headers:)
add_note_to_db(title, response)
# Essayer d'obtenir l'objet `response` à partir de la réponse
p response.response
# sorties :
# #<Net::HTTPInternalServerError 500 Internal Server Error readbody=true>
Il n’y a pas grand-chose à faire avec ça.
Voici les appels qui déclenchent le problème :
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'
Je pense qu’une erreur doit être levée dans TopicCreator#create, récupérée du bloc de transaction dans PostCreator#create, puis gérée dans le contrôleur des posts.
La solution idéale de mon point de vue serait de retourner une erreur indiquant quel sujet utilisait l’identifiant externe.
Lever une erreur dans 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
#...
# Il existe probablement une classe d'erreur existante qui pourrait gérer cela, mais coller ceci au bas de la classe topic_creator me convient :
class DuplicateExternalIdError < StandardError
attr_reader :topic
def initialize(topic)
@topic = topic
end
end
La récupérer dans PostCreator#create :
def create
if valid?
begin
transaction do
build_post_stats
create_topic # c'est ici que l'erreur se produit
# ...
end
rescue TopicCreator::DuplicateExternalIdError => e
@errors.add(:base, "External ID must be unique. Existing topic ID: #{e.topic.id}")
end
end
La gérer dans le contrôleur des 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
C’est peut-être trop spécifique à mon cas d’utilisation pour être la bonne solution. C’est lié au problème des identifiants externes et des sujets dupliqués mentionné ici : API topic's external_ID can't be reused after deleting a topic and creating a new one - #2 by simon. Idéalement, une application qui publie sur l’API Discourse pourrait utiliser le message de réponse pour désarchiver et mettre à jour le sujet qui utilisait déjà l’identifiant externe.