Filopplastinger er vanligvis et vanskelig område i webutvikling. I denne opplæringen lærer vi hvordan du bruker Dragonfly, en kraftig Ruby perle som gjør det enkelt og effektivt å legge til noen form for opplastingsfunksjonalitet til et Rails prosjekt.
Vårt søknadsprogram viser en liste over brukere, og for hver enkelt av dem vil vi kunne laste opp en avatar og ha den lagret. I tillegg vil Dragonfly tillate oss å:
I denne leksjonen følger vi en BDD [Behavior Driven Development] tilnærming, ved hjelp av agurk og RSpec.
Du må ha Imagemagick installert: Du kan henvise til denne siden for binærene å installere. Som jeg er basert på en Mac-plattform, bruk Homebrew, jeg kan bare skrive brygge installasjon imagemagick
.
Du må også klone et grunnleggende Rails-program som vi skal bruke som utgangspunkt.
Vi vil begynne med å klone startregisteret og sette opp våre avhengigheter:
git klone http: //[email protected]/cloud8421/tutorial_dragonfly_template.git cd tutorial_dragonfly_template
Denne applikasjonen krever minst Ruby 1.9.2 å kjøre, men jeg oppfordrer deg til å bruke 1.9.3. Rails versjonen er 3.2.1. Prosjektet inkluderer ikke a .rvmrc
eller a .rbenv
fil.
Deretter løper vi:
bunt installasjonspakke exec rake db: setup db: test: lag db: frø
Dette vil ta vare på perleavhengighetene og databaseoppsettet (vi bruker sqlite, så du trenger ikke å bekymre deg for database config).
For å teste at alt fungerer som forventet, kan vi kjøre:
bunt exec rspec bunke exec agurk
Du bør oppdage at alle tester har passert. La oss se på Agurks produksjon:
Funksjon: Administrere brukerprofil Som bruker For å administrere dataene mine vil jeg få tilgang til min brukerprofilside Bakgrunn: Gitt en bruker eksisterer med e-post "[email protected]" Scenario: visning av profilen min Gitt Jeg er på hjemmesiden Når Jeg følger "Profil" for "[email protected]" Da skal jeg være på profilsiden for "[email protected]" Scenario: redigering av profilen min. Gitt Jeg er på profilsiden for "[email protected]" Når Jeg følger "Rediger" og jeg endrer e-posten min med "[email protected]" Og jeg klikker "Lagre". Da skal jeg være på profilsiden for "[email protected]" Og jeg bør se "Bruker oppdatert" 2 scenarier (2 bestått) 11 trinn (11 bestått) 0m0.710s
Som du kan se, beskriver disse funksjonene en typisk brukervennlig arbeidsflyt: Vi åpner en brukerside fra en liste, trykker "Rediger" for å redigere brukerdataene, endre e-postadressen og lagre.
Prøv nå å kjøre appen:
skinner s
Hvis du åpner http :: // localhost: 3000
I nettleseren finner du en liste over brukere (vi pre-populerte databasen med 40 tilfeldige poster takket være Faker-perlen).
For nå vil hver enkelt bruker ha en liten, 16x16px avatar og en stor plassholder-avatar på sin profilside. Hvis du redigerer brukeren, vil du kunne endre detaljer (fornavn, etternavn og passord), men hvis du prøver å laste opp en avatar, blir den ikke lagret.
Du er velkommen til å bla gjennom kodebase: programmet bruker Enkel skjema for å generere skjermvisninger og Twitter Bootstrap for CSS og layout, da de integreres perfekt og hjelper mye med å øke prototypeprosessen.
Vi starter med å legge til et nytt scenario til funksjoner / managing_profile.feature
:
... Scenario: Legge til en avatar Gitt Jeg er på profilsiden for "[email protected]" Når jeg følger "Rediger" Og jeg laster opp mustasjenavnet Og jeg klikker "Lagre" Da skal jeg være på profilsiden for "e-post @ example.com "Og profilen skal vise" mustache avatar "
Denne funksjonen er ganske selvforklarende, men det krever noen få ekstra trinn å legge til funksjoner / step_definitions / user_steps.rb
:
... Når / ^ jeg laster opp overskriften avatar $ / gjør attach_file 'bruker [avatar_image]', Rails.root + 'spec / fixtures / mustache_avatar.jpg' end Da / ^ profilen skal vise "([^"] *) " $ / do | image | pattern = case image når 'mustache avatar' / mustache_avatar / end n = Nokogiri :: HTML (side.body) n.xpath (".// img [@ class = 'thumbnail']") .first ['src']. bør = ~ mønster slutten
Dette trinnet antar at du har et bilde, kalt mustache_avatar.jpg
innsiden spec / inventar
. Som du kanskje gjetter, er dette bare et eksempel; det kan være alt du vil ha.
Det første trinnet bruker Capybara til å finne Brukeren [avatar_image]
filfelt og last opp filen. Legg merke til at vi antar at vi vil ha en an avatar_image
attributt på Bruker
modell.
Det andre trinnet bruker Nokogiri (et kraftig HTML / XML-parsing-bibliotek) og XPath for å analysere innholdet på den resulterende profilsiden og søke etter den første img
tag med a thumbnail
klasse og test at src
attributtet inneholder mustache_avatar
.
Hvis du kjører agurk
Nå vil dette scenariet utløse en feil, da det ikke er noe filfelt med navnet vi oppgav. Nå er det dags å fokusere på Bruker
modell.
Før du integrerer Dragonfly med Bruker
modell, la oss legge til et par spesifikasjoner til user_spec.rb
.
Vi kan legge til en ny blokk rett etter egenskaper
kontekst:
kontekst "avatar attributter" gjør det should respond_to (: avatar_image) det should allow_mass_assignment_of (: avatar_image) avslutte
Vi tester at brukeren har en avatar_image
attributt og, siden vi skal oppdatere dette attributtet gjennom et skjema, må det være tilgjengelig (andre spesifikasjon).
Nå kan vi installere Dragonfly: ved å gjøre det, får vi disse spesifikasjonene for å bli grønn.
La oss legge til følgende linjer i Gemfile:
perle 'rack-cache', krever: 'rack / cache' perle 'dragonfly', '~> 0.9.10'
Deretter kan vi løpe bunt installasjon
. Rack-cache er nødvendig i utviklingen, da det er det enkleste alternativet for å ha HTTP-caching. Det kan også brukes i produksjon, selv om mer robuste løsninger (som lakk eller blekksprut) ville være bedre.
Vi må også legge til Dragonfly-initialisatoren. La oss lage konfig / initializers / dragonfly.rb
fil og legg til følgende:
krever 'dragonfly' app = Dragonfly [: bilder] app.configure_with (: imagemagick) app.configure_with (: rails) app.define_macro (ActiveRecord :: Base,: image_accessor)
Dette er vanilje Dragonfly-konfigurasjonen: den setter opp en Dragonfly-applikasjon og konfigurerer den med den nødvendige modulen. Den legger også til en ny makro til Active
at vi vil kunne bruke til å utvide vår Bruker
modell.
Vi må oppdatere config / application.rb
, og legg til et nytt direktiv i konfigurasjonen (rett før config.generators
blokkere):
config.middleware.insert 0, 'Rack :: Cache', verbose: true, metastore: URI.encode ("fil: # Rails.root / tmp / dragonfly / cache / meta"), virksomhetsbutikk: URI.encode ("fil: # Rails.root / tmp / dragonfly / cache / body") med mindre Rails.env.production? config.middleware.insert_after 'Rack :: Cache', 'Dragonfly :: Middleware',: bilder
Uten å gå i detalj, setter vi opp Rack :: Cache
(unntatt produksjon, hvor den er aktivert som standard), og sette opp Dragonfly for å bruke den.
Vi lagrer bildene våre på disk, men vi trenger en måte å spore foreningen med en bruker på. Det enkleste alternativet er å legge to kolonner til brukerbordet med en migrering:
skinner g migrasjon add_avatar_to_users avatar_image_uid: streng avatar_image_name: strengbunt exec rake db: migrere db: test: forberede
Igjen, dette er rett fra Dragonflys dokumentasjon: vi må ha en avatar_image_uid
kolonne for å identifisere avatarfilen og a avatar_image_name
for å lagre sitt opprinnelige filnavn (sistnevnte kolonne er ikke strengt nødvendig, men det gjør det mulig å generere bildeadresser som slutter med det opprinnelige filnavnet).
Til slutt kan vi oppdatere Bruker
modell:
klassen bruker < ActiveRecord::Base image_accessor :avatar_image attr_accessible :email, :first_name, :last_name, :avatar_image…
De image_accessor
Metoden er gjort tilgjengelig av Dragonfly initializer, og det krever bare et attributtnavn. Vi lager også samme attributt i linjen nedenfor.
Løping rspec
Nå burde alle spesifikasjoner være grønne.
For å teste opplastingsfunksjonen kan vi legge til en kontekst til users_controller_spec.rb
i PUT oppdatering
blokkere:
kontekst "avatar image" gjør la! (: image_file) fixture_file_upload ('/ mustache_avatar.jpg', 'image / jpg') kontekst "laster opp en avatar" gjør før du legger: oppdatering, id: user.id, bruker: avatar_image: image_file avslutte det "burde effektivt lagre bildeoppføringen på brukeren" gjør user.reload user.avatar_image_name.should = ~ / mustache_avatar / end-end
Vi vil gjenbruke samme fixtur og skape en mock for opplasting med fixture_file_upload
.
Siden denne funksjonaliteten utnytter Dragonfly, trenger vi ikke å skrive kode for å få det passert.
Vi må nå oppdatere våre synspunkter for å vise avataren. La oss starte fra brukervisningssiden og åpne app / visninger / brukere / show.html.erb
og oppdater det med følgende innhold:
<% if @user.avatar_image.present? %> <%= image_tag @user.avatar_image.url, class: 'thumbnail' %> <% else %> <% end %>
<%= @user.name %>
<%= @user.email %>
<%= link_to 'Edit', edit_user_path(@user), class: "btn" %>
Deretter kan vi oppdatere app / visninger / brukere / edit.html.erb
:
<%= simple_form_for @user, multipart: true do |f| %><% end %><% if @user.avatar_image.present? %> <%= image_tag @user.avatar_image.url, class: 'thumbnail' %> <% else %> <% end %><%= f.input :avatar_image, as: :file %><%= f.input :first_name %> <%= f.input :last_name %> <%= f.input :email %><%= f.submit 'Save', class: "btn btn-primary" %>
Vi kan vise brukerens avatar med et enkelt anrop til @ user.avatar_image.url
. Dette vil returnere en URL til en ikke-endret versjon av avataren lastet opp av brukeren.
Hvis du kjører agurk
Nå ser du den grønne funksjonen. Du kan også prøve det i nettleseren også!
Vi er implisitt avhengige av CSS for å endre størrelsen på bildet hvis det er for stort for beholderen. Det er en rystende tilnærming: Brukeren vår kan laste opp ikke-kvadratiske avatarer, eller et veldig lite bilde. I tillegg serverer vi alltid det samme bildet, uten for mye bekymring for sidestørrelse eller båndbredde.
Vi må jobbe på to forskjellige områder: legge til noen valideringsregler til avataropplastingen og angi bildestørrelse og forhold med Dragonfly.
Vi starter med å åpne user_spec.rb
fil og legge til en ny spesifikke blokk:
kontekst "avatar attributter" gjør% w (avatar_image retained_avatar_image remove_avatar_image) .each do | attr | den should respond_to (attr.to_sym) end% w (avatar_image retained_avatar_image remove_avatar_image) .each do | attr | det bør allow_mass_assignment_of (attr.to_sym) avslutte det "bør validere filstørrelsen til avataren" gjør user.avatar_image = Rails.root + 'spec / fixtures / huge_size_avatar.jpg' user.should_not be_valid # størrelse er> 100 KB avslutte det "burde validere formatet til avataren" gjør user.avatar_image = Rails.root + 'spec / fixtures / dummy.txt' user.should_not be_valid end-end
Vi tester for tilstedeværelse og tillater "masseoppgave" for ytterligere attributter som vi skal bruke for å forbedre brukerskjemaet (: retained_avatar_image
og : remove_avatar_image
).
I tillegg tester vi også at vår brukermodell ikke aksepterer store opplastinger (mer enn 200 kB) og filer som ikke er bilder. I begge tilfeller må vi legge til to fixturefiler (et bilde med det angitte navnet og hvis størrelse er over 200 kB og en tekstfil med noe innhold).
Som vanlig kjører disse spesifikasjonene ikke oss til grønt. La oss oppdatere brukermodellen for å legge til disse valideringsreglene:
... attr_accessible: email,: first_name,: last_name,: avatar_image,: retained_avatar_image,: remove_avatar_image ... validates_size_of: avatar_image, maksimum: 100.kilobytes validates_property: format, av:: avatar_image, i: [: jpeg,: png, : jpg] validates_property: mime_type, av:: avatar_image, i: ['image / jpg', 'image / jpeg', 'image / png', 'image / gif'], case_sensitive: false
Disse reglene er ganske effektive: Merk at i tillegg til å sjekke formatet, sjekker vi også mime typen for bedre sikkerhet. Å være et bilde, tillater vi jpg-, png- og gif-filer.
Våre spesifikasjoner skal passere nå, så det er på tide å oppdatere visningene for å optimalisere bildebelastningen.
Som standard bruker Dragonfly ImageMagick til å behandle bilder dynamisk når det blir bedt om det. Forutsatt at vi har a bruker
eksempel i en av våre synspunkter, kan vi da:
user.avatar_image.thumb ('100x100'). url user.avatar_image.process (: gråtoner) .url
Disse metodene vil skape en behandlet versjon av dette bildet med en unik hash, og takket være vårt caching-lag, blir ImageMagick kalt bare en gang per bilde. Deretter blir bildet servert direkte fra cachen.
Du kan bruke mange innebygde metoder eller bare bygge din egen. Dragonflys dokumentasjon har mange eksempler.
La oss gå tilbake til brukeren vår redigere
side og oppdater visningskoden:
... <% if @user.avatar_image.present? %> <%= image_tag @user.avatar_image.thumb('400x400#').url, class: 'thumbnail' %> <% else %>...
Vi gjør det samme for brukeren vise fram
side:
... <% if @user.avatar_image.present? %> <%= image_tag @user.avatar_image.thumb('400x400#').url, class: 'thumbnail' %> <% else %>...
Vi tvinger bildestørrelsen til 400 x 400 piksler. De #
parameteren instruerer også ImageMagick å beskjære bildet som holder en sentral tyngdekraft. Du kan se at vi har samme kode på to steder, så la oss reflektere dette inn i en delvis kalt visninger / brukere / _avatar_image.html.erb
<% if @user.avatar_image.present? %> <%= image_tag @user.avatar_image.thumb('400x400#').url, class: 'thumbnail' %> <% else %> <% end %>
Da kan vi erstatte innholdet i .thumbnail
container med et enkelt anrop til:
<%= render 'avatar_image' %>
Vi kan gjøre enda bedre ved å flytte argumentet til tommel
ut av den delvise. La oss oppdatere _avatar_image.html.erb
:
<% if user.avatar_image.present? %> <%= image_tag user.avatar_image.thumb(args).url %> <% else %> & text = Super + kul + avatar "alt =" Super cool avatar "> <% end %>
Vi kan nå ringe vårt parti med to argumenter: ett for ønsket aspekt og en for brukeren:
<%= render 'avatar_image', args: '400x400#', user: @user %>
Vi kan bruke koden ovenfor i redigere
og vise fram
visninger, mens vi kan kalle det på følgende måte inni visninger / brukere / _user_table.html.erb
, hvor vi viser de små miniatyrbildene.
...<%= link_to 'Profile', user_path(user) %> <%= render 'avatar_image', args: '16x16#', user: user %> <%= user.first_name %> ...
I begge tilfeller utfører vi også en Regex på aspektet for å trekke ut en streng som er kompatibel med placehold.it-tjenesten (dvs. fjerning av ikke-alfanumeriske tegn).
Dragonfly lager to ekstra attributter som vi kan bruke i et skjema:
retained_avatar_image
: Dette lagrer det opplastede bildet mellom omlastninger. Hvis valideringer for et annet skjemafelt (sier e-post) mislykkes, og siden oppdateres, er det opplastede bildet fortsatt tilgjengelig uten å måtte laste det opp igjen. Vi bruker den direkte i skjemaet.remove_avatar_image
: Når det er sant, blir det nåværende avatarbildet slettet både fra brukeroppføringen og disken.Vi kan teste avatar fjerning ved å legge til en ekstra spec til users_controller_spec.rb
, i avatar bilde
blokkere:
... kontekst "fjerner en avatar" gjør før bruker.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg' user.save avslutte det "bør fjerne avataren fra brukeren" put: oppdatering, id: bruker. id, bruker: remove_avatar_image: "1" user.reload user.avatar_image_name.should be_nil endend ...
Igjen, vil Dragonfly få denne spesifikasjonen til å passere automatisk som vi allerede har remove_avatar_image
attributt tilgjengelig for brukerens forekomst.
La oss da legge til en annen funksjon til managing_profile.feature
:
Scenario: Fjern en avatar Gitt brukeren med e-post "[email protected]" har mustasjenavnet og jeg er på profilsiden for "[email protected]" Når jeg følger "Rediger" Og jeg sjekker "Fjern avatar image" Og jeg klikker "Lagre" Da skal jeg være på profilsiden for "[email protected]" Og profilen skal vise "stedholderens avatar"
Som vanlig må vi legge til noen skritt for user_steps.rb
og oppdater en for å legge til en Regex for stedholder-avataren:
Gitt / ^ brukeren med e-post "([^"] *) "har overskje avatar $ / do | email | u = User.find_by_email (email) u.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg 'u.save ende når / ^ jeg sjekker "([^"] *) "$ / do | avkrysningsboksen | sjekk avmerkingsboksen slutt Da / ^ profilen skal vise "([^"] *) "$ / do | image | pattern = case image når" placeholder avatar "/placehold.it/ når" mustache avatar "/ mustache_avatar / end n = Nokogiri :: HTML (side.body) n.xpath (".// img [@ class = 'thumbnail']"). først ['src'].
Vi må også legge til to ytterligere felt til redigere
form:
...<%= f.input :retained_avatar_image, as: :hidden %> <%= f.input :avatar_image, as: :file, label: false %> <%= f.input :remove_avatar_image, as: :boolean %>...
Dette vil få vår funksjon å passere.
For å unngå å ha en stor og for detaljert funksjon, kan vi teste den samme funksjonaliteten i en forespørselspesifikasjon.
La oss lage en ny fil som heter spec / forespørsler / user_flow_spec.rb
og legg til dette innholdet:
krever "spec_helper" beskrive "User flow" la! (: bruker) Fabrikk (: bruker, e-post: "[email protected]") beskriver "visning av profilen" gjør det "skal vise profilen til brukeren" besøke '/' page.find ('tr', text: user.email) .click_link ("Profile") current_path = URI.parse (current_url) .veien current_path.should == user_path (bruker) endeendat beskriver "oppdatering profildata "gjør det" bør lagre endringene "gjør besøk" / "page.find ('tr', tekst: user.email) .click_link (" Profile ") click_link 'Rediger' fill_in: email, with:" new_email @ example.com "click_button" Lagre "current_path.should == user_path (user) page.should have_content" Brukeroppdatert "sluttendat beskriver" administrere avataren "gjør det" skal lagre den opplastede avataren "do user.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg' user.save besøk user_path (bruker) click_link 'Rediger' attach_file 'bruker [avatar_image]', Rails.root + 'spec / fixtures / mustache_avatar.jpg' click_button 'Lagre' current_path.should == user_path (user) page.should have_con telt "Bruker oppdatert" n = Nokogiri :: HTML (side.body) n.xpath (".// img [@ class = 'thumbnail']"). først ['src']. should = ~ / mustache_avatar / end det "bør fjerne avataren hvis det er påkrevd" gjør user.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg' user.save besøk user_path (bruker) click_link 'Rediger' sjekk "Fjern avatar image" click_button 'Lagre' current_path .should == user_path (user) page.should have_content "Bruker oppdatert" n = Nokogiri :: HTML (side.body) n.xpath (".// img [@ class = 'thumbnail']"). src ']. burde = ~ /placehold.it/ slutten slutten
Spesifikasjonen encapsulates alle trinnene vi brukte til å definere hovedfunksjonen. Det tester grundig oppmåling og flyt, slik at vi kan sørge for at alt fungerer ordentlig på granulært nivå.
Nå kan vi forkorte managing_profile.feature
:
Funksjon: Administrere brukerprofil Som bruker For å administrere dataene mine, vil jeg få tilgang til min brukerprofilside Bakgrunn: Gitt en bruker eksisterer med e-post "[email protected]" Scenario: redigering av profilen min Gitt jeg endrer e-posten med "new_mail @ example.com "for" [email protected] "Da skal jeg se" Bruker oppdatert "Scenario: legge til en avatar Gitt Jeg laster opp overskriften avatar for" [email protected] "Da skal profilen vise" mustache avatar " Scenario: å fjerne en avatar Gitt brukeren "[email protected]" har mustasjeavataren, og jeg fjerner den. Da skal brukeren "[email protected]" ha "placeholder avatar"
oppdatert user_steps.rb
:
Gitt / ^ en bruker eksisterer med e-post "([^"] *) "$ / do | e-post | Fabrikk (: bruker, epost: epost) ende Gitt / ^ brukeren med e-post" the mustache avatar $ / do | email | u = User.find_by_email (email) u.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg' u.save avslutte Da / ^ Jeg burde se "([^"] *) "$ / do | content | page.should have_content (content) end Da / ^ profilen skal vise "([^"] *) "$ / do | image | n = Nokogiri :: HTML (side.body) n.xpath (".// img [@ class = 'thumbnail']"). først ['src']. bør = ~ mønster_for (bilde) ende Gitt / ^ I endre e-posten med "([^"] *) "for" ([^ "] *)" $ / do | new_email, old_email | u = User.find_by_email (old_email) besøk edit_user_path (u) fill_in: email, med: new_email click_button 'Lagre slutt' Gitt / ^ Jeg laster opp overskriften avatar for "([^"] *) "$ / do | email | u = User.find_by_email (email) besøk edit_user_path (u) attach_file 'bruker [avatar_image]', Rails.root + 'spec / fixtures / mustache_avatar.jpg' click_button 'Lagre slutt' Gitt / ^ brukeren [ ) "har mustache avatar og jeg fjerner det $ / do | email | u = Bruker.find_by_email (e-post) u.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg' u.save besøk edit_user_path (u) sjekk "Fjern avatar image" click_button 'Save' slutt Da / ^ brukeren " ([^ "] *)" burde ha "([^"] *) "$ / do | email, image | u = User.find_by_email (email) besøk user_path (u) n = Nokogiri :: HTML (side.body) n.xpath (".// img [@ class = 'thumbnail']"). først ['src'] .should = ~ pattern_for (image) avslutte def pattern_for (image_name) case image_name når 'the placeholder avatar' /placehold.it/ når 'mustache avatar' / mustache_avatar / end end
Som et siste skritt kan vi enkelt legge til S3-støtte for å lagre avatarfiler. La oss gjenåpne konfig / initializers / dragonfly.rb
og oppdater konfigurasjonsblokken:
Dragonfly :: App [: images] .configure do | c | c.datastore = Dragonfly :: DataStorage :: S3DataStore.new c.datastore.configure do | d | d.bucket_name = 'dragonfly_tutorial' d.access_key_id = 'some_access_key_id' d.secret_access_key = 'some_secret_access_key' slutten ende med mindre% (utviklingstest agurk) .include? Rails.env
Dette vil fungere ut av boksen, og vil bare påvirke produksjonen (eller et annet miljø som ikke er spesifisert i filen). Dragonfly vil standard til filsystemlagring for alle andre tilfeller.
Jeg håper du fant denne opplæringen interessant, og klarte å hente noen få interessante spennende opplysninger.
Jeg oppfordrer deg til å referere til Dragonfly GitHub-siden for omfattende dokumentasjon og andre eksempler på brukstilfeller - selv utenfor en Rails-applikasjon.