Încărcările de fișiere sunt, în general, o zonă dificilă în dezvoltarea web-ului. În acest tutorial, vom învăța cum să folosim Dragonfly, o bijuterie puternică Ruby care face ușor și eficient adăugarea oricărui tip de funcționalitate de încărcare la un proiect Rails.
Aplicația noastră de probă va afișa o listă de utilizatori, iar pentru fiecare dintre ei, vom putea să încărcați un avatar și să îl stocăm. În plus, Dragonfly ne va permite:
În această lecție vom urmări o abordare BDD [Behavior Driven Development], folosind Cucumber și RSpec.
Va trebui să aveți instalat Imagemagick: puteți consulta această pagină pentru instalarea fișierelor binare. Deoarece am bazat pe o platformă Mac, folosiți Homebrew, pot pur și simplu tasta brew instala imagemagick
.
De asemenea, va trebui să clonați o aplicație Rails de bază pe care o vom folosi ca punct de plecare.
Vom începe prin clonarea depozitului de pornire și prin înființarea dependențelor noastre:
clona git http: //[email protected]/cloud8421/tutorial_dragonfly_template.git cd tutorial_dragonfly_template
Această aplicație cere cel puțin Ruby 1.9.2 să ruleze, cu toate acestea, vă încurajez să utilizați 1.9.3. Versiunea Rails este 3.2.1. Proiectul nu include a .rvmrc
sau a .rbenv
fişier.
Apoi, rulați:
pachet de instalare pachet db: setup db: test: pregăti db: semințe
Acest lucru va avea grijă de dependențele de bijuterii și de configurarea bazei de date (vom folosi sqlite, deci nu trebuie să vă faceți griji în legătură cu configurarea bazei de date).
Pentru a testa că totul funcționează conform așteptărilor, putem rula:
bundle exec rspec bundle exec castravete
Ar trebui să găsiți că au trecut toate testele. Să revizuim producția de castraveți:
Caracteristică: gestionarea profilului utilizatorului Ca utilizator Pentru a gestiona datele mele Vreau să accesez pagina de profil a utilizatorului Poziție: Dat fiind un utilizator există cu e-mail "[email protected]" Scenariu: vizualizarea profilului meu Având în vedere că sunt pe pagina de pornire Când Urmărește "Profil" pentru "[email protected]" Atunci ar trebui să fiu pe pagina de profil pentru "[email protected]" Scenariu: editarea profilului meu Având în vedere că sunt pe pagina de profil pentru "[email protected]" Când Urmărește "Editează" Și îmi schimb emailul cu "[email protected]" Și fac clic pe "Salvează" Apoi ar trebui să fiu pe pagina de profil pentru "[email protected]" Și ar trebui să văd "Scenariu actualizat de utilizator" 2 (2 au trecut) 11 pași (11 au trecut) 0m0.710s
După cum puteți vedea, aceste caracteristici descriu un flux de lucru tipic pentru utilizatori: deschidem o pagină de utilizator dintr-o listă, apăsăm pe "Editare" pentru a edita datele utilizatorului, a schimba e-mailul și a salva.
Acum, încercați să rulați aplicația:
rails s
Dacă vă deschideți http :: // localhost: 3000
în browser, veți găsi o listă de utilizatori (am pre-populat baza de date cu 40 de înregistrări aleatoare datorită gemului Faker).
Pentru moment, fiecare dintre utilizatori va avea un avatar mic, de 16x16px și un avatar mare placeholder pe pagina lor de profil. Dacă editați utilizatorul, veți putea schimba detaliile (numele, prenumele și parola), dar dacă încercați să încărcați un avatar, acesta nu va fi salvat.
Simțiți-vă liber să răsfoiți codul: aplicația folosește formularul simplu pentru a genera vizualizări de formate și Twitter Bootstrap pentru CSS și aspect, deoarece acestea se integrează perfect și ajută foarte mult la accelerarea procesului de prototipare.
Vom începe prin a adăuga un nou scenariu la Caracteristici / managing_profile.feature
:
... Scenariu: adăugarea unui avatar Având în vedere că sunt pe pagina de profil pentru "[email protected]" Când urmez "Editare" Și am încărcat avatarul mustașului Și fac clic pe "Salvează" Apoi ar trebui să fiu pe pagina de profil pentru "email @ example.com "Și profilul ar trebui să arate" avatarul mustașului "
Această caracteristică este destul de explicită, însă necesită câțiva pași suplimentari pentru a adăuga Caracteristici / step_definitions / user_steps.rb
:
... Când / ^ Incarc avatarul mustașului $ / to attach_file 'user [avatar_image]', Rails.root + 'spec / fixtures / mustache_avatar.jpg' sfârșit Apoi / ^ profilul ar trebui să afișeze " $ / a | image | pattern = imaginea casei când "mustața avatar" / mustache_avatar / end n = Nokogiri :: HTML (pag.body) n.xpath (".// img [@ class = 'thumbnail' .first ['src'] ar trebui să = = sfârșitul modelului
Acest pas presupune că aveți o imagine numită mustache_avatar.jpg
interior spec / luminări
. Așa cum ați putea ghici, acesta este doar un exemplu; poate fi orice vrei tu.
Primul pas folosește Capybara pentru a găsi user [avatar_image]
fișier și încărcați fișierul. Rețineți că presupunem că vom avea un an avatar_image
atribut pe Utilizator
model.
Al doilea pas folosește Nokogiri (o bibliotecă puternică de parsing HTML / XML) și XPath pentru a analiza conținutul paginii de profil rezultate și pentru a căuta prima img
etichetați cu a miniatură
clasa și testați că src
atributul conține mustache_avatar
.
Dacă alergi castravete
acum, acest scenariu va declanșa o eroare, deoarece nu există un câmp de fișiere cu numele pe care l-am specificat. Acum este timpul să vă concentrați asupra Utilizator
model.
Înainte de a integra Dragonfly cu Utilizator
model, să adăugăm câteva specificații user_spec.rb
.
Putem adăuga un nou bloc imediat după atribute
context:
context "avatar attributes" face ar trebui să răspundă la (: avatar_image) it should allow_mass_assignment_of (: avatar_image) end
Testează faptul că utilizatorul are a avatar_image
atributul și, pe măsură ce vom actualiza acest atribut printr-un formular, acesta trebuie să fie accesibil (al doilea spec).
Acum putem instala Dragonfly: făcând asta, vom obține aceste specificații pentru a deveni verde.
Să adăugăm următoarele linii la Gemfile:
gem 'rack-cache', necesită: 'rack / cache' gem 'dragonfly', '~> 0.9.10'
În continuare, putem fugi instalare pachet
. Rack-cache-ul este necesar în dezvoltare, deoarece este cea mai simplă opțiune de a avea cache-ul HTTP. Poate fi folosit si in productie, chiar daca solutii mai robuste (cum ar fi Varnish sau Squid) ar fi mai bine.
De asemenea, trebuie să adăugăm inițializatorul Dragonfly. Să creăm config / initializatori / dragonfly.rb
fișier și adăugați următoarele:
cereți aplicația dragonfly = Dragonfly [: images] app.configure_with (: imagemagick) app.configure_with (: rails) app.define_macro (ActiveRecord :: Base, image_accessor)
Aceasta este configurația vaniliei Dragonfly: instalează o aplicație Dragonfly și o configurează cu modulul necesar. De asemenea, adaugă o nouă macrocomandă ActiveRecord
pe care o vom putea folosi pentru a ne extinde Utilizator
model.
Trebuie să ne actualizăm config / application.rb
, și adăugați o nouă directivă la configurație (chiar înainte de config.generators
bloc):
config.middleware.insert 0, 'Rack :: Cache', verbose: true, metastore: URI.encode ("fișier: # Rails.root / tmp / dragonfly / cache / meta"), entitate: URI.encode ("fișier: # Rails.root / tmp / dragonfly / cache / body") cu excepția cazului în care Rails.env.production? config.middleware.insert_after 'Rack :: Cache', 'Dragonfly :: Middleware',: imagini
Fără a intra în detaliu, suntem înființați Rack :: Cache
(cu excepția producției, unde este activată implicit) și configurarea Dragonfly pentru al utiliza.
Ne vom păstra imaginile pe disc, cu toate acestea, avem nevoie de o modalitate de a urmări asocierea cu un utilizator. Cea mai simplă opțiune este să adăugați două coloane în tabelul de utilizatori cu o migrare:
rails g migrație add_avatar_to_users avatar_image_uid: șir avatar_image_name: șir de șir executări rake db: migrează db: test: pregăti
Încă o dată, acest lucru este direct din documentația Dragonfly: trebuie să avem a avatar_image_uid
pentru a identifica în mod unic fișierul avatar și a avatar_image_name
pentru a stoca numele original al fișierului (ultima coloană nu este strict necesară, dar permite generarea urlărilor de imagine care se termină cu numele original al fișierului).
În cele din urmă, putem actualiza Utilizator
model:
utilizator de clasă < ActiveRecord::Base image_accessor :avatar_image attr_accessible :email, :first_name, :last_name, :avatar_image…
image_accessor
metoda este pusă la dispoziție de inițiatorul Dragonfly și necesită doar un nume de atribut. De asemenea, facem același atribut accesibil în rândul de mai jos.
Alergare rspec
acum ar trebui să arate toate specificațiile verde.
Pentru a testa funcția de încărcare, putem adăuga un context la users_controller_spec.rb
în Actualizare PUT
bloc:
Context "avatar imagine" nu permite! (: image_file) fixture_file_upload ('/ mustache_avatar.jpg', 'image / jpg') context "uploading an avatar" avatar_image: image_file terminați-l "ar trebui să salveze efectiv înregistrarea imaginii pe utilizator" faceți user.reload user.avatar_image_name.should = ~ / mustache_avatar / end end end
Vom reutiliza același dispozitiv și vom crea un mock pentru încărcare cu fixture_file_upload
.
Pe măsură ce această funcție funcționează pe Dragonfly, nu este nevoie să scriem codul pentru a trece.
Acum trebuie să ne actualizăm opiniile pentru a afișa avatarul. Să începem de pe pagina de afișare a utilizatorului și să o deschidem app / opinii / utilizatori / show.html.erb
și actualizați-l cu următorul conținut:
<% 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" %>
Apoi, putem actualiza app / opinii / utilizatori / 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" %>
Putem arata avatar-ul utilizatorului cu un simplu apel la @ user.avatar_image.url
. Aceasta va returna o adresă URL către o versiune nemodificată a avatarului încărcat de utilizator.
Dacă alergi castravete
acum, veți vedea caracteristica verde. Simțiți-vă liber să-l încercați și în browser!
Implicăm în mod implicit pe CSS să redimensionăm imaginea dacă este prea mare pentru containerul său. Este o abordare neclară: utilizatorul nostru ar putea încărca avatare non-pătrat sau o imagine foarte mică. În plus, oferim întotdeauna aceeași imagine, fără prea multă îngrijorare pentru dimensiunea paginii sau pentru lățimea de bandă.
Trebuie să lucrăm în două domenii diferite: adăugarea unor reguli de validare la încărcarea avatarului și specificarea dimensiunii imaginii și a raportului cu Dragonfly.
Vom începe prin deschiderea user_spec.rb
fișier și adăugarea unui nou bloc spec.
context "avatar atribute" nu% w (avatar_image retained_avatar_image remove_avatar_image). it ar trebui să răspund_to (attr.to_sym) sfârșitul% w (avatar_image retained_avatar_image remove_avatar_image) .exe | attr | acesta should allow_mass_assignment_of (attr.to_sym) să termine "trebuie să valideze dimensiunea fișierului avatarului" do user.avatar_image = Rails.root + 'spec / fixtures / huge_size_avatar.jpg "user.should_not be_valid # size is> 100 KB end "ar trebui să valideze formatul avatarului" do user.avatar_image = Rails.root + "spec / fixtures / dummy.txt" user.should_not be_valid end end
Testează prezența și permite "atribuirea în masă" pentru atribute suplimentare pe care le vom folosi pentru a îmbunătăți formularul de utilizator (: retained_avatar_image
și : remove_avatar_image
).
În plus, încercăm, de asemenea, ca modelul nostru de utilizator să nu accepte încărcări mari (mai mult de 200 KB) și fișiere care nu sunt imagini. În ambele cazuri, trebuie să adăugăm două fișiere de fixare (o imagine cu numele specificat și a cărei dimensiune este mai mare de 200 KB și un fișier text cu orice conținut).
Ca de obicei, rularea acestor specificații nu ne va duce la verde. Să actualizăm modelul de utilizator pentru a adăuga acele reguli de validare:
... atr_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, de: avatar_image, in: [: jpeg, : jpg] validates_property: mime_type, de:: avatar_image, în: ['image / jpg', 'image / jpeg', 'image / png', 'image / gif'], case_sensitive: false
Aceste reguli sunt destul de eficiente: rețineți că, pe lângă verificarea formatului, verificăm și tipul de mime pentru o mai bună siguranță. Fiind o imagine, permitem fișiere jpg, png și gif.
Specificațiile noastre ar trebui să treacă acum, deci este timpul să actualizăm vizualizările pentru a optimiza încărcarea imaginii.
În mod implicit, Dragonfly utilizează ImageMagick pentru a procesa dinamic imagini atunci când este solicitat. Presupunând că avem a utilizator
exemplu, într-una dintre punctele noastre de vedere, am putea:
user.avatar_image.thumb ('100x100'). url user.avatar_image.process (: greyscale) .url
Aceste metode vor crea o versiune prelucrată a acestei imagini cu un hash unic și datorită stratului nostru de caching, ImageMagick va fi apelat o dată pe imagine. După aceasta, imaginea va fi difuzată direct din memoria cache.
Aveți posibilitatea să utilizați mai multe metode integrate sau pur și simplu să vă construiți propria, documentația Dragonfly are o mulțime de exemple.
Să ne revedem utilizatorul Editați | ×
pagina și actualizați codul de vizualizare:
... <% if @user.avatar_image.present? %> <%= image_tag @user.avatar_image.thumb('400x400#').url, class: 'thumbnail' %> <% else %>...
Vom face același lucru pentru utilizator spectacol
pagină:
... <% if @user.avatar_image.present? %> <%= image_tag @user.avatar_image.thumb('400x400#').url, class: 'thumbnail' %> <% else %>...
Forțăm dimensiunea imaginii la 400 x 400 pixeli. #
parametru instruiește, de asemenea, ImageMagick pentru a decupa imaginea păstrând o gravitate centrală. Puteți observa că avem același cod în două locuri, deci refăctorizăm acest lucru într-un apel parțial vizualizari / utilizatorii / _avatar_image.html.erb
<% if @user.avatar_image.present? %> <%= image_tag @user.avatar_image.thumb('400x400#').url, class: 'thumbnail' %> <% else %> <% end %>
Apoi putem înlocui conținutul .miniatură
container cu un simplu apel la:
<%= render 'avatar_image' %>
Putem face chiar mai bine prin mutarea argumentului deget mare
din partea parțială. Să actualizăm _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 %>
Acum ne putem numi parțial cu două argumente: unul pentru aspectul dorit și unul pentru utilizator:
<%= render 'avatar_image', args: '400x400#', user: @user %>
Putem folosi fragmentul de mai sus Editați | ×
și spectacol
vederile, în timp ce îl putem numi în felul următor vizualizari / utilizatorii / _user_table.html.erb
, unde arată miniaturile mici.
...<%= link_to 'Profile', user_path(user) %> <%= render 'avatar_image', args: '16x16#', user: user %> <%= user.first_name %> ...
În ambele cazuri, efectuăm și un Regex pe aspect pentru a extrage un șir compatibil cu serviciul placehold.it (adică a elimina caractere non-alfanumerice).
Dragonfly creează două atribute suplimentare pe care le putem folosi într-o formă:
retained_avatar_image
: aceasta stochează imaginea încărcată între reîncărcări. Dacă validările pentru un alt câmp de formular (de exemplu e-mail) nu reușesc și pagina este reîncărcată, imaginea încărcată este încă disponibilă fără a fi nevoie să o reîncărcați. O vom folosi direct în formă.remove_avatar_image
: când este adevărat, imaginea curentă a avatarului va fi ștearsă atât din înregistrarea utilizatorului, cât și din disc.Putem testa eliminarea avatarului adăugând o spec users_controller_spec.rb
, în avatar imagine
bloc:
... context "înlăturarea unui avatar" înainte de a face user.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg "user.save end it" ar trebui să elimine avatarul utilizatorului "do put: update, id: user. id, utilizator: remove_avatar_image: "1" user.reload user.avatar_image_name.should be_nil end end ...
Încă o dată, Dragonfly va face ca această spec. Să treacă automat așa cum deja avem remove_avatar_image
atributul disponibil pentru instanța utilizatorului.
Să adăugăm apoi o altă caracteristică managing_profile.feature
:
Scenariul: eliminarea unui avatar Având în vedere că utilizatorul cu adresa de email "[email protected]" are avatarul de mustață Și eu sunt pe pagina de profil pentru "[email protected]" Când urmez "Editare" Și verific "Eliminați imaginea avatar" Și fac clic pe "Salvează" Apoi ar trebui să fiu pe pagina de profil pentru "[email protected]" Și profilul ar trebui să afișeze "avatarul placeholder"
Ca de obicei, trebuie să adăugăm câțiva pași user_steps.rb
și actualizați unul pentru a adăuga un Regex pentru avatarul placeholder:
Având în vedere / ^ utilizatorul cu e-mail "([^"] *) "are avatarul mustașului $ / do | email | u = User.find_by_email (email) u.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg 'u.save end Când / ^ verific "([^"] *) "$ / do | checkbox | bifați căsuța de validare Închideți apoi / ^ profilul ar trebui să afișeze "([^"] *) "$ / do | image | pattern = imagine imagine când 'the placeholder avatar' /placehold.it/ când 'mustața avatar' / mustache_avatar / end n = Nokogiri :: HTML (pag.body) n.xpath (".// img [@ class = 'thumbnail']").
De asemenea, trebuie să adăugăm două câmpuri suplimentare la Editați | ×
formă:
...<%= f.input :retained_avatar_image, as: :hidden %> <%= f.input :avatar_image, as: :file, label: false %> <%= f.input :remove_avatar_image, as: :boolean %>...
Aceasta va face ca funcția noastră să treacă.
Pentru a evita o caracteristică mare și prea detaliată, putem testa aceeași funcționalitate într-o specificație de solicitare.
Să creați un nou fișier numit spec / cereri / user_flow_spec.rb
și adăugați acest conținut la acesta:
("user", "e-mail: [email protected]") descrieți "vizualizarea profilului" face "ar trebui să afișeze profilul pentru utilizator" vizitați '/' page.find ('tr', text: user.email) .click_link ("Profil") current_path = URI.parse (curent_url) .path current_path.should == end_path (user) datele profilului "face it" ar trebui să salveze modificările "do visit" / "page.find ('tr', text: user.email) .click_link (" Profil ") click_link 'Edit' fill_in: example.com "click_button" Salveaza "current_path.should == user_path (user) page.should_content" end user "end end descrie" gestionarea avatarului "a face" ar trebui sa salveze avatarul uploadat "do user.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg' user.save vizitați user_path (user) click_link 'Editare' atașament_file 'user [avatar_image]', Rails.root + 'spec / fixtures / mustache_avatar.jpg' click_button 'Salvează' current_path.should == user_path (utilizator) cant "Utilizatorul actualizat" n = Nokogiri :: HTML (page.body) n.xpath (".// img [@ class = 'thumbnail']"). acesta "ar trebui să elimine avatarul dacă este solicitat" do user.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg' user.save vizitați user_path (user) click_link 'Editați' check "Eliminare imagine avatar" click_button 'Salvează' .should == user_path (utilizator) page.should have_content "Actualizat de utilizator" n = Nokogiri :: HTML (page.body) n.xpath (".// img [@ class = 'thumbnail' src '] ar trebui să fie = ~ /placehold.it/ sfârșitul final
Specificația încapsulează toți pașii pe care i-am folosit pentru a defini caracteristica principală. Testează temeinic marcajul și fluxul, pentru a ne asigura că totul funcționează corect la un nivel granular.
Acum putem scurta managing_profile.feature
:
Caracteristică: gestionarea profilului de utilizator Ca utilizator Pentru a gestiona datele mele Vreau să accesez pagina de profil a utilizatorului Poziție: Dat fiind un utilizator există cu e-mail "[email protected]" Scenariu: editarea profilului meu Având în vedere că schimbați e-mailul cu "new_mail @ example.com "pentru" [email protected] "Apoi ar trebui să văd" User updated "Scenariu: adăugând un avatar Având în vedere că am încărcat avatarul de mustaș pentru" [email protected] "Atunci profilul ar trebui să afișeze" avatarul mustașului " Scenariu: eliminarea unui avatar Având în vedere faptul că utilizatorul "[email protected]" are avatarul de mușchi și îl elimină, atunci utilizatorul "[email protected]" ar trebui să aibă "avatarul placeholder"
La curent user_steps.rb
:
Având la dispoziție / ^ un utilizator există cu email "([^"] *) "$ / do | email | Factory (: user, email: email) mustafa avatar $ / do | email u = user.find_by_email (e-mail) u.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg' u.save end Apoi / ^ Ar trebui să văd "([^"] *) "$ / do | page.should have_content (content) end Apoi / ^ profilul ar trebui să afișeze "([^"] *) "$ / do | image | n = Nokogiri :: HTML (pag.body) n.xpath (".// img [@ class = 'thumbnail']"). schimbați e-mailul cu "([^"] *) "pentru" ([^ "] *)" $ / do | new_email, old_email | u = User.find_by_email (old_email) vizitați edit_user_path (u) fill_in: email, cu: new_email click_button " = User.find_by_email (email) vizitați edit_user_path (u) attach_file 'user [avatar_image]', Rails.root + 'spec / fixtures / mustache_avatar.jpg' click_button ' ) "are avatarul de mustață și îl elimină $ / do | email u = User.find_by_email (e-mail) u.avatar_image = Rails.root + 'spec / fixtures / mustache_avatar.jpg' u.save vizita edit_user_path (u) ([^ "] *)" ar trebui să aibă "([^"] *) "$ / do | e-mail, imagine | u = User.find_by_email (email) vizitați user_path (u) n = Nokogiri :: HTML (pag.body) n.xpath (".// img [@ class = 'thumbnail']"). .should = model_for (imagine) end def pattern_for (image_name) caz image_name atunci când 'the placeholder avatar' /placehold.it/ când 'mustața avatar' / mustache_avatar / end end
Ca ultimul pas, putem adăuga cu ușurință suportul S3 pentru a stoca fișierele de avatar. Să redeschidem config / initializatori / dragonfly.rb
și actualizați blocul de configurare:
Dragonfly :: App [: imagini] .configure nu | 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' sfârșitul final, cu excepția cazului în care% (castravete test de dezvoltare). include? Rails.env
Aceasta va ieși din cutie și va afecta numai producția (sau orice alt mediu care nu este specificat în fișier). Dragonfly va implicit să stocheze sistemul de fișiere pentru toate celelalte cazuri.
Sper că ați găsit acest tutorial interesant și ați reușit să luați câteva informații interesante.
Vă încurajez să vă referiți la pagina Dragonfly GitHub pentru o documentație extensivă și alte exemple de cazuri de utilizare - chiar și în afara unei aplicații Rails.