Bestanden uploaden met rails en schrijn

Er zijn veel edelstenen voor het uploaden van bestanden zoals CarrierWave, Paperclip en Dragonfly, om maar een paar te noemen. Ze hebben allemaal hun specifieke kenmerken, en waarschijnlijk heb je al minstens één van deze edelstenen gebruikt.

Vandaag wil ik echter een relatief nieuwe, maar erg gave oplossing introduceren genaamd Shrine, gemaakt door Janko Marohnić. In tegenstelling tot sommige andere soortgelijke edelstenen, heeft het een modulaire aanpak, wat betekent dat elke functie is verpakt als een module (of inpluggen in de terminologie van Shrine). Wilt u validaties ondersteunen? Voeg een plugin toe. Wil je wat bestandsverwerking doen? Voeg een plugin toe! Ik ben echt dol op deze aanpak, omdat je eenvoudig kunt bepalen welke functies beschikbaar zijn voor welk model.

In dit artikel laat ik je zien hoe je:

  • integreer Shrine in een Rails-applicatie
  • configureer het (globaal en per-uploader)
  • voeg de mogelijkheid toe om bestanden te uploaden
  • verwerk bestanden
  • voeg validatieregels toe
  • sla extra metadata op en gebruik bestandswolkopslag met Amazon S3

De broncode voor dit artikel is beschikbaar op GitHub.

De werkende demo is hier te vinden.

Schrijn integreren

Maak om te beginnen een nieuwe Rails-applicatie zonder de standaard testsuite:

rails nieuwe FileGuru -T

Ik zal Rails 5 gebruiken voor deze demo, maar de meeste concepten zijn ook van toepassing op versies 3 en 4.

Drop de Shrine-edelsteen in je Gemfile:

gem "schrijn"

Voer dan uit:

bundel installeren

Nu hebben we een model nodig dat ik ga bellen Foto. Shrine slaat alle bestandsgerelateerde informatie op in een speciale tekstkolom die eindigt op a _gegevens achtervoegsel. Maak en pas de bijbehorende migratie toe:

rails g model Fototitel: string image_data: tekstrails db: migreren

Merk op dat voor oudere versies van Rails, de laatste opdracht zou moeten zijn:

rake db: migreren

Configuratie-opties voor Shrine kunnen zowel globaal als per model worden ingesteld. Globale instellingen zijn natuurlijk gedaan in het initialisatiebestand. Daar ga ik de nodige bestanden en aanhaken plugins. Plugins worden gebruikt in Shrine om stukjes functionaliteit in afzonderlijke modules te extraheren, waardoor u de volledige controle over alle beschikbare functies hebt. Er zijn bijvoorbeeld plug-ins voor validatie, beeldverwerking, cachingbijlagen en meer.

Laten we voorlopig twee plug-ins toevoegen: een om ActiveRecord te ondersteunen en een andere om logboekregistratie in te stellen. Ze zullen wereldwijd worden opgenomen. Stel ook de opslag van het bestandssysteem in:

config / initialiseerders / shrine.rb

vereisen "schrijn" vereisen "schrijn / opslag / bestandssysteem" Shrine.plugin: activerecord Shrine.plugin: logging, logger: Rails.logger Shrine.storages = cache: Shrine :: Opslag :: FileSystem.new ("public", voorvoegsel : "uploads / cache"), winkel: Shrine :: Opslag :: FileSystem.new ("public", voorvoegsel: "uploads / store"),

Logger voert eenvoudig wat foutopsporingsinformatie uit in de console, zodat u kunt zeggen hoeveel tijd er is besteed aan het verwerken van een bestand. Dit kan van pas komen.

2015-10-09T20: 06: 06.676Z # 25602: WINKEL [cache] ImageUploader [: avatar] Gebruiker [29543] 1 bestand (0.1s) 2015-10-09T20: 06: 06.854Z # 25602: PROCES [winkel]: ImageUploader [: avatar] Gebruiker [29543] 1-3 bestanden (0.22s) 2015-10-09T20: 06: 07.133Z # 25602: DELETE [destroyed]: ImageUploader [: avatar] Gebruiker [29543] 3 bestanden (0.07s)

Alle geüploade bestanden worden opgeslagen in de public / uploads directory. Ik wil deze bestanden niet volgen in Git, dus sluit deze map uit:

.gitignore

public / uploads

Maak nu een speciale "uploader" -klasse die modelspecifieke instellingen gaat hosten. Voor nu zal deze klasse leeg zijn:

modellen / image_uploader.rb

class ImageUploader < Shrine end

Tenslotte, neem deze klasse op in de Foto model:

modellen / photo.rb

include ImageUploader [: afbeelding]

[:beeld] voegt een virtueel attribuut toe dat zal worden gebruikt bij het construeren van een formulier. De bovenstaande regel kan worden herschreven als:

 include ImageUploader.attachment (: image) # of include ImageUploader :: Attachment.new (: afbeelding) 

Leuk! Het model is nu uitgerust met de functies van Shrine en we kunnen doorgaan naar de volgende stap.

Controller, weergaven en routes

Voor deze demo hebben we maar één controller nodig om foto's te beheren. De inhoudsopgave pagina zal dienen als de root:

pages_controller.rb

class PhotosController < ApplicationController def index @photos = Photo.all end end

Het uitzicht:

views / photos / index.html.erb

foto's

<%= link_to 'Add Photo', new_photo_path %> <%= render @photos %>

Om de. Te renderen @photos array, een deel is vereist:

views / photos / _photo.html.erb

<% if photo.image_data? %> <%= image_tag photo.image_url %> <% end %>

<%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %>

image_data? is een methode gepresenteerd door ActiveRecord die controleert of een record een afbeelding heeft.

afbeelding URL is een Shrine-methode die eenvoudigweg een pad naar de originele afbeelding retourneert. Het is natuurlijk veel beter om in plaats daarvan een kleine miniatuur weer te geven, maar daar zorgen we later voor.

Voeg alle benodigde routes toe:

config / routes.rb

 middelen: alleen foto's: [: nieuw,: maken,: index,: bewerken,: bijwerken] root 'photos # index'

Dit is het - het grondwerk is voltooid en we kunnen doorgaan naar het interessante deel!

Bestanden uploaden

In deze sectie zal ik u laten zien hoe u de functionaliteit kunt toevoegen om bestanden daadwerkelijk te uploaden. De acties van de controller zijn heel eenvoudig:

photos_controller.rb

def new @photo = Photo.new end def create @photo = Photo.new (photo_params) if @ photo.save flash [: succes] = 'Foto toegevoegd!' redirect_to photos_path anders render 'nieuw' einde

Het enige datcha is dat je voor sterke parameters de beeld virtueel attribuut, niet het image_data.

photos_controller.rb

private def photo_params params.require (: photo) .permit (: title,: image) end

Maak het nieuwe uitzicht:

views / photos / new.html.erb

Voeg foto toe

<%= render 'form' %>

Het deel van het formulier is ook triviaal:

views / photos / _form.html.erb

<%= form_for @photo do |f| %> <%= render "shared/errors", object: @photo %> <%= f.label :title %> <%= f.text_field :title %> <%= f.label :image %> <%= f.file_field :image %> <%= f.submit %> <% end %>

Merk nogmaals op dat we de beeld attribuut, niet het image_data.

Voeg ten slotte nog een deel toe aan weergavefouten:

views / shared / _errors.html.erb

<% if object.errors.any? %> 

De volgende errors zijn gevonden:

    <% object.errors.full_messages.each do |message| %>
  • <%= message %>
  • <% end %>
<% end %>

Dit is zo ongeveer alles - u kunt nu meteen afbeeldingen uploaden.

validaties

Uiteraard moet er nog veel werk worden verzet om de demo-app te voltooien. Het grootste probleem is dat de gebruikers absoluut elk type bestand mogen uploaden met elke grootte, wat niet bijzonder goed is. Voeg daarom een ​​andere plug-in toe om validaties te ondersteunen:

config / inititalizers / shrine.rb

Shrine.plugin: validation_helpers

Stel de validatielogica in voor de ImageUploader:

modellen / image_uploader.rb

Attacher.validate validate_max_size 1.megabyte doen, bericht: "is te groot (max is 1 MB)" validate_mime_type_inclusion ['image / jpg', 'image / jpeg', 'image / png'] einde

Ik sta toe dat alleen JPG- en PNG-afbeeldingen van minder dan 1 MB worden geüpload. Pas deze regels naar eigen inzicht aan.

MIME-typen

Een ander belangrijk ding om op te merken is dat Shrine standaard het MIME-type van een bestand zal bepalen met behulp van de Content-Type HTTP-header. Deze header wordt door de browser doorgegeven en alleen ingesteld op basis van de extensie van het bestand, wat niet altijd wenselijk is.

Als u het MIME-type wilt bepalen op basis van de inhoud van het bestand, gebruikt u een plug-in genaamd define_mime_type. Ik zal het opnemen in de uploader-klasse, omdat andere modellen deze functionaliteit mogelijk niet nodig hebben:

modellen / image_uploader.rb

plugin: bepalen_mime_type

Deze plugin gebruikt standaard het bestandshulpprogramma van Linux.

Geregistreerde afbeeldingen cachen

Momenteel, wanneer een gebruiker een formulier met onjuiste gegevens verzendt, wordt het formulier opnieuw weergegeven met fouten die hierboven zijn weergegeven. Het probleem is echter dat de bijgevoegde afbeelding verloren gaat en dat de gebruiker deze opnieuw moet selecteren. Dit is heel gemakkelijk op te lossen met behulp van nog een andere plugin genaamd cached_attachment_data:

modellen / image_uploader.rb

plugin: cached_attachment_data

Voeg eenvoudig een verborgen veld toe aan uw formulier.

views / photos / _form.html.erb

<%= f.hidden_field :image, value: @photo.cached_image_data %> <%= f.label :image %> <%= f.file_field :image %>

Een foto bewerken

Nu kunnen afbeeldingen worden geüpload, maar er is geen manier om ze te bewerken, dus laten we het meteen oplossen. De acties van de bijbehorende controller zijn enigszins triviaal:

photos_controller.rb

def edit @photo = Photo.find (params [: id]) einde def update @photo = Photo.find (params [: id]) if @ photo.update_attributes (photo_params) flash [: succes] = 'Foto bewerkt!' redirect_to photos_path anders render 'einde' einde

Hetzelfde _het formulier gedeeltelijk zal worden gebruikt:

views / photos / edit.html.erb

Bewerk foto

<%= render 'form' %>

Leuk, maar niet genoeg: gebruikers kunnen een geüploade afbeelding nog steeds niet verwijderen. Om dit mogelijk te maken, moeten we een andere plug-in raden: 

modellen / image_uploader.rb

plugin: remove_attachment

Het gebruikt een virtueel attribuut genaamd : remove_image, dus laat het in de controller:

photos_controller.rb

def photo_params params.require (: foto) .permit (: title,: image,: remove_image) einde

Geef nu een selectievakje weer om een ​​afbeelding te verwijderen als een record een bijlage bevat:

views / photos / _form.html.erb

<% if @photo.image_data? %> Bijlage verwijderen: <%= f.check_box :remove_image %> <% end %>

Een miniatuurafbeelding genereren

Momenteel geven we originele afbeeldingen weer, wat niet de beste benadering is voor voorbeelden: foto's kunnen groot zijn en te veel ruimte in beslag nemen. Natuurlijk kunt u eenvoudig de CSS gebruiken breedte en hoogte attributen, maar dat is ook een slecht idee. U ziet dat, zelfs als de afbeelding klein is met gebruik van stijlen, de gebruiker het originele bestand nog steeds moet downloaden, wat behoorlijk groot kan zijn.

Daarom is het veel beter om een ​​kleine voorbeeldafbeelding aan de serverzijde te genereren tijdens de eerste upload. Dit omvat twee plug-ins en twee extra edelstenen. Laat eerst de edelstenen vallen:

gem "image_processing" gem "mini_magick", "> = 4.3.5"

Image_processing is een speciaal juweel gecreëerd door de auteur van Shrine. Het presenteert enkele high-level helper-methoden om afbeeldingen te manipuleren. Dit juweeltje is op zijn beurt afhankelijk van mini_magick, een Ruby-wrapper voor ImageMagick. Zoals je al geraden hebt, heb je ImageMagick nodig op je systeem om deze demo uit te voeren.

Installeer deze nieuwe edelstenen:

bundel installeren

Voeg nu de plug-ins samen met hun afhankelijkheden toe:

modellen / image_uploader.rb

vereisen "image_processing / mini_magick" class ImageUploader < Shrine include ImageProcessing::MiniMagick plugin :processing plugin :versions # other code… end

Verwerking is de plug-in waarmee we een afbeelding kunnen manipuleren (bijvoorbeeld krimpen, roteren, converteren naar een ander formaat, enz.). Versies, op zijn beurt, laten ons toe om een ​​afbeelding in verschillende varianten te hebben. Voor deze demo worden twee versies opgeslagen: "origineel" en "thumb" (van grootte veranderd naar 300x300).

Hier is de code om een ​​afbeelding te verwerken en de twee versies op te slaan:

modellen / image_uploader.rb

class ImageUploader < Shrine process(:store) do |io, context|  original: io, thumb: resize_to_limit!(io.download, 300, 300)  end end

resize_to_limit! is een methode die wordt geleverd door de edelsteen image_processing. Het verkleint eenvoudig een afbeelding tot 300x300 als het groter is en niets doet als het kleiner is. Bovendien behoudt het de originele beeldverhouding.

Wanneer u nu de afbeelding weergeeft, hoeft u alleen de : origineel of :duim argument voor de afbeelding URL methode:

views / photos / _photo.html.erb

<% if photo.image_data? %> <%= image_tag photo.image_url(:thumb) %> <% end %>

<%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %>

Hetzelfde kan gedaan worden in het formulier:

views / photos / _form.html.erb

<% if @photo.image_data? %> <%= image_tag @photo.image_url(:thumb) %> Bijlage verwijderen: <%= f.check_box :remove_image %> <% end %>

Als u de verwerkte bestanden automatisch wilt verwijderen nadat het uploaden is voltooid, kunt u een plug-in toevoegen met de naam delete_raw:

modellen / image_uploader.rb

plugin: delete_raw

Metagegevens van afbeeldingen

Afgezien van het daadwerkelijk renderen van een afbeelding, mag u ook de metadata ophalen. Laten we bijvoorbeeld de grootte van de originele foto en het MIME-type weergeven:

views / photos / _photo.html.erb

<% if photo.image_data? %> <%= image_tag photo.image_url(:thumb) %>

Grootte <%= photo.image[:original].size %> bytes
Mime type <%= photo.image[:original].mime_type %>

<% end %>

<%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %>

Hoe zit het met zijn afmetingen? Helaas worden ze niet standaard opgeslagen, maar dit is mogelijk met een plug-in genaamd store_dimensions.

Dimensies van afbeeldingen

De plug-in store_dimensions is afhankelijk van het fastimage-juweel, dus sluit het nu aan:

gem 'fastimage'

Vergeet niet om te lopen:

bundel installeren

Voeg nu gewoon de plug-in toe:

modellen / image_uploader.rb

plugin: store_dimensions

En toon de dimensies met behulp van de breedte en hoogte methoden:

views / photos / _photo.html.erb

<% if photo.image_data? %> <%= image_tag photo.image_url(:thumb) %>

Grootte <%= photo.image[:original].size %> bytes
Mime type <%= photo.image[:original].mime_type %>
Dimensies <%= "#photo.image[:original].widthx#photo.image[:original].height" %>

<% end %>

<%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %>

Ook is er een dimensies beschikbare methode die een array retourneert met de breedte en hoogte (bijvoorbeeld, [500, 750]).

Verhuizen naar de cloud

Ontwikkelaars kiezen vaak voor cloudservices om geüploade bestanden te hosten, en Shrine biedt zo'n mogelijkheid. In deze sectie zal ik je laten zien hoe je bestanden kunt uploaden naar Amazon S3.

Als de eerste stap, voeg nog twee edelstenen toe aan de Gemfile:

gem "aws-sdk", "~> 2.1" groep: ontwikkeling do edel 'dotenv-rails' einde

aws-sdk moet werken met S3's SDK, terwijl dotenv-rails zullen worden gebruikt om omgevingsvariabelen in ontwikkeling te beheren.

bundel installeren

Voordat u doorgaat, moet u een sleutelpaar verkrijgen om via API toegang te krijgen tot S3. Om het te krijgen, logt u in (of registreert u zich) bij Amazon Web Services Console en navigeert u naar Beveiligingslegitimatie> Gebruikers. Maak een gebruiker aan met machtigingen om bestanden op S3 te manipuleren. Hier is het eenvoudige beleid dat volledige toegang tot S3 biedt:

"Versie": "2016-11-14", "Statement": ["Effect": "Allow", "Action": "s3: *", "Resource": "*"]

Download het sleutelpaar van de gemaakte gebruiker. Je kunt ook root-toegangstoetsen gebruiken, maar ik sterk ontmoedigen jij doet dat omdat het erg onzeker is.

Maak vervolgens een S3-bucket om uw bestanden te hosten en voeg een bestand toe aan de hoofdmap van het project om uw configuratie te hosten:

.env

S3_KEY = YOUR_KEY S3_SECRET = YOUR_SECRET S3_BUCKET = YOUR_BUCKET S3_REGION = YOUR_REGION

Nooit blootstellen dit bestand voor het publiek, en zorg ervoor dat je het uitsluit van Git:

.gitignore

.env

Pas nu de algemene configuratie van Shrine aan en introduceer een nieuwe opslag:

config / initialiseerders / shrine.rb

vereist "schrijn" vereist "schrijn / opslag / s3" s3_options = access_key_id: ENV ['S3_KEY'], secret_access_key: ENV ['S3_SECRET'], regio: ENV ['S3_REGION'], bucket: ENV ['S3_BUCKET'] , Shrine.storages = cache: Shrine :: Opslag :: FileSystem.new ("public", prefix: "uploads / cache"), winkel: Shrine :: Storage :: S3.new (prefix: "store", ** s3_opties),

Dat is het! Er hoeven geen wijzigingen in de andere delen van de app te worden aangebracht en u kunt deze nieuwe opslag meteen testen. Als u fouten van S3 ontvangt die betrekking hebben op onjuiste sleutels, moet u ervoor zorgen dat u de sleutel en het geheim nauwkeurig hebt gekopieerd, zonder spaties en onzichtbare speciale symbolen.

Conclusie

We zijn aan het einde van dit artikel gekomen. Hopelijk heb je inmiddels veel vertrouwen in het gebruik van Shrine en wil je het graag gebruiken in een van je projecten. We hebben veel van de functies van dit juweel besproken, maar er zijn er nog meer, zoals de mogelijkheid om extra context samen met bestanden en het directe uploadmechanisme op te slaan.. 

Blader daarom door de documentatie van Shrine en de officiële website die alle beschikbare plug-ins grondig beschrijft. Als je nog andere vragen over dit juweeltje hebt, aarzel dan niet om ze te plaatsen. Ik dank je dat je bij me bent gebleven en ik zie je binnenkort!