Exporter des fichiers vers des buckets Supabase en Ruby
Pour exporter un fichier vers Supabase depuis Ruby, envoyez ses octets à l’API REST de Storage, puis téléchargez l’objet sélectionné et comparez-le au fichier d’entrée. Cet exemple se connecte en tant qu’utilisateur standard, refuse les chemins d’objet existants et conserve le fichier téléversé et vérifié dans le bucket. La suppression fait l’objet d’une commande distincte.
Prérequis
- Ruby 4.0.7, Bundler, ainsi qu’un compilateur et
makepour installer le gem JSON. La page de téléchargement de Ruby répertorie les versions actuelles et les options d’installation. La configuration Linux est testée avec Bundler 4.0.20, ainsi qu’avec Ruby 3.3.12 et Bundler 2.5.22. - Bash pour les commandes shell ci-dessous. Partez d’un répertoire accessible en écriture, sans
sous-répertoire
supabase-storage-demoexistant et sans redéfinition deBUNDLE_*,GEM_HOMEouGEM_PATHdans les variables d’environnement exportées. - Un projet Supabase, un utilisateur Auth confirmé utilisant une adresse e-mail et un mot de passe,
ainsi qu’un bucket Storage privé dédié nommé
ruby-exports. Suivez le guide officiel de création d’un bucket pour le créer ; autorisez le type de contenuapplication/octet-streamet les fichiers jusqu’à 1 MiB. - L’URL et la clé du projet, ainsi que l’adresse e-mail et le mot de passe de l’utilisateur, fournis sous forme de variables d’environnement.
Pour les projets hébergés, utilisez une clé de projet publiable.
Elle doit figurer dans l’en-tête apikey ; le JWT de l’utilisateur connecté
doit figurer dans Authorization: Bearer.
Supabase annonce la dépréciation des clés historiques anon et service_role d’ici fin 2026.
Les projets existants peuvent utiliser les deux systèmes de clés pendant la migration.
La vérification native de cet exemple utilise un projet local autohébergé avec une clé historique
anon, et non un projet hébergé avec une clé publiable.
Le provisionnement des clés des projets hébergés et TLS n’ont pas été testés ici.
Exécutez le SQL suivant en tant qu’administrateur du projet pour ce bucket dédié. Ces
politiques de Storage autorisent les téléversements sous l’UUID
de l’utilisateur et lui permettent de lire et de supprimer ses propres objets.
Le téléversement nécessite INSERT ; la relecture nécessite
SELECT. La suppression nécessite également
SELECT et DELETE, comme indiqué dans la
référence de Storage sur la suppression.
Vérifiez d’abord s’il existe des politiques plus larges : les politiques permissives se cumulent,
ces règles ne restreignent donc pas les accès déjà accordés par une autre politique.
create policy ruby_exports_insert on storage.objects
for insert to authenticated with check (
bucket_id = 'ruby-exports'
and (storage.foldername(name))[1] = (select auth.uid())::text
);
create policy ruby_exports_select on storage.objects
for select to authenticated using (
bucket_id = 'ruby-exports'
and (storage.foldername(name))[1] = (select auth.uid())::text
and owner_id = (select auth.uid())::text
);
create policy ruby_exports_delete on storage.objects
for delete to authenticated using (
bucket_id = 'ruby-exports'
and (storage.foldername(name))[1] = (select auth.uid())::text
and owner_id = (select auth.uid())::text
);
Fournissez SUPABASE_URL, SUPABASE_PROJECT_KEY,
SUPABASE_EMAIL et SUPABASE_PASSWORD via votre gestionnaire de secrets
ou l’environnement protégé du processus. Utilisez l’URL racine du projet, par exemple
https://PROJECT_REF.supabase.co. Ne placez pas de mots de passe, de jetons utilisateur ou de clés
d’administration dans le code source ou l’historique du shell. Le programme obtient une nouvelle
session utilisateur via le flux par mot de passe d’Auth à chaque
exécution ; il n’a besoin ni d’une clé secrète ni d’une clé service_role.
Configurer votre environnement
Collez ce bloc dans Bash. Le sous-shell préserve votre répertoire courant et vos options de shell, y compris si la configuration échoue. Tout répertoire de projet existant est conservé.
(
command -v ruby >/dev/null &&
command -v bundle >/dev/null &&
mkdir supabase-storage-demo &&
cd supabase-storage-demo &&
bundle init &&
bundle config set --local path vendor/bundle
)
Remplacez le fichier supabase-storage-demo/Gemfile nouvellement créé par :
source 'https://rubygems.org'
gem 'json', '3.0.2'
gem 'net-http', '0.10.0'
Les versions épinglées correspondent aux versions actuelles de JSON
et de Net::HTTP, vérifiées le 12 octobre 2026.
Installez-les dans le répertoire vendor/bundle de ce projet, sans installer de gems
à l’échelle du système :
(cd supabase-storage-demo && bundle install)
Si l’installation échoue, résolvez le problème signalé lié aux prérequis réseau ou au compilateur, puis réexécutez ce bloc d’installation dans le même projet. Conservez son Gemfile et sa configuration Bundler locale ; ne répétez pas le bloc de création du répertoire et ne supprimez pas un projet existant pour réessayer.
Configurer un client d’API REST en Ruby
Enregistrez ce programme complet sous le nom supabase-storage-demo/storage.rb. Il traite un fichier
à la fois, y compris les fichiers vides et binaires, avec une limite applicative de 1 MiB.
Les noms de fichiers locaux doivent contenir des octets UTF-8 valides, sans caractères de contrôle ;
la validation ne dépend pas des paramètres régionaux du shell. Les clés d’objet utilisent des
lettres ASCII, des chiffres, des espaces, _,
-, . et /,
sans segment de dossier vide ni segment . ou ...
Choisissez une clé prise en charge indépendamment du nom de fichier local : la
version de Storage testée refuse les caractères Unicode et les signes pour cent dans les clés.
Le programme encode chaque segment de clé pris en charge pour l’URL.
require 'digest'
require 'json'
require 'net/http'
require 'uri'
class StorageError < StandardError
end
class SupabaseStorage
MAX_BYTES = 1024 * 1024
attr_reader :user_id
def initialize
@url = ENV.fetch('SUPABASE_URL').chomp('/')
origin = URI(@url)
secure = origin.is_a?(URI::HTTPS)
local = origin.is_a?(URI::HTTP) && %w[localhost 127.0.0.1 [::1]].include?(origin.host)
unless (secure || local) && origin.userinfo.nil? && origin.query.nil? &&
origin.fragment.nil? && origin.path.empty?
raise StorageError, 'Use an HTTPS project root URL, or HTTP on localhost for local testing'
end
@project_key = ENV.fetch('SUPABASE_PROJECT_KEY')
login = Net::HTTP::Post.new('/auth/v1/token?grant_type=password')
login['Content-Type'] = 'application/json'
login.body = JSON.generate(email: ENV.fetch('SUPABASE_EMAIL'),
password: ENV.fetch('SUPABASE_PASSWORD'))
session = JSON.parse(request(login).body)
@token = session.fetch('access_token')
user = JSON.parse(request(Net::HTTP::Get.new('/auth/v1/user')).body)
@user_id = user.fetch('id')
end
def upload(bucket, key, path)
bytes = input_bytes(path)
upload = Net::HTTP::Post.new(object_path(bucket, key))
upload['Content-Type'] = 'application/octet-stream'
upload['x-upsert'] = 'false'
upload.body = bytes
request(upload)
verify_bytes(bucket, key, bytes)
end
def verify(bucket, key, path)
verify_bytes(bucket, key, input_bytes(path))
end
def delete(bucket, key)
key = text(key)
object_path(bucket, key)
deletion = Net::HTTP::Delete.new("/storage/v1/object/#{segment(bucket)}")
deletion['Content-Type'] = 'application/json'
deletion.body = JSON.generate(prefixes: [key])
deleted = JSON.parse(request(deletion).body)
unless deleted.is_a?(Array) && deleted.any? { |object| object['name'] == key }
raise StorageError, 'Deletion was not confirmed; the object may be missing or inaccessible'
end
puts 'Deletion confirmed'
end
private
def request(message)
message['apikey'] = @project_key
message['Authorization'] = "Bearer #{@token}" if @token
uri = URI(@url)
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https',
open_timeout: 10, read_timeout: 30, write_timeout: 30) do |http|
http.max_retries = 0
response = http.request(message)
unless response.is_a?(Net::HTTPSuccess)
raise StorageError, "Request failed (HTTP #{response.code})"
end
response
end
end
def text(value)
value = value.dup.force_encoding(Encoding::UTF_8)
unless value.valid_encoding? && !value.match?(/[\x00-\x1f\x7f]/)
raise StorageError, 'Paths must be valid UTF-8 without control characters'
end
value
end
def segment(value)
URI.encode_www_form_component(text(value)).gsub('+', '%20')
end
def object_path(bucket, key)
raise StorageError, 'Invalid bucket name' unless bucket.match?(/\A[a-z0-9][a-z0-9-]*\z/)
key = text(key)
unless key.match?(/\A[A-Za-z0-9_.\/ -]+\z/)
raise StorageError, 'Use an ASCII object key with letters, digits, spaces, _, -, ., and /'
end
parts = key.split('/', -1)
if parts.any? { |part| ['', '.', '..'].include?(part) } || key.include?('\\')
raise StorageError, 'Use a nonempty object key with ordinary folder segments'
end
"/storage/v1/object/#{segment(bucket)}/#{parts.map { |part| segment(part) }.join('/')}"
end
def input_bytes(path)
path = text(path)
raise StorageError, 'Input must be a regular file' unless File.file?(path)
bytes = File.binread(path, MAX_BYTES + 1) || ''.b
raise StorageError, 'Input exceeds the 1 MiB application limit' if bytes.bytesize > MAX_BYTES
bytes
end
def verify_bytes(bucket, key, expected)
download = Net::HTTP::Get.new(object_path(bucket, key))
download['Accept-Encoding'] = 'identity'
actual = request(download).body || ''.b
unless actual.b == expected.b
raise StorageError, 'Stored bytes differ from the input; inspect the object before retrying'
end
puts "Verified #{expected.bytesize} bytes; SHA-256 #{Digest::SHA256.hexdigest(actual)}"
end
end
begin
command, bucket, key, path = ARGV
valid = (command == 'whoami' && ARGV.length == 1) ||
(%w[upload verify].include?(command) && ARGV.length == 4) ||
(command == 'delete' && ARGV.length == 3)
unless valid
raise StorageError, 'Usage: storage.rb whoami | upload/verify BUCKET KEY FILE | delete BUCKET KEY'
end
storage = SupabaseStorage.new
case command
when 'whoami' then puts storage.user_id
when 'upload' then storage.upload(bucket, key, path)
when 'verify' then storage.verify(bucket, key, path)
when 'delete' then storage.delete(bucket, key)
end
rescue StandardError => error
# Only our deliberate messages are safe to print; library errors can contain private data.
safe = error.instance_of?(StorageError) ? error.message : 'Check configuration, input, and connectivity'
warn "Storage command failed: #{safe}"
exit 1
end
Le téléversement envoie des octets bruts, et non du JSON ou des champs multipart. La relecture utilise le point de terminaison d’objet authentifié, avec les permissions du même utilisateur. Chaque opération réseau a un délai d’expiration de connexion de 10 secondes et des délais d’inactivité en lecture et en écriture de 30 secondes, mais pas de délai global pour le transfert. Les nouvelles tentatives automatiques sont désactivées : une réponse perdue peut laisser un objet stocké même si la commande signale un échec.
Exemples d’utilisation
Vérifiez les informations d’accès fournies et obtenez l’UUID de l’utilisateur :
(cd supabase-storage-demo && bundle exec ruby storage.rb whoami)
Placez un fichier nommé report.bin dans le projet. Remplacez
USER_UUID ci-dessous par l’UUID affiché ; il doit être le premier dossier
de la clé d’objet pour respecter les politiques ci-dessus.
(cd supabase-storage-demo &&
bundle exec ruby storage.rb upload ruby-exports 'USER_UUID/exports/report.bin' ./report.bin)
Une commande réussie affiche le nombre d’octets et l’empreinte SHA-256 de l’objet téléchargé, puis se termine avec un code de sortie zéro. L’objet reste à la clé sélectionnée. Pour le vérifier à nouveau sans le téléverser ni le remplacer :
(cd supabase-storage-demo &&
bundle exec ruby storage.rb verify ruby-exports 'USER_UUID/exports/report.bin' ./report.bin)
Gardez le fichier source inchangé pendant la comparaison. Placez entre guillemets les arguments
de chemin contenant des espaces et utilisez ./ pour un nom de fichier
local commençant par un trait d’union. Cet exemple ne répertorie pas les dossiers et ne crée pas
de liens de téléchargement publics.
Gestion des erreurs et validation des fichiers
Tout téléversement refusé, toute relecture échouée ou toute suppression non confirmée entraîne un code de sortie non nul. Les clés existantes sont refusées par les téléversements standard ; le programme ne demande pas d’upsert et ne remplace pas les octets précédents. Exécutez les commandes séquentiellement.
Un échec HTTP ne permet pas à lui seul d’en identifier la cause. Vérifiez l’URL du projet, les informations d’accès de l’utilisateur, les restrictions du bucket, le préfixe UUID de la clé et les politiques applicables. Un objet absent ou inaccessible peut donner la même réponse. Le programme omet de son diagnostic les corps bruts des réponses du serveur, les informations d’accès et les erreurs des bibliothèques.
La limite de 1 MiB inclut cette valeur exacte et autorise zéro octet. Il s’agit de la limite de cet
exemple ; votre projet et votre bucket peuvent imposer des restrictions supplémentaires.
L’en-tête MIME déclare un contenu binaire générique ; il ne reconnaît pas les formats de fichiers
et ne permet pas d’établir l’innocuité du contenu. Si le téléversement réussit mais que la vérification
échoue, inspectez cette clé avec la commande verify avant de réessayer :
le téléversement n’est pas annulé et le fichier n’est pas supprimé silencieusement.
Bonnes pratiques
Pour les exports récurrents de rapports, choisissez une nouvelle clé pour chaque rapport,
par exemple USER_UUID/exports/2026/10/report-12.bin. Un dossier par date permet d’organiser les exports ;
un identifiant de tâche supplémentaire distingue plusieurs rapports du même jour.
Conservez la clé avec l’enregistrement de la tâche afin que les vérifications et suppressions
ultérieures ciblent le même objet.
Traiter les fichiers volumineux
Cet exemple pour petits fichiers met en mémoire tampon à la fois le fichier d’entrée et les données relues, et ne peut pas reprendre les téléversements interrompus. Pour les transferts plus volumineux, utilisez le protocole de téléversement avec reprise de Supabase avec un client tus compatible. Des blocs indépendants envoyés au point de terminaison d’objet standard ne mettent pas en œuvre ce protocole. Les transferts avec reprise ne font pas partie du périmètre vérifié de cet exemple.
Supprimer explicitement un export
Lorsque le fichier exporté n’est plus nécessaire, supprimez uniquement sa clé sélectionnée :
(cd supabase-storage-demo &&
bundle exec ruby storage.rb delete ruby-exports 'USER_UUID/exports/report.bin')
Pour que l’opération soit considérée comme réussie, la réponse de suppression doit identifier cet objet. Un tableau vide peut signifier qu’il est absent ou masqué par les politiques ; la commande signale donc un échec dans ce cas. La suppression d’un export ne supprime ni son bucket, ni les autres objets, ni les utilisateurs, ni les politiques.
