Filuppladdningar är i allmänhet ett knepigt område i webbutveckling. I denna handledning lär vi oss att använda Dragonfly, en kraftfull Ruby-pärla som gör det enkelt och effektivt att lägga till någon form av uppladdningsfunktionalitet till ett Rails-projekt.
Vårt exempelapplikation visar en lista över användare, och för var och en av dem kommer vi att kunna ladda upp en avatar och lagra den. Dessutom tillåter Dragonfly oss att:
I denna lektion kommer vi att följa en BDD [Behavior Driven Development] -metod med hjälp av Gurka och RSpec.
Du måste ha Imagemagick installerat: du kan hänvisa till den här sidan för binärerna att installera. Eftersom jag bygger på en Mac-plattform använder du Homebrew, jag kan helt enkelt skriva brygga installera imagemagick
.
Du måste också klona en grundläggande Rails applikation som vi kommer att använda som utgångspunkt.
Vi börjar med att klona startförvaret och etablera våra beroenden:
git klon http: //[email protected]/cloud8421/tutorial_dragonfly_template.git cd tutorial_dragonfly_template
Denna applikation kräver åtminstone Ruby 1.9.2, men jag uppmanar dig att använda 1.9.3. Rails-versionen är 3.2.1. Projektet innehåller inte en .rvmrc
eller a .rbenv
fil.
Därefter kör vi:
bunt installera bunt exec rake db: setup db: test: förbereda db: frö
Detta kommer att ta hand om pärlaberoende och databasinstallation (vi kommer att använda sqlite, så du behöver inte oroa dig för databaskonfigurationen).
För att testa att allt fungerar som förväntat kan vi springa:
bunt exec rspec bunt exec gurka
Du bör upptäcka att alla tester har passerat. Låt oss granska gurkas produktion:
Funktion: hantera användarprofil Som användare För att hantera mina data vill jag komma åt min användarprofil sida Bakgrund: Med tanke på att en användare finns med e-postadressen "[email protected]" Scenario: visning av min profil Eftersom jag är på hemsidan När Jag följer "Profil" för "[email protected]" Då borde jag vara på profilsidan för "[email protected]" Scenario: redigering av min profil Eftersom jag är på profilsidan för "[email protected]" När Jag följer "Redigera" och jag ändrar min e-postadress med "[email protected]" och jag klickar på "Spara". Då borde jag vara på profilsidan för "[email protected]" Och jag ska se "User updated" 2 scenarier (2 passerade) 11 steg (11 passerade) 0m0.710s
Som du kan se beskriver dessa funktioner ett typiskt användarflöde: vi öppnar en användarsida från en lista, trycker på "Redigera" för att redigera användardata, ändra e-post och spara.
Försök nu att köra appen:
skenor s
Om du öppnar http :: // localhost: 3000
I webbläsaren hittar du en lista över användare (vi förfyllde databasen med 40 slumpmässiga poster tack vare Faker-pärlan).
För tillfället kommer var och en av användarna ha en liten, 16x16px avatar och en stor placeholder avatar på sin profilsida. Om du redigerar användaren kan du ändra dess uppgifter (förnamn, efternamn och lösenord), men om du försöker ladda upp en avatar sparas den inte.
Känn dig fri att bläddra i codebase: programmet använder Simple Form för att skapa formulärvyer och Twitter Bootstrap för CSS och layout, eftersom de integrerar perfekt och hjälper mycket för att påskynda prototypprocessen.
Vi börjar med att lägga till ett nytt scenario till funktioner / managing_profile.feature
:
... Scenario: Lägg till en avatar Eftersom jag är på profilsidan för "[email protected]" När jag följer "Redigera" Och jag laddar upp mustaschens avatar och jag klickar på "Spara" Då borde jag vara på profilsidan för "email @ example.com "Och profilen ska visa" mustaschens avatar "
Den här funktionen är ganska självförklarande, men det kräver några ytterligare steg att lägga till funktioner / step_definitions / user_steps.rb
:
... När / ^ Jag laddar upp mustaschens avatar $ / gör attach_file 'användare [avatar_image]', Rails.root + 'spec / fixtures / mustache_avatar.jpg' slut Då / ^ profilen ska visa "([^"] *) " $ / do | image | pattern = case image när "mustache avatar" / mustache_avatar / end n = Nokogiri :: HTML (page.body) n.xpath (".// img [@ class = 'thumbnail']") .first ['src']. should = ~ mönster slut
Det här steget förutsätter att du har en bild som heter mustache_avatar.jpg
inuti spec / fixturer
. Som du kanske gissar är detta bara ett exempel; det kan vara allt du vill ha.
Det första steget använder Capybara för att hitta användaren [avatar_image]
filfält och ladda upp filen. Observera att vi antar att vi kommer att ha en an avatar_image
attribut på Användare
modell.
Det andra steget använder Nokogiri (ett kraftfullt HTML / XML-parsing-bibliotek) och XPath för att analysera innehållet på den resulterande profilsidan och söka efter den första img
tagga med a Miniatyr
klass och test att src
attributet innehåller mustache_avatar
.
Om du kör gurka
Nu kommer detta scenario att utlösa ett fel, eftersom det inte finns något filfält med namnet vi angav. Nu är det dags att fokusera på Användare
modell.
Innan du integrerar Dragonfly med Användare
modell, låt oss lägga till ett par specifikationer till user_spec.rb
.
Vi kan lägga till ett nytt block strax efter attribut
sammanhang:
context "avatar attributes" gör det should respond_to (: avatar_image) det should allow_mass_assignment_of (: avatar_image) avsluta
Vi testar att användaren har en avatar_image
attribut och, eftersom vi kommer att uppdatera detta attribut via ett formulär, måste det vara tillgängligt (andra spec).
Nu kan vi installera Dragonfly: genom att göra så får vi dessa specs för att bli grön.
Låt oss lägga till följande rader i Gemfile:
pärla 'rack-cache', kräver: 'rack / cache' pärla 'dragonfly', '~> 0.9.10'
Därefter kan vi springa buntinstallation
. Rack-cache behövs under utveckling, eftersom det är det enklaste alternativet att ha HTTP-caching. Det kan även användas i produktionen, även om mer robusta lösningar (som Larn eller Squid) skulle vara bättre.
Vi måste också lägga till Dragonfly initializer. Låt oss skapa config / initializers / dragonfly.rb
fil och lägg till följande:
kräver "dragonfly" app = Dragonfly [: images] app.configure_with (: imagemagick) app.configure_with (: rails) app.define_macro (ActiveRecord :: Base,: image_accessor)
Det här är vaniljsländakonfigurationen: det sätter upp en Dragonfly-applikation och konfigurerar den med den nödvändiga modulen. Det lägger också till ett nytt makro till Active
att vi kommer att kunna använda för att förlänga vår Användare
modell.
Vi behöver uppdatera config / application.rb
, och lägg till ett nytt direktiv i konfigurationen (strax före config.generators
blockera):
config.middleware.insert 0, 'Rack :: Cache', verbose: true, metastore: URI.encode ("fil: # Rails.root / tmp / dragonfly / cache / meta"), affär: URI.encode ("fil: # Rails.root / tmp / dragonfly / cache / body") om inte Rails.env.production? config.middleware.insert_after 'Rack :: Cache', 'Dragonfly :: Middleware',: bilder
Utan att gå i mycket detalj, sätter vi upp Rack :: Cache
(förutom produktion, där den är aktiverad som standard), och inställning av Dragonfly för att använda den.
Vi lagrar våra bilder på disk, men vi behöver ett sätt att spåra föreningen med en användare. Det enklaste alternativet är att lägga till två kolumner till användartabellen med en migrering:
rails g migration add_avatar_to_users avatar_image_uid: sträng avatar_image_name: strängbunt exec rake db: migrera db: test: förbereda
Återigen är detta direkt från Dragonflys dokumentation: vi måste ha en avatar_image_uid
kolumn för att unikt identifiera avatarfilen och a avatar_image_name
för att lagra sitt ursprungliga filnamn (den senare kolumnen är inte absolut nödvändig, men det möjliggör generering av bildadresser som slutar med det ursprungliga filnamnet).
Slutligen kan vi uppdatera Användare
modell:
klass användare < ActiveRecord::Base image_accessor :avatar_image attr_accessible :email, :first_name, :last_name, :avatar_image…
De image_accessor
Metoden tillhandahålls av Dragonfly-initieraren, och det kräver bara ett attributnamn. Vi gör också samma attribut tillgängligt i raden nedan.
Löpning rspec
Nu borde visa alla specifikationer grön.
För att testa uppladdningsfunktionen kan vi lägga till ett sammanhang till users_controller_spec.rb
i PUT uppdatering
blockera:
kontext "avatar image" laddar! (: image_file) fixture_file_upload ('/ mustache_avatar.jpg', 'image / jpg') kontext "uppladdning av en avatar" gör innan du gör: uppdatera, id: user.id, användare: avatar_image: image_file avsluta det "ska effektivt lagra bildrekordet på användaren" gör user.reload user.avatar_image_name.should = ~ / mustache_avatar / endänden slutet
Vi kommer att återanvända samma fixtur och skapa en mock för uppladdningen med fixture_file_upload
.
Eftersom denna funktionalitet utnyttjar Dragonfly behöver vi inte skriva kod för att få den att passera.
Vi måste nu uppdatera våra synpunkter för att visa avataren. Låt oss börja från användarvisningssidan och öppna app / views / användare / show.html.erb
och uppdatera den med följande innehåll:
<% 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" %>
Därefter kan vi uppdatera app / views / användare / 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 visa användarens avatar med ett enkelt samtal till @ user.avatar_image.url
. Detta kommer att returnera en url till en icke-modifierad version av den avatar som laddats upp av användaren.
Om du kör gurka
Nu ser du den gröna funktionen. Du kan också prova det i webbläsaren också!
Vi är implicit beroende av CSS för att ändra storlek på bilden om den är för stor för dess behållare. Det är ett skakigt förhållningssätt: vår användare kan ladda upp icke-kvadratiska avatarer eller en mycket liten bild. Dessutom serverar vi alltid samma bild, utan för mycket oro för sidstorlek eller bandbredd.
Vi behöver jobba på två olika områden: lägga till några valideringsregler till avataruppladdningen och specificera bildstorlek och förhållande med Dragonfly.
Vi börjar med att öppna user_spec.rb
fil och lägga till ett nytt spec-block:
kontext "avatar attribut" gör% w (avatar_image retained_avatar_image remove_avatar_image) .each do | attr | det should respond_to (attr.to_sym) slutet% w (avatar_image retained_avatar_image remove_avatar_image) .each do | attr | det should allow_mass_assignment_of (attr.to_sym) avsluta "bör validera filstorleken för avataren" gör user.avatar_image = Rails.root + 'spec / fixtures / huge_size_avatar.jpg' user.should_not be_valid # storlek är> 100 KB avsluta det "bör validera formatet för avataren" gör user.avatar_image = Rails.root + 'spec / fixtures / dummy.txt' user.should_not be_valid slutändan
Vi testar för närvaro och tillåter "masstilldelning" för ytterligare attribut som vi ska använda för att förbättra användarformuläret (: retained_avatar_image
och : remove_avatar_image
).
Dessutom testar vi också att vår användarmodell inte accepterar stora uppladdningar (mer än 200 kB) och filer som inte är bilder. För båda fallen måste vi lägga till två fixturfiler (en bild med det angivna namnet och vars storlek är mer än 200 KB och en textfil med något innehåll).
Som vanligt kommer körningen av dessa specifikationer inte att få oss till grön. Låt oss uppdatera användarmodellen för att lägga till dessa valideringsregler:
... attr_accessible: email,: first_name,: last_name,: avatar_image,: retained_avatar_image,: remove_avatar_image ... validates_size_of: avatar_image, maximum: 100.kilobytes validates_property: format, av:: avatar_image, i: [: jpeg,: png, : jpg] validates_property: mime_type, av:: avatar_image, i: ['image / jpg', 'image / jpeg', 'bild / png', 'image / gif'], case_sensitive: false
Dessa regler är ganska effektiva: Observera att vi, förutom att kontrollera formatet, även kontrollerar mime-typen för bättre säkerhet. Att vara en bild tillåter vi jpg-, png- och gif-filer.
Våra specifikationer bör gå nu, så det är dags att uppdatera vyerna för att optimera bildbelastningen.
Som vanligt använder Dragonfly ImageMagick för att dynamiskt bearbeta bilder när det begärs. Förutsatt att vi har en användare
Exempel i en av våra åsikter, vi kunde då:
user.avatar_image.thumb ('100x100'). url user.avatar_image.process (: gråskala) .url
Dessa metoder skapar en bearbetad version av den här bilden med en unik hash och tack vare vårt cacheringslager, kommer ImageMagick att ringas en gång per bild. Därefter serveras bilden direkt från cachen.
Du kan använda många inbyggda metoder eller helt enkelt bygga din egen, Dragonfly dokumentation har många exempel.
Låt oss återvända till användaren redigera
sida och uppdatera visningskoden:
... <% if @user.avatar_image.present? %> <%= image_tag @user.avatar_image.thumb('400x400#').url, class: 'thumbnail' %> <% else %>...
Vi gör samma för användaren show
sida:
... <% if @user.avatar_image.present? %> <%= image_tag @user.avatar_image.thumb('400x400#').url, class: 'thumbnail' %> <% else %>...
Vi tvingar bildstorleken till 400 x 400 pixlar. De #
parameteren instruerar också ImageMagick att beskära bilden som håller en central gravitation. Du kan se att vi har samma kod på två ställen, så låt oss reflektera detta till en partiell kallad visningar / användare / _avatar_image.html.erb
<% if @user.avatar_image.present? %> <%= image_tag @user.avatar_image.thumb('400x400#').url, class: 'thumbnail' %> <% else %> <% end %>
Då kan vi ersätta innehållet i .Miniatyr
behållare med ett enkelt samtal till:
<%= render 'avatar_image' %>
Vi kan göra ännu bättre genom att flytta argumentet från tumme
ut ur partiet. Låt oss uppdatera _avatar_image.html.erb
:
<% if user.avatar_image.present? %> <%= image_tag user.avatar_image.thumb(args).url %> <% else %> & text = Super + cool + avatar "alt =" Super cool avatar "> <% end %>
Vi kan nu ringa vårt parti med två argument: en för önskad aspekt och en för användaren:
<%= render 'avatar_image', args: '400x400#', user: @user %>
Vi kan använda koden ovan i redigera
och show
åsikter, medan vi kan kalla det på följande sätt inuti visningar / användare / _user_table.html.erb
, där vi visar de små miniatyrerna.
...<%= link_to 'Profile', user_path(user) %> <%= render 'avatar_image', args: '16x16#', user: user %> <%= user.first_name %> ...
I båda fallen utför vi också en Regex på aspekten för att extrahera en sträng som är kompatibel med placehold.it-tjänsten (dvs. att ta bort icke alfanumeriska tecken).
Dragonfly skapar ytterligare två attribut som vi kan använda i en form:
retained_avatar_image
: Det lagrar den uppladdade bilden mellan omladdningar. Om valideringar för ett annat formulärfält (säger e-post) misslyckas och sidan laddas om, är den uppladdade bilden fortfarande tillgänglig utan att behöva ladda upp den igen. Vi använder den direkt i formuläret.remove_avatar_image
: När det är sant kommer den nuvarande avatarbilden att raderas både från användarregistret och disken.Vi kan testa avlägsnandet av avatar genom att lägga till ytterligare en spec till users_controller_spec.rb
, i avatar bild
blockera:
... kontext "ta bort en avatar" gör innan användaren.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg' user.save avsluta det 'ska ta bort avataren från användaren' put: update, id: user. id, användare: remove_avatar_image: "1" user.reload user.avatar_image_name.should be_nil slutet slutet ...
Återigen kommer Dragonfly att få denna spec att passera automatiskt som vi redan har remove_avatar_image
attribut tillgängligt för användarens instans.
Låt oss sedan lägga till en annan funktion till managing_profile.feature
:
Scenario: Ta bort en avatar Med tanke på användaren med e-postadressen "[email protected]" har mustaschens avatar och jag är på profilsidan för "[email protected]" När jag följer "Redigera" Och jag kolla "Ta bort avatar image" Och jag klickar på "Spara" Då borde jag vara på profilsidan för "[email protected]" och profilen ska visa "placeholder avatar"
Som vanligt behöver vi lägga till några steg till user_steps.rb
och uppdatera en för att lägga till en Regex för platshållaren avatar:
Given / ^ användaren med email "([^"] *) "har mustasch avatar $ / do | email | u = User.find_by_email (email) u.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg 'u.save slutar när / ^ jag kontrollerar "([^"] *) "$ / do | checkbox | kryssrutan slutar Då / ^ ska profilen visa "([^"] *) "$ / do | image | pattern = case image när" placeholder avatar "/placehold.it/ när" mustasch avatar "/ mustache_avatar / end n = Nokogiri :: HTML (page.body) n.xpath (".// img [@ class = 'thumbnail']"). först ['src'].
Vi behöver också lägga till ytterligare två fält till redigera
form:
...<%= f.input :retained_avatar_image, as: :hidden %> <%= f.input :avatar_image, as: :file, label: false %> <%= f.input :remove_avatar_image, as: :boolean %>...
Detta kommer att få vår funktion att passera.
För att undvika att ha en stor och för detaljerad funktion kan vi testa samma funktionalitet i en begäranspec.
Låt oss skapa en ny fil som heter spec / förfrågningar / user_flow_spec.rb
och lägg till det här innehållet:
kräva "spec_helper" beskriv "Användarflöde" låt! (: användare) Fabrikat (: användare, email: "[email protected]") beskriv "visa profilen" gör det "ska visa profilen för användaren" besök "/" page.find ('tr', text: user.email) .click_link ("Profile") current_path = URI.parse (current_url). path current_path.should == user_path (user) änden beskriver "uppdatering profildata "gör det" ska spara ändringarna "besök" / "page.find (" tr ", text: user.email) .click_link (" Profile ") click_link" Redigera "fill_in: email, with:" new_email @ example.com "click_button" Spara "current_path.should == user_path (user) page.should have_content" Användare uppdaterad "änden beskriver" hantera avataren "gör det" ska spara den uppladdade avataren "gör user.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg' user.save besök user_path (user) click_link 'Redigera' attach_file 'användare [avatar_image]', Rails.root + 'spec / fixtures / mustache_avatar.jpg' click_button 'Spara' current_path.should == user_path (user) page.should have_con tält "Användare uppdaterad" n = Nokogiri :: HTML (page.body) n.xpath (".// img [@ class = 'thumbnail']"). första ['src']. should = ~ / mustache_avatar / end det ska "ta bort avataren om det behövs" gör user.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg' user.save besök user_path (användare) click_link 'Redigera' kolla "Ta bort avatar image" click_button 'Spara' current_path .should == user_path (user) page.should have_content "Användare uppdaterad" n = Nokogiri :: HTML (page.body) n.xpath (".// img [@ class = 'thumbnail']"). src ']. should = ~ /placehold.it/ slutet änden
Spec encapsulates alla de steg vi använde för att definiera vår huvudsakliga funktion. Det testar noggrant markeringen och flödet, så vi kan se till att allt fungerar ordentligt på granulär nivå.
Nu kan vi förkorta managing_profile.feature
:
Funktion: hantera användarprofil Som användare För att hantera mina data vill jag komma åt min användarprofil sida Bakgrund: Eftersom en användare finns med e-postadressen "[email protected]" Scenario: Redigering av min profil Eftersom jag ändrar e-postmeddelandet med "new_mail @ example.com "för" [email protected] "Då ska jag se" Användare uppdaterad "Scenario: Lägg till en avatar Eftersom jag laddar upp mustaschens avatar för" [email protected] "Då ska profilen visa" mustaschens avatar " Scenario: Ta bort en avatar Med tanke på användaren "[email protected]" har mustaschens avatar och jag tar bort den. Då ska användaren "[email protected]" ha "placeholder avatar"
Uppdaterad user_steps.rb
:
Givet / ^ en användare existerar med e-post "([^"] *) "$ / do | email | Factory (: användare, email: email) end Given / ^ användaren med e-post" mustaschens avatar $ / do | email | u = User.find_by_email (email) u.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg' u.save slutet Då / ^ ska jag se "([^"] *) "$ / do | content | page.should have_content (content) avsluta Då / ^ profilen ska visa "([^"] *) "$ / do | image | n = Nokogiri :: HTML (page.body) n.xpath (".// img [@ class = 'thumbnail']"). första ['src']. borde = ~ mönster_for ändra e-post med "([^"] *) "för" ([^ "] *)" $ / do | new_email, old_email | u = User.find_by_email (old_email) besök edit_user_path (u) fill_in: email, with: new_email click_button "Spara" slutgiltigt / ^ Jag laddar upp mustaschens avatar för "([^"] *) "$ / do | email | u = User.find_by_email (email) besök edit_user_path (u) attach_file 'användare [avatar_image]', Rails.root + 'spec / fixtures / mustache_avatar.jpg' click_button 'Spara' slutgiltig / ^ användaren "([^"] * ) "har mustaschens avatar och jag tar bort det $ / do | email | u = User.find_by_email (email) u.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg' u.save besöka edit_user_path (u) kolla "Ta bort avatar image" click_button "Spara" slut Då / ^ användaren " ([^ "] *)" ska ha "([^"] *) "$ / do | email, image | u = User.find_by_email (email) besök user_path (u) n = Nokogiri :: HTML (page.body) n.xpath (".// img [@ class = 'thumbnail']"). första ['src'] .should = ~ pattern_for (bild) slut def pattern_for (bildnamn) case image_name när "placeholder avatar" /placehold.it/ när "mustache avatar" / mustache_avatar / endänden
Som ett sista steg kan vi enkelt lägga till S3-support för att lagra avatarfilerna. Låt oss åter öppna config / initializers / dragonfly.rb
och uppdatera konfigurationsblocket:
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' slutändan om inte% (utvecklingstest gurka). inkludera? Rails.env
Detta kommer att fungera ur rutan och påverkar endast produktionen (eller annan miljö som inte anges i filen). Dragonfly kommer som standard att lagra filsystem för alla andra fall.
Jag hoppas att du hittade den här handledningen intressant, och lyckades hämta några intressanta informationer.
Jag uppmanar dig att hänvisa till Dragonfly GitHub-sidan för omfattande dokumentation och andra exempel på användarfall - även utanför en Rails-applikation.