Handle MultipleUpload file uploads with Ruby on Rails using Active Storage or direct file handling. This guide covers both standard multipart uploads and chunked uploads.
Add upload routes in config/routes.rb.
# config/routes.rb Rails.application.routes.draw do post "/api/upload", to: "uploads#create" post "/api/upload-chunk", to: "uploads#chunk" end
Add the rack-cors gem
and configure it in config/initializers/cors.rb.
# Gemfile
gem "rack-cors"
# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins "*"
resource "/api/*",
headers: %w[
Content-Type X-Upload-Id X-Chunk-Index
X-Chunk-Count X-File-Name X-File-Size
],
methods: [:post, :options]
end
end
The controller receives the file through Rails params and saves it with a unique GUID.
# app/controllers/uploads_controller.rb
class UploadsController < ApplicationController
skip_before_action :verify_authenticity_token
UPLOAD_DIR = Rails.root.join("storage", "uploads")
CHUNK_DIR = Rails.root.join("storage", "uploads", "chunks")
def create
file = params[:file]
unless file.present?
return render json: { success: false, error: "No file uploaded" }, status: :bad_request
end
file_guid = SecureRandom.uuid
ext = File.extname(file.original_filename)
dest_path = UPLOAD_DIR.join("#{file_guid}#{ext}")
FileUtils.mkdir_p(UPLOAD_DIR)
File.open(dest_path, "wb") do |f|
f.write(file.read)
end
render json: {
success: true,
fileGuid: file_guid,
fileName: file.original_filename,
fileSize: file.size
}
end
end
Each chunk arrives as a raw body with custom headers. Store chunks in a temporary directory and assemble the final file once all chunks have arrived.
# Add to UploadsController
def chunk
upload_id = request.headers["X-Upload-Id"]
chunk_index = request.headers["X-Chunk-Index"].to_i
chunk_count = request.headers["X-Chunk-Count"].to_i
file_name = request.headers["X-File-Name"] || "unknown"
file_size = request.headers["X-File-Size"].to_i
if upload_id.blank? || chunk_count.zero?
return render json: { success: false, error: "Missing headers" }, status: :bad_request
end
session_dir = CHUNK_DIR.join(upload_id)
FileUtils.mkdir_p(session_dir)
# Save this chunk from raw body
chunk_path = session_dir.join("chunk_#{chunk_index}")
File.open(chunk_path, "wb") do |f|
f.write(request.body.read)
end
# Count received chunks
received = Dir.glob(session_dir.join("chunk_*")).count
if received == chunk_count
# Assemble final file
file_guid = SecureRandom.uuid
ext = File.extname(file_name)
final_path = UPLOAD_DIR.join("#{file_guid}#{ext}")
FileUtils.mkdir_p(UPLOAD_DIR)
File.open(final_path, "wb") do |output|
chunk_count.times do |i|
cp = session_dir.join("chunk_#{i}")
output.write(File.binread(cp))
end
end
# Clean up chunks
FileUtils.rm_rf(session_dir)
return render json: {
success: true,
fileGuid: file_guid,
fileName: file_name,
fileSize: file_size
}
end
render json: { success: true, chunksReceived: received }
end
Both endpoints must return this JSON structure on successful upload completion.
{
"success": true,
"fileGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"fileName": "photo.jpg",
"fileSize": 204800
}
Initialize MultipleUpload on the client, pointing to your Rails API endpoints.
<link rel="stylesheet" href="multipleupload.css" />
<div id="uploader"></div>
<script src="multipleupload.js"></script>
<script>
var uploader = new MultipleUpload("#uploader", {
uploadUrl: "/api/upload",
chunkUploadUrl: "/api/upload-chunk",
chunkSize: 2 * 1024 * 1024, // 2 MB chunks
onFileUploaded: function (file, response) {
console.log("Uploaded:", response.fileName, response.fileGuid);
}
});
</script>