Skip to content
← Wszystkie wpisy
4 min czytania Dawid Skłodowski

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.