Cod Buggy Rails: Cele mai frecvente 10 greșeli pe care le fac dezvoltatorii Rails
Publicat: 2022-03-11Ruby on Rails („Rails”) este un cadru popular open source, bazat pe limbajul de programare Ruby, care se străduiește să simplifice și să simplifice procesul de dezvoltare a aplicațiilor web.
Rails este construit pe principiul convenției asupra configurației. Mai simplu spus, aceasta înseamnă că, în mod implicit, Rails presupune că dezvoltatorii săi experți vor urma convențiile de bune practici „standard” (pentru lucruri precum denumirea, structura codului și așa mai departe) și, dacă o faci, lucrurile vor funcționa pentru tine „auto -magic” fără a fi nevoie să specificați aceste detalii. Deși această paradigmă are avantajele ei, nu este lipsită de capcanele sale. Cel mai important, „magia” care se întâmplă în culise în cadru poate duce uneori la falsuri de cap, confuzie și „ce naiba se întâmplă?” tipuri de probleme. De asemenea, poate avea ramificații nedorite în ceea ce privește securitatea și performanța.
În consecință, în timp ce Rails este ușor de utilizat, nu este, de asemenea, greu de utilizat greșit. Acest tutorial analizează 10 probleme comune Rails, inclusiv cum să le evitați și problemele pe care le provoacă.
Greșeala obișnuită #1: Pune prea multă logică în controler
Rails se bazează pe o arhitectură MVC. În comunitatea Rails, am vorbit despre model gras, controler slab, dar mai multe aplicații Rails recente pe care le-am moștenit au încălcat acest principiu. Este prea ușor să mutați logica de vizualizare (care este mai bine găzduită într-un ajutor) sau logica de domeniu/model în controler.
Problema este că obiectul controlerului va începe să încalce principiul responsabilității unice, ceea ce face ca modificările viitoare ale bazei de cod să fie dificile și predispuse la erori. În general, singurele tipuri de logică pe care ar trebui să le aveți în controler sunt:
- Gestionarea sesiunilor și cookie-urilor. Aceasta poate include, de asemenea, autentificare/autorizare sau orice procesare suplimentară de cookie-uri pe care trebuie să o faceți.
- Alegerea modelului. Logica pentru găsirea obiectului model potrivit, având în vedere parametrii trecuți din cerere. În mod ideal, acesta ar trebui să fie un apel la o singură metodă de găsire care setează o variabilă de instanță care să fie folosită mai târziu pentru a reda răspunsul.
- Solicitați managementul parametrilor. Colectarea parametrilor de cerere și apelarea unei metode de model adecvate pentru a le persista.
- Redare/redirecționare. Redarea rezultatului (html, xml, json etc.) sau redirecționarea, după caz.
Deși acest lucru încă împinge limitele principiului responsabilității unice, este un fel de minimul strict pe care cadrul Rails ne cere să îl avem în controler.
Greșeala obișnuită #2: A pune prea multă logică în viziune
Motorul de șabloane Rails ieșit din cutie, ERB, este o modalitate excelentă de a crea pagini cu conținut variabil. Cu toate acestea, dacă nu ești atent, poți ajunge în curând cu un fișier mare care este un amestec de cod HTML și Ruby care poate fi dificil de gestionat și întreținut. Aceasta este, de asemenea, o zonă care poate duce la o mulțime de repetiții, ceea ce duce la încălcări ale principiilor DRY (nu te repeta).
Acest lucru se poate manifesta în mai multe moduri. Una este utilizarea excesivă a logicii condiționate în vederi. Ca exemplu simplu, luăm în considerare un caz în care avem disponibilă o metodă current_user
care returnează utilizatorul conectat în prezent. Adesea, vor ajunge să existe structuri logice condiționate ca aceasta în fișierele de vizualizare:
<h3> Welcome, <% if current_user %> <%= current_user.name %> <% else %> Guest <% end %> </h3>
O modalitate mai bună de a gestiona așa ceva este să vă asigurați că obiectul returnat de current_user
este întotdeauna setat, indiferent dacă cineva este conectat sau nu, și că răspunde la metodele utilizate în vizualizare într-un mod rezonabil (uneori denumit nul obiect). De exemplu, puteți defini asistentul current_user
în app/controllers/application_controller
astfel:
require 'ostruct' helper_method :current_user def current_user @current_user ||= User.find session[:user_id] if session[:user_id] if @current_user @current_user else OpenStruct.new(name: 'Guest') end end
Acest lucru vă va permite apoi să înlocuiți exemplul de cod de vizualizare anterior cu această linie simplă de cod:
<h3>Welcome, <%= current_user.name -%></h3>
Câteva bune practici suplimentare recomandate pentru Rails:
- Utilizați în mod corespunzător machetele de vizualizare și părțile parțiale pentru a încapsula lucrurile care se repetă în paginile dvs.
- Folosiți prezentatori/decoratori precum bijuteria Draper pentru a încapsula logica de construire a vederii într-un obiect Ruby. Puteți adăuga apoi metode în acest obiect pentru a efectua operații logice pe care altfel le-ați fi introdus în codul de vizualizare.
Greșeala obișnuită #3: A pune prea multă logică în model
Având în vedere îndrumarea pentru a minimiza logica în vizualizări și controlere, singurul loc rămas într-o arhitectură MVC pentru a pune toată acea logică ar fi în modele, nu?
Ei bine, nu chiar.
Mulți dezvoltatori Rails fac de fapt această greșeală și ajung să lipească totul în clasele lor de modele ActiveRecord
, ceea ce duce la fișiere mongo care nu numai că încalcă principiul responsabilității unice, ci sunt și un coșmar de întreținere.
Funcționalități precum generarea de notificări prin e-mail, interfața cu servicii externe, conversia în alte formate de date și altele asemenea nu au mare legătură cu responsabilitatea de bază a unui model ActiveRecord
, care ar trebui să facă puțin mai mult decât să găsească și să persiste date într-o bază de date.
Deci, dacă logica nu ar trebui să intre în vizualizări și nu ar trebui să intre în controlere și nu ar trebui să intre în modele, atunci unde ar trebui să meargă?
Introduceți obiecte Ruby vechi (PORO). Cu un cadru cuprinzător precum Rails, dezvoltatorii mai noi sunt adesea reticenți în a-și crea propriile clase în afara cadrului. Cu toate acestea, mutarea logicii din model în PORO este adesea exact ceea ce a ordonat medicul pentru a evita modelele prea complexe. Cu PORO, puteți încapsula lucruri precum notificările prin e-mail sau interacțiunile API în propriile clase, mai degrabă decât să le lipiți într-un model ActiveRecord
.
Deci, având în vedere asta, în general, singura logică care ar trebui să rămână în modelul tău este:
- Configurare
ActiveRecord
(adică relații și validări) - Metode simple de mutare pentru a încapsula actualizarea unui număr mic de atribute și salvarea lor în baza de date
- Accesați wrapper -urile pentru a ascunde informațiile interne ale modelului (de exemplu, o metodă
full_name
care combină câmpurilefirst_name
șilast_name
din baza de date) - Interogări sofisticate (adică, care sunt mai complexe decât o simplă
find
); în general, nu ar trebui să utilizați niciodată metodawhere
, sau orice alte metode de creare a interogărilor ca aceasta, în afara clasei de model în sine
Greșeala comună #4: Folosirea claselor de ajutor generice ca teren de gunoi
Această greșeală este într-adevăr un fel de corolar al greșelii #3 de mai sus. După cum sa discutat, cadrul Rails pune accent pe componentele numite (de exemplu, modelul, vizualizarea și controlerul) ale unui cadru MVC. Există definiții destul de bune ale tipurilor de lucruri care aparțin claselor fiecăreia dintre aceste componente, dar uneori s-ar putea să avem nevoie de metode care par să nu se încadreze în niciuna dintre cele trei.
Generatoarele de șine construiesc în mod convenabil un director de ajutor și o nouă clasă de ajutor pentru fiecare resursă nouă pe care o creăm. Devine, totuși, prea tentant să începeți să introduceți orice funcționalitate care nu se încadrează în mod oficial în model, vizualizare sau controler în aceste clase de ajutor.
Deși Rails este cu siguranță centrat pe MVC, nimic nu vă împiedică să vă creați propriile tipuri de clase și să adăugați directoare adecvate pentru a păstra codul pentru acele clase. Când aveți funcționalități suplimentare, gândiți-vă la ce metode se grupează și găsiți nume bune pentru clasele care dețin acele metode. Utilizarea unui cadru cuprinzător precum Rails nu este o scuză pentru a lăsa bunele practici de design orientat pe obiecte să treacă pe margine.
Greșeala comună #5: Folosirea prea multor pietre prețioase
Ruby și Rails sunt susținute de un ecosistem bogat de pietre prețioase care oferă împreună aproape orice capacitate la care se poate gândi un dezvoltator. Acest lucru este grozav pentru a construi rapid o aplicație complexă, dar am văzut și multe aplicații umflate în care numărul de pietre prețioase din Gemfile
al aplicației este disproporționat de mare în comparație cu funcționalitatea oferită.
Acest lucru cauzează mai multe probleme cu șine. Utilizarea excesivă a pietrelor prețioase face ca dimensiunea unui proces Rails să fie mai mare decât trebuie să fie. Acest lucru poate încetini performanța în producție. Pe lângă frustrarea utilizatorilor, acest lucru poate duce și la necesitatea unor configurații mai mari de memorie a serverului și a costurilor de operare crescute. De asemenea, durează mai mult pentru a porni aplicații Rails mai mari, ceea ce face dezvoltarea mai lentă și face ca testele automate să dureze mai mult (și, de regulă, testele lente pur și simplu nu sunt executate la fel de des).
Rețineți că fiecare bijuterie pe care o aduceți în aplicația dvs. poate avea, la rândul său, dependențe de alte pietre prețioase, iar acestea pot avea, la rândul lor, dependențe de alte pietre prețioase și așa mai departe. Adăugarea altor pietre prețioase poate avea astfel un efect de combinare. De exemplu, adăugarea bijuteriei rails_admin
va aduce încă 11 pietre prețioase în total, cu o creștere cu 10% față de instalarea de bază Rails.
În momentul scrierii acestui articol, o nouă instalare Rails 4.1.0 include 43 de pietre prețioase în fișierul Gemfile.lock
. Acest lucru este, evident, mai mult decât este inclus în Gemfile
și reprezintă toate pietrele prețioase pe care o mână de pietre standard Rails le aduc ca dependențe.
Luați în considerare cu atenție dacă costul suplimentar este util pe măsură ce adăugați fiecare bijuterie. De exemplu, dezvoltatorii vor adăuga adesea bijuteria rails_admin
, deoarece în esență oferă un front-end web frumos structurii modelului, dar într-adevăr nu este mult mai mult decât un instrument de navigare în baze de date. Chiar dacă aplicația dvs. necesită utilizatori admin cu privilegii suplimentare, probabil că nu doriți să le oferiți acces brut la baza de date și ați fi mai bine dezvăluit prin dezvoltarea propriei funcții de administrare mai simplificate decât prin adăugarea acestei bijuterii.
Greșeala comună #6: Ignorarea fișierelor de jurnal
În timp ce majoritatea dezvoltatorilor Rails sunt conștienți de fișierele jurnal implicite disponibile în timpul dezvoltării și în producție, adesea nu acordă suficientă atenție informațiilor din acele fișiere. În timp ce multe aplicații se bazează pe instrumente de monitorizare a jurnalelor, cum ar fi Honeybadger sau New Relic, în producție, este, de asemenea, important să țineți cont de fișierele de jurnal pe parcursul procesului de dezvoltare și testare a aplicației.

După cum am menționat anterior în acest tutorial, cadrul Rails face multă „magie” pentru tine, în special în modele. Definirea asocierilor în modelele dvs. face foarte ușor să trageți în relații și să aveți totul la dispoziție pentru opiniile dvs. Toate SQL-urile necesare pentru a vă completa obiectele modelului sunt generate pentru dvs. Grozav. Dar de unde știi că SQL-ul generat este eficient?
Un exemplu în care veți alerga adesea se numește problema de interogare N+1. Deși problema este bine înțeleasă, singura modalitate reală de a observa că se întâmplă este să revizuiți interogările SQL din fișierele dvs. jurnal.
Să presupunem, de exemplu, că aveți următoarea interogare într-o aplicație tipică de blog în care veți afișa toate comentariile pentru un set selectat de postări:
def comments_for_top_three_posts posts = Post.limit(3) posts.flat_map do |post| post.comments.to_a end end
Când ne uităm la fișierul jurnal al unei cereri care apelează această metodă, vom vedea ceva de genul următor, unde se face o singură interogare pentru a obține cele trei obiecte post, apoi se fac încă trei interogări pentru a obține comentariile fiecăruia dintre acele obiecte:
Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:13 -0700 Processing by PostsController#some_comments as HTML Post Load (0.4ms) SELECT "posts".* FROM "posts" LIMIT 3 Comment Load (5.6ms) ELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 1]] Comment Load (0.4ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 2]] Comment Load (1.5ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 3]] Rendered posts/some_comments.html.erb within layouts/application (12.5ms) Completed 200 OK in 581ms (Views: 225.8ms | ActiveRecord: 10.0ms)
Capacitatea de încărcare nerăbdătoare a ActiveRecord
în Rails face posibilă reducerea semnificativă a numărului de interogări, permițându-vă să specificați în avans toate asociațiile care urmează să fie încărcate. Acest lucru se face prin apelarea metodei includes
(sau preload
) pe obiectul Arel ( ActiveRecord::Relation
) care este construit. Cu includes
, ActiveRecord
asigură că toate asociațiile specificate sunt încărcate folosind numărul minim posibil de interogări; de exemplu:
def comments_for_top_three_posts posts = Post.includes(:comments).limit(3) posts.flat_map do |post| post.comments.to_a end end
Când este executat codul revizuit de mai sus, vedem în fișierul jurnal că toate comentariile au fost colectate într-o singură interogare în loc de trei:
Started GET "/posts/some_comments" for 127.0.0.1 at 2014-05-20 20:05:18 -0700 Processing by PostsController#some_comments as HTML Post Load (0.5ms) SELECT "posts".* FROM "posts" LIMIT 3 Comment Load (4.4ms) SELECT "comments".* FROM "comments" WHERE"comments "."post_id" IN (1, 2, 3) Rendered posts/some_comments.html.erb within layouts/application (12.2ms) Completed 200 OK in 560ms (Views: 219.3ms | ActiveRecord: 5.0ms)
Mult mai eficient.
Această soluție la problema N+1 este de fapt menită doar ca un exemplu al tipului de ineficiențe care pot exista „sub capotă” în aplicația dvs. dacă nu acordați o atenție adecvată. Ideea aici este că ar trebui să vă verificați fișierele jurnal de dezvoltare și de testare în timpul dezvoltării pentru a verifica (și a remedia!) ineficiențele în codul care creează răspunsurile dumneavoastră.
Revizuirea fișierelor jurnal este o modalitate excelentă de a fi informat asupra ineficiențelor din codul dvs. și de a le corecta înainte ca aplicația dvs. să intre în producție. În caz contrar, este posibil să nu fiți conștient de o problemă de performanță Rails rezultată până când sistemul dvs. este activ, deoarece setul de date cu care lucrați în dezvoltare și testare este probabil mult mai mic decât în producție. Dacă lucrați la o aplicație nouă, chiar și setul de date de producție poate începe mic, iar aplicația dvs. va părea că funcționează bine. Cu toate acestea, pe măsură ce setul de date de producție crește, problemele Rails de acest fel vor face ca aplicația dvs. să ruleze din ce în ce mai lent.
Dacă descoperiți că fișierele dvs. de jurnal sunt înfundate cu o mulțime de informații de care nu aveți nevoie, iată câteva lucruri pe care le puteți face pentru a le curăța (tehnicile de acolo funcționează pentru dezvoltare, precum și pentru jurnalele de producție).
Greșeala comună #7: Lipsa testelor automate
Ruby și Rails oferă în mod implicit capabilități puternice de testare automată. Mulți dezvoltatori Rails scriu teste foarte sofisticate folosind stiluri TDD și BDD și folosesc cadre de testare și mai puternice cu pietre prețioase precum rspec și castravete.
În ciuda cât de ușor este să adăugați testare automată la aplicația dvs. Rails, totuși, am fost foarte neplăcut surprins de câte proiecte am moștenit sau m-am alăturat unde nu au existat literalmente teste scrise (sau în cel mai bun caz, foarte puține) de către anterioară. echipă de dezvoltare. Deși există o mulțime de dezbateri despre cât de cuprinzătoare ar trebui să fie testarea dvs., este destul de clar că ar trebui să existe cel puțin unele teste automatizate pentru fiecare aplicație.
Ca regulă generală, ar trebui să existe cel puțin un test de integrare la nivel înalt scris pentru fiecare acțiune din controlerele dvs. La un moment dat în viitor, alți dezvoltatori Rails vor dori, cel mai probabil, să extindă sau să modifice codul sau să actualizeze o versiune Ruby sau Rails, iar acest cadru de testare le va oferi o modalitate clară de a verifica dacă funcționalitatea de bază a aplicației este lucru. Un avantaj suplimentar al acestei abordări este că oferă viitorilor dezvoltatori o delimitare clară a întregii colecții de funcționalități oferite de aplicație.
Greșeala comună #8: Blocarea apelurilor către servicii externe
Furnizorii terți de servicii Rails fac de obicei foarte ușor să-și integreze serviciile în aplicația dvs. prin intermediul pietrelor prețioase care înglobează API-urile lor. Dar ce se întâmplă dacă serviciul tău extern are o întrerupere sau începe să ruleze foarte lent?
Pentru a evita blocarea acestor apeluri, în loc să apelați aceste servicii direct în aplicația dvs. Rails în timpul procesării normale a unei cereri, ar trebui să le mutați într-un fel de serviciu de așteptare a joburilor în fundal, acolo unde este posibil. Unele pietre prețioase populare utilizate în aplicațiile Rails în acest scop includ:
- Lucru întârziat
- Resque
- Sidekiq
În cazurile în care nu este practic sau imposibil să delegați procesarea la o coadă de joburi de fundal, atunci va trebui să vă asigurați că aplicația dvs. are suficiente dispoziții de gestionare a erorilor și de fail-over pentru acele situații inevitabile când serviciul extern scade sau întâmpină probleme. . De asemenea, ar trebui să testați aplicația dvs. fără serviciul extern (poate prin eliminarea serverului pe care se află aplicația dvs. din rețea) pentru a verifica dacă nu are consecințe neprevăzute.
Greșeala comună #9: Căsătorirea cu migrarea bazelor de date existente
Mecanismul de migrare a bazei de date al Rails vă permite să creați instrucțiuni pentru a adăuga și elimina automat tabelele și rândurile bazei de date. Deoarece fișierele care conțin aceste migrări sunt denumite într-o manieră secvențială, le puteți reda de la începutul timpului pentru a aduce o bază de date goală la aceeași schemă ca și producția. Prin urmare, aceasta este o modalitate excelentă de a gestiona modificări granulare ale schemei bazei de date a aplicației dvs. și de a evita problemele Rails.
Deși acest lucru funcționează cu siguranță bine la începutul proiectului dvs., pe măsură ce timpul trece, procesul de creare a bazei de date poate dura destul de mult și, uneori, migrările sunt deplasate, inserate defectuoase sau introduse din alte aplicații Rails folosind același server de bază de date.
Rails creează o reprezentare a schemei dvs. curente într-un fișier numit db/schema.rb
(în mod implicit), care este de obicei actualizat atunci când se execută migrarea bazei de date. Fișierul schema.rb
poate fi generat chiar și atunci când nu sunt prezente migrații prin rularea sarcinii rake db:schema:dump
. O greșeală comună Rails este să verificați o nouă migrare în depozitul sursă, dar nu și fișierul schema.rb
actualizat corespunzător.
Când migrațiile au scăpat de sub control și durează prea mult să ruleze sau nu mai creează corect baza de date, dezvoltatorii nu ar trebui să se teamă să ștergă vechiul director de migrare, să arunce o nouă schemă și să continue de acolo. Configurarea unui nou mediu de dezvoltare ar necesita apoi un rake db:schema:load
mai degrabă decât rake db:migrate
pe care se bazează majoritatea dezvoltatorilor.
Unele dintre aceste probleme sunt discutate și în Ghidul șinelor.
Greșeala comună #10: Verificarea informațiilor sensibile în depozitele de cod sursă
Cadrul Rails facilitează crearea de aplicații sigure, rezistente la multe tipuri de atacuri. Unele dintre acestea se realizează prin utilizarea unui simbol secret pentru a securiza o sesiune cu un browser. Chiar dacă acest token este acum stocat în config/secrets.yml
și acel fișier citește jetonul dintr-o variabilă de mediu pentru serverele de producție, versiunile anterioare de Rails au inclus jetonul în config/initializers/secret_token.rb
. Acest fișier este adesea verificat din greșeală în depozitul de cod sursă împreună cu restul aplicației dvs. și, atunci când se întâmplă acest lucru, oricine are acces la depozit poate compromite cu ușurință toți utilizatorii aplicației dvs. .
Prin urmare, ar trebui să vă asigurați că fișierul dvs. de configurare a depozitului (de exemplu, .gitignore
pentru utilizatorii git) exclude fișierul cu simbolul dvs. Serverele dvs. de producție își pot prelua token-ul dintr-o variabilă de mediu sau dintr-un mecanism precum cel oferit de bijuteria dotenv.
Încheierea tutorialului
Rails este un cadru puternic care ascunde o mulțime de detalii urâte necesare pentru a construi o aplicație web robustă. În timp ce acest lucru face dezvoltarea aplicațiilor web Rails mult mai rapidă, dezvoltatorii ar trebui să acorde atenție potențialelor erori de proiectare și codare, pentru a se asigura că aplicațiile lor sunt ușor de extins și de întreținut pe măsură ce cresc.
De asemenea, dezvoltatorii trebuie să fie conștienți de problemele care pot face aplicațiile lor mai lente, mai puțin fiabile și mai puțin sigure. Este important să studiați cadrul și să vă asigurați că înțelegeți pe deplin compromisurile de arhitectură, design și codare pe care le faceți pe parcursul procesului de dezvoltare, pentru a asigura o aplicație de înaltă calitate și de înaltă performanță.