From 345a222163ac3417f9c0293d2e8e24a99b31e350 Mon Sep 17 00:00:00 2001 From: evazion Date: Mon, 7 Feb 2022 18:42:24 -0600 Subject: [PATCH] Fix #4999: Unexpected error: ActiveRecord::RecordNotUnique sometimes appears when uploading posts Fix two issues that could lead to duplicate errors when creating posts: * Fix the submit button on the upload form to disable itself on submit, to prevent accidental double submit errors. * Fix a race condition when checking for MD5 duplicates. MD5 uniqueness is checked on both the Rails level, with a uniqueness validation, and on the database level, with a unique index on the md5 column. Creating a post could fail with an ActiveRecord::RecordNotUnique error if the uniqueness validation in Rails passed, but the uniqueness constraint in the database failed. In this case, we catch the RecordNotUnique error and convert it to a Rails validation error so we can treat it like a normal validation failure. --- app/controllers/posts_controller.rb | 2 +- app/models/application_record.rb | 10 ++++++++++ app/views/uploads/show.html.erb | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 82c6e1457..728d72e89 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -66,7 +66,7 @@ class PostsController < ApplicationController def create @upload_media_asset = UploadMediaAsset.find(params[:upload_media_asset_id]) @post = authorize Post.new_from_upload(@upload_media_asset.upload, @upload_media_asset.media_asset, **permitted_attributes(Post).to_h.symbolize_keys) - @post.save + @post.save_if_unique(:md5) if @post.errors.none? if @post.warnings.any? diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 97b653f6a..e6c268682 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -172,6 +172,16 @@ class ApplicationRecord < ActiveRecord::Base end end end + + # Save the record, but convert RecordNotUnique exceptions thrown by the database into + # Rails validation errors. This way duplicate records only return one type of error. + # This assumes the table only has one uniqueness constraint in the database. + def save_if_unique(column) + save + rescue ActiveRecord::RecordNotUnique => e + self.errors.add(column, :taken) + false + end end concerning :UserMethods do diff --git a/app/views/uploads/show.html.erb b/app/views/uploads/show.html.erb index 0afdb959d..9897a594c 100644 --- a/app/views/uploads/show.html.erb +++ b/app/views/uploads/show.html.erb @@ -92,7 +92,7 @@ <%= render "related_tags/buttons" %> - <%= f.submit "Upload", id: "submit-button", data: { disable_with: false } %> + <%= f.submit "Post" %> <% if CurrentUser.can_upload_free? %> <%= f.input :is_pending, as: :boolean, label: "Upload for approval", wrapper_html: { class: "inline-block" }, input_html: { checked: @post.is_pending? } %>