Wgrywanie plików z ActiveStorage
Rails 5.2 wreszcie umieszcza wgrywanie plików we frameworku z ActiveStorage. Przegląd tego, jak działa — bloby, załączniki, backendy usług, warianty i bezpośrednie uploady — oraz uczciwe porównanie z CarrierWave i Paperclip.
Latami wgrywanie plików w Rails oznaczało sięgnięcie po gem — CarrierWave albo Paperclip — i okablowanie własnego składowania, przetwarzania i konwencji. Działały, ale każdy projekt wynajdywał to samo ustawienie na nowo, a dwa wiodące gemy miały realne tarcie (Paperclip zmierzał ku emeryturze, a oba niosły nagromadzoną złożoność). Rails 5.2 to zmienia, wnosząc wgrywanie plików do frameworka przez ActiveStorage. Użyłem wszystkich trzech; oto jak ActiveStorage działa i jak się porównuje.
Model: bloby i załączniki
Projekt ActiveStorage jest czystszy niż oparte na kolumnach podejście starszych gemów. Zamiast
dodawać kolumny avatar_file_name, avatar_content_type itd. do twojego modelu, używa dwóch
własnych tabel: blob (metadane o wgranym pliku — nazwa, typ zawartości, rozmiar w bajtach,
suma kontrolna i gdzie żyje w składowaniu) oraz attachment (złączenie łączące blob z jednym z
twoich modeli). Deklarujesz załączniki deklaratywnie:
class User < ApplicationRecord
has_one_attached :avatar
end
class Product < ApplicationRecord
has_many_attached :images
end
user.avatar.attach(params[:avatar]) # załącz wgrany plik
user.avatar.attached? # => true
url_for(user.avatar) # URL do serwowania go
has_one_attached i has_many_attached czytają się dokładnie jak has_one/has_many, i o to
chodzi — uploady są teraz pierwszoklasową koncepcją asocjacji, a nie stertą kolumn i konwencji
gemu. Tabela twojego modelu zostaje czysta; metadane pliku żyją w tabelach ActiveStorage.
Backendy usług: lokalny, S3 i spółka
To, gdzie pliki są przechowywane, konfiguruje się osobno od kodu, przez usługę. Rails dostarcza adaptery dla dysku lokalnego, Amazon S3, Google Cloud Storage i Azure, wybierane na środowisko:
# config/storage.yml
local:
service: Disk
root: <%= Rails.root.join("storage") %>
amazon:
service: S3
bucket: my-app-uploads
region: eu-west-1
# config/environments/production.rb
config.active_storage.service = :amazon
Używaj dysku lokalnego w developmencie i S3 na produkcji, zmieniając jedną linię — twój kod
aplikacji (user.avatar.attach(...)) nie zmienia się wcale. To rozdzielenie jest dokładnie
właściwe: gdzie pliki żyją, to sprawa środowiska, nie aplikacji, i ucieleśnia tę samą lekcję
„przechowuj pliki w object storage, nie na serwerze aplikacji” z pracy nad potokiem obrazów,
teraz wbudowaną we framework.
Warianty: transformacje obrazów na żądanie
Dla obrazów ActiveStorage generuje warianty — przekształcone wersje (przeskalowane,
przycięte) — przez ImageMagick (via mini_magick/image_processing) i robi to leniwie:
wariant jest generowany przy pierwszym żądaniu, a potem cache’owany:
<%= image_tag user.avatar.variant(resize_to_limit: [200, 200]) %>
To model on-the-fly z dyskusji o potoku obrazów, wbudowany: prosisz o rozmiar, ActiveStorage go produkuje i cache’uje. Jest wygodny i usuwa sporo ręcznej deklaracji wersji, której wymagały starsze gemy. (Dla dużego katalogu z ustalonym zbiorem rozmiarów i ciężkim ruchem możesz wciąż chcieć pomyśleć o eager generation i cache’owaniu CDN, jak omówiono wcześniej — ActiveStorage daje ci mechanizm, ale strategia przy skali wciąż jest twoja.)
Bezpośrednie uploady: omiń serwer aplikacji
Naprawdę miłą funkcją są bezpośrednie uploady: ActiveStorage dostarcza JavaScript, który wgrywa plik bezpośrednio z przeglądarki do twojej usługi składowania (S3), omijając serwer aplikacji całkowicie. Twój proces Rails nie jest wiązany strumieniowaniem dużego pliku przez siebie; przeglądarka wysyła go prosto do S3, a potem wysyła formularz z referencją. Dla aplikacji obsługujących duże pliki albo wiele jednoczesnych uploadów to trzyma twoich workerów webowych wolnymi dla rzeczywistych żądań — realna wygrana architektoniczna, której starsze gemy nie oferowały od ręki.
ActiveStorage vs CarrierWave/Paperclip
Uczciwe porównanie, bo „jest teraz we frameworku” nie znaczy automatycznie „przerzuć wszystko”:
- Zalety ActiveStorage: jest wbudowany i utrzymywany przez zespół Rails (żadnego gemu strony trzeciej do śledzenia), model blob/attachment jest czysty, bezpośrednie uploady są pierwszoklasowe, a abstrakcja usług jest znakomita. Dla nowej aplikacji to naturalny domyślny wybór.
- Gdzie starsze gemy wciąż prowadzą (w 2018): CarrierWave jest dojrzały i oferuje drobnoziarnistą kontrolę nad przetwarzaniem, warunkowymi wersjami i walidacją, której ActiveStorage, będąc nowy, jeszcze nie wszystkim dogonił. Walidacja typu i rozmiaru pliku w szczególności jest w wczesnym ActiveStorage zauważalnie mniej rozbudowana. Więc istniejąca aplikacja CarrierWave o złożonych potrzebach nie ma pilnego powodu do migracji, a niektóre przypadki brzegowe są wciąż łatwiejsze w starszych gemach.
Nasze zdanie: sięgaj po ActiveStorage w nowych projektach — to ścieżka błogosławiona przez framework i właściwy domyślny wybór idący naprzód — uznając zarazem, że jest młody i dojrzeje. Nie spiesz się z wyrwaniem działającego ustawienia CarrierWave; migruj, gdy masz powód, nie dla mody.
Werdykt
ActiveStorage to mile widziany i spóźniony dodatek: wgrywanie plików wreszcie należy do Rails, a nie do gemu, który doczepiasz i konfigurujesz od zera. Model blob/attachment trzyma twoje tabele czystymi, abstrakcja usług czyni lokalny-vs-S3 jednolinijkową zmianą konfiguracji, warianty obsługują transformację obrazów na żądanie, a bezpośrednie uploady trzymają duże pliki z dala od serwerów aplikacji. Jest młody w 2018 — walidacja i część zaawansowanego przetwarzania zostają w tyle za CarrierWave — więc to jasny domyślny wybór dla nowych aplikacji, choć nie nakaz migracji istniejących. Gdy dojrzeje, stanie się po prostu sposobem, w jaki robi się uploady w Rails, tak samo jak framework stopniowo wchłonął każdy inny kawałek hydrauliki, który każda aplikacja kiedyś składała ręcznie.