diff --git a/app/controllers/content_controller.rb b/app/controllers/content_controller.rb index 2cbeb0df..dc138aa5 100644 --- a/app/controllers/content_controller.rb +++ b/app/controllers/content_controller.rb @@ -492,20 +492,32 @@ class ContentController < ApplicationController # Are we pinning or unpinning? new_pin_status = !@image.pinned - # Use update instead of update_column to trigger model callbacks - # The model callbacks will handle unpinning other images automatically - @image.update!(pinned: new_pin_status) + # If we're pinning this image, unpin all other images for this content first + # This prevents database locking issues from the model callbacks + if new_pin_status == true + # Unpin other image uploads for this content + ImageUpload.where(content_type: content.class.name, content_id: content.id, pinned: true) + .where.not(id: @image.id) + .update_all(pinned: false) + + # Also unpin any basil commissions for this content + BasilCommission.where(entity_type: content.class.name, entity_id: content.id, pinned: true) + .update_all(pinned: false) + end + + # Now update this image's pin status (without triggering callbacks that cause locks) + @image.update_column(:pinned, new_pin_status) # Clear any cached images to ensure pinned images are shown content.instance_variable_set(:@random_image_including_private_cache, nil) content.instance_variable_set(:@pinned_public_image_cache, nil) content.instance_variable_set(:@first_public_image_cache, nil) - # Return the updated status + # Return the updated status (use new_pin_status since update_column might not refresh the object) render json: { id: @image.id, type: params[:image_type], - pinned: @image.pinned + pinned: new_pin_status } end diff --git a/app/models/basil_commission.rb b/app/models/basil_commission.rb index d50bd6ea..b3baa767 100644 --- a/app/models/basil_commission.rb +++ b/app/models/basil_commission.rb @@ -52,22 +52,7 @@ class BasilCommission < ApplicationRecord # Use acts_as_list for ordering images acts_as_list scope: [:entity_type, :entity_id] - # Add callback to ensure only one pinned image per entity - before_save :ensure_single_pinned_image, if: -> { pinned_changed? && pinned? } + # Note: Pin unpinning logic is handled in the controller to prevent database locking issues private - - # Ensures only one basil commission can be pinned per entity - def ensure_single_pinned_image - # Unpin other basil commissions for this entity - BasilCommission.where(entity_type: entity_type, entity_id: entity_id, pinned: true) - .where.not(id: id) - .update_all(pinned: false) - - # Also unpin any image uploads for this entity - if entity.respond_to?(:image_uploads) - ImageUpload.where(content_type: entity_type, content_id: entity_id, pinned: true) - .update_all(pinned: false) - end - end end diff --git a/app/models/page_data/image_upload.rb b/app/models/page_data/image_upload.rb index 5081ba03..1deada07 100644 --- a/app/models/page_data/image_upload.rb +++ b/app/models/page_data/image_upload.rb @@ -38,8 +38,7 @@ class ImageUpload < ApplicationRecord # Use acts_as_list for ordering images acts_as_list scope: [:content_type, :content_id] - # Add callback to ensure only one pinned image per content - before_save :ensure_single_pinned_image, if: -> { pinned_changed? && pinned? } + # Note: Pin unpinning logic is handled in the controller to prevent database locking issues def delete_s3_image # todo: put this in a task for faster delete response times @@ -47,18 +46,4 @@ class ImageUpload < ApplicationRecord end private - - # Ensures only one image can be pinned per content item - def ensure_single_pinned_image - # Unpin other image uploads for this content - ImageUpload.where(content_type: content_type, content_id: content_id, pinned: true) - .where.not(id: id) - .update_all(pinned: false) - - # Also unpin any basil commissions for this content - if content.respond_to?(:basil_commissions) - BasilCommission.where(entity_type: content_type, entity_id: content_id, pinned: true) - .update_all(pinned: false) - end - end end