Exportar archivos a buckets de Supabase en Ruby
Supabase ofrece una solución de almacenamiento en la nube mediante su sistema de buckets. Puedes interactuar con Supabase Storage usando las bibliotecas HTTP integradas de Ruby. En esta guía mostramos cómo exportar archivos a buckets de almacenamiento de Supabase a través de la REST API.
Requisitos previos
Antes de empezar, asegúrate de tener:
- Ruby 2.7 o superior instalado
- Una cuenta de Supabase y un proyecto con el almacenamiento habilitado
- Conocimientos básicos de programación en Ruby
- La URL de tu proyecto de Supabase, la clave publicable o la clave anon heredada del proyecto, y el JWT de acceso de un usuario con sesión iniciada
- Políticas de almacenamiento que concedan a ese usuario los permisos requeridos de inserción, selección y eliminación
Configurar tu entorno
Crea un nuevo proyecto de Ruby y configura las dependencias necesarias. Aunque algunas bibliotecas
como net/http y json vienen con Ruby, las incluimos por claridad y añadimos mime-types para
determinar los tipos de contenido de los archivos:
mkdir supabase-storage-demo
cd supabase-storage-demo
bundle init
Añade las siguientes gemas a tu Gemfile:
source 'https://rubygems.org'
# Net/http and JSON are part of Ruby's standard library
gem 'mime-types'
Instala las dependencias:
bundle install
Configurar un cliente de la REST API en Ruby
Construiremos un pequeño cliente con Net::HTTP de Ruby para interactuar con la REST API de Supabase Storage. La clave del proyecto identifica el proyecto; el JWT de usuario, que es independiente, determina el acceso del usuario según las políticas de Storage. Guarda ambos en variables de entorno y renueva la sesión del usuario cuando expire.
require 'net/http'
require 'json'
require 'mime/types'
class SupabaseStorage
def initialize(project_url, api_key, access_token)
@project_url = project_url.chomp('/')
@api_key = api_key
@access_token = access_token
@storage_url = "#{@project_url}/storage/v1"
end
private
def headers
{
'Authorization' => "Bearer #{@access_token}",
'apikey' => @api_key
}
end
def make_request(uri, request)
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
response = http.request(request)
unless response.is_a?(Net::HTTPSuccess)
raise "Storage request failed (HTTP #{response.code})"
end
response
end
end
def encode_segment(value)
URI.encode_www_form_component(value).gsub('+', '%20')
end
def encode_path(path)
path.split('/').map { |segment| encode_segment(segment) }.join('/')
end
public
# Upload a file to a specific bucket and destination path
def upload_file(bucket_name, file_path, destination_path)
uri = URI("#{@storage_url}/object/#{encode_segment(bucket_name)}/#{encode_path(destination_path)}")
request = Net::HTTP::Post.new(uri)
headers.each { |key, value| request[key] = value }
# Open the file in binary mode and set up the request stream
File.open(file_path, 'rb') do |file|
request.body_stream = file
request['Content-Type'] = MIME::Types.type_for(file_path).first&.to_s || 'application/octet-stream'
request['Content-Length'] = File.size(file_path).to_s
response = make_request(uri, request)
JSON.parse(response.body)
end
rescue StandardError => e
{ 'error' => e.message }
end
# List one page of immediate children of a prefix
def list_files(bucket_name, path = '', limit: 100, offset: 0)
uri = URI("#{@storage_url}/object/list/#{encode_segment(bucket_name)}")
request = Net::HTTP::Post.new(uri)
headers.each { |key, value| request[key] = value }
request['Content-Type'] = 'application/json'
request.body = JSON.generate(prefix: path, limit: limit, offset: offset,
sortBy: { column: 'name', order: 'asc' })
response = make_request(uri, request)
JSON.parse(response.body)
rescue StandardError => e
{ 'error' => e.message }
end
# Return true only when the API confirms deletion of the requested file
def delete_file(bucket_name, file_path)
uri = URI("#{@storage_url}/object/#{encode_segment(bucket_name)}")
request = Net::HTTP::Delete.new(uri)
headers.each { |key, value| request[key] = value }
request['Content-Type'] = 'application/json'
request.body = JSON.generate(prefixes: [file_path])
response = make_request(uri, request)
deleted = JSON.parse(response.body)
deleted.is_a?(Array) && deleted.any? { |object| object.is_a?(Hash) && object['name'] == file_path }
rescue StandardError
false
end
end
Ejemplos de uso
Un resultado de eliminación vacío no es una confirmación: puede que el archivo no exista o que no sea accesible según las políticas de almacenamiento de quien hace la llamada. El método auxiliar informa de éxito únicamente cuando la respuesta incluye el archivo solicitado.
A continuación se muestra un ejemplo de cómo usar la clase SupabaseStorage para subir, listar y eliminar
archivos de un bucket de Supabase.
# Initialize the client with your environment variables
storage = SupabaseStorage.new(
ENV.fetch('SUPABASE_URL'),
ENV.fetch('SUPABASE_PROJECT_KEY'),
ENV.fetch('SUPABASE_ACCESS_TOKEN')
)
# Upload a file
result = storage.upload_file(
'my-bucket',
'path/to/local/image.jpg',
'uploads/image.jpg'
)
if result['error']
puts "Upload failed: #{result['error']}"
else
puts 'File uploaded successfully'
end
# List files in a bucket
files = storage.list_files('my-bucket', 'uploads/')
if files.is_a?(Array)
files.each do |file|
puts "File: #{file['name']}, Size: #{file.dig('metadata', 'size')}"
end
else
puts "Error listing files: #{files['error']}"
end
# Delete a file
if storage.delete_file('my-bucket', 'uploads/image.jpg')
puts 'File deleted successfully'
else
puts 'Failed to delete file'
end
list_files devuelve una página de elementos hijos directos, incluidas las filas de carpetas. Cuando
necesites más de 100 entradas, incrementa offset según el tamaño de página hasta que se devuelva
una página más corta. Este ejemplo no lista subcarpetas de forma recursiva. Las subidas crean
objetos nuevos; las rutas existentes requieren un upsert intencionado y los permisos de
actualización correspondientes.
Manejo de errores y validación de archivos
Es importante validar los archivos antes de subirlos para evitar errores y garantizar la seguridad. El siguiente validador comprueba que se trate de archivos regulares, un límite de tamaño específico de la aplicación y las extensiones permitidas. Las consultas de extensión y del registro MIME no inspeccionan el contenido ni establecen que sea seguro.
class FileValidator
MAX_FILE_SIZE = 50 * 1024 * 1024 # 50MB
ALLOWED_TYPES = %w[.jpg .jpeg .png .pdf .doc .docx]
def self.validate!(file_path)
raise 'File not found' unless File.file?(file_path)
size = File.size(file_path)
raise 'File exceeds maximum size' if size > MAX_FILE_SIZE
extension = File.extname(file_path).downcase
raise 'Invalid file type' unless ALLOWED_TYPES.include?(extension)
true
end
end
# Example usage:
begin
FileValidator.validate!('path/to/file.jpg')
result = storage.upload_file('my-bucket', 'path/to/file.jpg', 'uploads/file.jpg')
raise result['error'] if result['error']
rescue StandardError => e
puts "Validation failed: #{e.message}"
end
Buenas prácticas
Generar nombres de archivo únicos
Crear nombres de archivo únicos ayuda a evitar colisiones en el almacenamiento. Por ejemplo:
require 'securerandom'
def generate_unique_filename(original_filename)
extension = File.extname(original_filename)
basename = File.basename(original_filename, extension)
timestamp = Time.now.strftime('%Y%m%d-%H%M%S')
"#{basename}-#{timestamp}-#{SecureRandom.hex(4)}#{extension}"
end
Organizar los archivos por fecha
Almacenar los archivos en directorios basados en la fecha puede simplificar la gestión:
def generate_storage_path(filename)
date = Time.now
"uploads/#{date.year}/#{date.month}/#{filename}"
end
Manejo de archivos grandes
El cliente anterior transmite una subida estándar, pero no puede reanudar una petición interrumpida.
Supabase admite subidas reanudables con el protocolo tus
en /storage/v1/upload/resumable. Usa un cliente compatible con el protocolo tus en lugar de subir
fragmentos independientes al endpoint estándar de objetos. Revisa los límites de tamaño de archivo
configurados en el proyecto antes de transferir archivos grandes.
Casos de uso comunes
- Subidas de avatares de usuario
- Sistemas de almacenamiento de documentos
- Gestión de archivos multimedia
- Soluciones de copia de seguridad
- Sistemas de entrega de contenido
Consideraciones de seguridad
- Valida los archivos antes de subirlos para evitar problemas.
- Guarda la URL de tu proyecto de Supabase y la clave de API en variables de entorno.
- Implementa políticas CORS adecuadas si expones tu API.
- Establece permisos adecuados en los buckets.
- Usa URL seguras para el contenido sensible.
Conclusión
Usar Net::HTTP de Ruby y la REST API de Supabase ofrece un método para gestionar el almacenamiento de archivos. Con la validación, el manejo de errores y las medidas de seguridad adecuadas, puedes exportar archivos de forma eficiente a los buckets de almacenamiento de Supabase en tus aplicaciones Ruby.
Para necesidades adicionales de procesamiento multimedia, considera explorar las funciones de Transloadit para mejorar aún más tu flujo de trabajo.
