I’m using ActiveStorage::DirectUploadsController to get an id and upload url Then creating a record via API
it’s a video_edit
which has_one_attached :video
my Rails 7 code was the following:
def create
@video_edit = VideoEdit.new(video_edit_params)
if @video_edit.save
render :show, status: :created
else
render json: {errors: @video_edit.errors}, status: :unprocessable_content
end
rescue ActiveSupport::MessageVerifier::InvalidSignature
# Handle invalid blob ID
render json: { errors: ["Invalid attachment signature"] }, status: :unprocessable_entity
end
in Rails 8, this fails with
ActiveRecord::ActiveRecordError: Cannot touch on a new or destroyed record object. Consider using persisted?, new_record?, or destroyed? before touching.
the problem seems to be with attach updating metadata and triggering an error:
If I break it down (thank you chatGPT)
def create
logger.info "🔍 Starting VideoEditsController#create"
# Create the video edit without the attachment
@video_edit = VideoEdit.new(video_edit_params.except(:video))
logger.info "🔍 Created VideoEdit instance in memory, before save"
# Save the record first
if @video_edit.save
logger.info "🔍 VideoEdit saved successfully with ID: #{@video_edit.id}"
# Now try to attach the video
if video_edit_params[:video].present?
logger.info "🔍 Video parameter is present, attempting to find blob"
begin
# Find the blob first
blob = ActiveStorage::Blob.find_signed(video_edit_params[:video])
logger.info "🔍 Found blob with ID: #{blob.id}, key: #{blob.key}, content_type: #{blob.content_type}"
# Check if record is properly persisted
logger.info "🔍 VideoEdit persisted?: #{@video_edit.persisted?}, new_record?: #{@video_edit.new_record?}, destroyed?: #{@video_edit.destroyed?}"
# Try to attach with more detailed logging
logger.info "🔍 About to attach blob to VideoEdit"
attachment = nil
# Try with manual SQL transaction
ActiveRecord::Base.transaction do
logger.info "🔍 Inside transaction - before attachment"
# Use the normal attach method to see where it fails
logger.info "🔍 About to call attach method"
@video_edit.video.attach(blob)
logger.info "🔍 Attach method completed successfully"
logger.info "🔍 After attachment save attempt"
end
logger.info "🔍 After transaction - attachment creation completed"
render :show, status: :created
rescue ActiveRecord::ActiveRecordError => e
logger.error "🔍 ActiveRecord error during attachment: #{e.class.name}: #{e.message}"
logger.error "🔍 Error backtrace: #{e.backtrace[0..5].join("\n")}"
@video_edit.destroy
render json: {errors: {base: [e.message]}}, status: :unprocessable_content
rescue StandardError => e
logger.error "🔍 General error during attachment: #{e.class.name}: #{e.message}"
logger.error "🔍 Error backtrace: #{e.backtrace[0..5].join("\n")}"
@video_edit.destroy
render json: {errors: {base: [e.message]}}, status: :unprocessable_content
end
else
logger.info "🔍 No video parameter present, skipping attachment"
render :show, status: :created
end
else
logger.error "🔍 Failed to save VideoEdit: #{@video_edit.errors.full_messages.join(', ')}"
render json: {errors: @video_edit.errors}, status: :unprocessable_content
end
rescue ActiveSupport::MessageVerifier::InvalidSignature => e
logger.error "🔍 Invalid signature error: #{e.message}"
# Handle invalid blob ID
render json: { errors: ["Invalid attachment signature"] }, status: :unprocessable_entity
rescue StandardError => e
logger.error "🔍 Unexpected error in create: #{e.class.name}: #{e.message}"
logger.error "🔍 Error backtrace: #{e.backtrace[0..5].join("\n")}"
render json: { errors: ["An unexpected error occurred"] }, status: :internal_server_error
end
this fails at the attach:
web | 🔍 About to call attach method
web | TRANSACTION (2.4ms) BEGIN /*action='create',application='JumpstartApp',controller='video_edits'*/
web | ActiveStorage::Blob Load (7.7ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" INNER JOIN "active_storage_attachments" ON "active_storage_blobs"."id" = "active_storage_attachments"."blob_id" WHERE "active_storage_attachments"."record_id" = 594 AND "active_storage_attachments"."record_type" = 'VideoEdit' AND "active_storage_attachments"."name" = 'video' LIMIT 1 /*action='create',application='JumpstartApp',controller='video_edits'*/
web | ActiveStorage::Attachment Load (2.4ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = 594 AND "active_storage_attachments"."record_type" = 'VideoEdit' AND "active_storage_attachments"."name" = 'video' LIMIT 1 /*action='create',application='JumpstartApp',controller='video_edits'*/
web | ActiveStorage::Blob Update (2.5ms) UPDATE "active_storage_blobs" SET "metadata" = '{"identified":true}' WHERE "active_storage_blobs"."id" = 33154 /*action='create',application='JumpstartApp',controller='video_edits'*/
web | ActiveStorage::Attachment Load (2.8ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."blob_id" = 33154 /*action='create',application='JumpstartApp',controller='video_edits'*/
web | TRANSACTION (1.4ms) ROLLBACK /*action='create',application='JumpstartApp',controller='video_edits'*/
web | 🔍 ActiveRecord error during attachment: ActiveRecord::ActiveRecordError: Cannot touch on a new or destroyed record object. Consider using persisted?, new_record?, or destroyed? before touching.
web | 🔍 Error backtrace: /Users/rob/.rvm/gems/ruby-3.4.2/gems/activerecord-8.0.2/lib/active_record/persistence.rb:962:in 'ActiveRecord::Persistence#_raise_record_not_touched_error'
web | /Users/rob/.rvm/gems/ruby-3.4.2/gems/activerecord-8.0.2/lib/active_record/persistence.rb:794:in 'ActiveRecord::Persistence#touch'
web | /Users/rob/.rvm/gems/ruby-3.4.2/gems/activerecord-8.0.2/lib/active_record/callbacks.rb:432:in 'block in ActiveRecord::Callbacks#touch'
web | /Users/rob/.rvm/gems/ruby-3.4.2/gems/activesupport-8.0.2/lib/active_support/callbacks.rb:100:in 'ActiveSupport::Callbacks#run_callbacks'
web | /Users/rob/.rvm/gems/ruby-3.4.2/gems/activesupport-8.0.2/lib/active_support/callbacks.rb:913:in 'ActiveRecord::Base#_run_touch_callbacks'
web | /Users/rob/.rvm/gems/ruby-3.4.2/gems/activerecord-8.0.2/lib/active_record/callbacks.rb:432:in 'ActiveRecord::Callbacks#touch'
web | TRANSACTION (1.3ms) BEGIN /*action='create',application='JumpstartApp',controller='video_edits'*/
web | VideoEdit Destroy (3.5ms) DELETE FROM "video_edits" WHERE "video_edits"."id" = 594 /*action='create',application='JumpstartApp',controller='video_edits'*/
web | TRANSACTION (1.4ms) COMMIT /*action='create',application='JumpstartApp',controller='video_edits'*/
Disabling touching gets me out of the hole
def create
@video_edit = VideoEdit.new(video_edit_params)
# Disable touch operations for the entire process
ActiveRecord::Base.no_touching do
if @video_edit.save
render :show, status: :created
else
render json: {errors: @video_edit.errors}, status: :unprocessable_content
end
end
rescue ActiveSupport::MessageVerifier::InvalidSignature
# Handle invalid blob ID
render json: { errors: ["Invalid attachment signature"] }, status: :unprocessable_entity
end
not sure how good an idea this is though!
My video_edit record doesn’t have any explicit callbacks