Ostatnio zsynchronizowaliśmy bazę danych z plikami zdalnymi. Dziś zajmiemy się wysyłaniem i zapisywaniem plików lokalnych na zewnętrzym serwerze oraz w bazie danych. Czynności, które musimy wykonać to:
- połaczenie z serwerem, zapisanie na nim wybranego pliku,
- zapisanie informacji n.t. nowego pliku w bazie danych.
Tak jak poprzednio, akcje te podzielimy na osobne serwisy. Rozpoczniemy od połączenia z serwerem i tak samo jak w poprzednich przykładach serwis będzie dziedziczył z ApplicationService
w celu ułatwienia nam połączenia.
require 'net/ftp'
module Media
class UploadFile < ApplicationService
def call(file)
Net::FTP.open(host) do |ftp|
ftp.login(username, password)
ftp.putbinaryfile(file.tempfile, file.original_filename)
rescue Net::FTPPermError => e
puts e
failure('Connection refused')
end
end
end
end
Do zapisania pliku, po połączeniu z serwerem, wystarczy tylko jedna linijka kodu! A dokładniej metoda putbinaryfile
. W tym przypadku korzystamy z file.tempfile
oraz file.original_filename
, ponieważ zakładamy, że plik, który został przekazany do funkcji call
został wysłany do kontrolera przez formularz. Tzn. użytkownik wszedł na stronę, w formularzu wybrał plik z komputera, który chciał wysłać i go zatwierdził. Do przykładowego kodu kontrolera dojdziemy za chwilę, wróćmy jeszcze do zapisywania pliku.
Po udanym zapisie na zewnętrznym serwerze, musimy zachować dane n.t. pliku w bazie danych (aby nie musieć po każdym uploadzie wykonywać synchronizacji). Posłuży nam do tego napisany ostatnio serwis Media::Create
. Proponuję, by akcje zapisu na serwerze oraz rejestrowania w bazie danych “opakować” w jeszcze jeden serwis, który będzie wywoływany przez kontroler.
module Media
class UploadAndSave
def call(file)
Media::UploadFile.new.call(file)
Media::Create.call(media_params: media_params(file))
end
private
def media_params(file)
{
name: file.original_filename,
bytesize: file.size,
mimetype: file.content_type
}
end
end
end
W kontrolerze mielibyśmy przykładowo taki kod:
class MediaController < ApplicationController
...
def perform_uploading
Media::UploadAndSave.new.call(media_params[:file])
end
...
def media_params
params.require(:medium).permit(:file)
end
end
W wyniku wysłania formularza użytkownik równocześnie wysłałby plik na serwer FTP oraz zapisał dane na jego temat w bazie danych.
Powyższy kod jest maksymalnie uproszczony, tak by pokazać jedynie funkcjonalność zapisu i nie rozpraszać innymi aspektami. W kodzie produkcyjnym na pewno powinno pojawić się wyłapywanie wyjatków, komunikowanie użytkownika o niepowodzeniu, itd…