Ruby on Rails
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.
Routes
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
CORS Configuration
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
Multipart Form Upload
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 Chunked Upload
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 Expected JSON Response
Both endpoints must return this JSON structure on successful upload completion.
{
"success": true,
"fileGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"fileName": "photo.jpg",
"fileSize": 204800
} Client-Side JavaScript
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>