socialgekon.com
  • Principal
  • Procese Financiare
  • Rentabilitate Și Eficiență
  • Investitori Și Finanțare
  • Filmare
Back-End

Construiți componente de șine elegante cu obiecte vechi de rubin simplu

Site-ul dvs. web câștigă aderență și creșteți rapid. Ruby / Rails este limbajul dvs. de programare la alegere. Echipa dvs. este mai mare și ați renunțat la „modele grase, controlere slabe” ca stil de design pentru aplicațiile dvs. Rails. Cu toate acestea, încă nu doriți să renunțați la utilizarea Rails.

Nici o problemă. Astăzi, vom discuta despre cum să utilizați cele mai bune practici ale POO pentru a vă face codul mai curat, mai izolat și mai decuplat.

Aplicația dvs. merită refactorizată?

Să începem prin a analiza modul în care ar trebui să decideți dacă aplicația dvs. este un candidat bun pentru refactorizare.



Iată o listă de valori și întrebări pe care mi le pun de obicei pentru a determina dacă codul meu are nevoie sau nu de refactorizare.

  • Teste unitare lente. Testele unitare PORO rulează de obicei rapid cu un cod bine izolat, astfel încât testele de rulare lentă pot fi adesea un indicator al unui design defect și al unor responsabilități excesiv de cuplate.
  • Modele FAT sau controlere. Un model sau controler cu mai mult de 200 linii de cod (LOC) este, în general, un bun candidat pentru refactorizare.
  • Baza de cod excesiv de mare. Dacă aveți ERB / ​​HTML / HAML cu peste 30.000 LOC sau cod sursă Ruby (fără GEM-uri ) cu peste 50.000 LOC, există șanse mari să refactorizați.

Încercați să utilizați așa ceva pentru a afla câte linii de cod sursă Ruby aveți:

find app -iname '*.rb' -type f -exec cat {} ;| wc -l

Această comandă va căuta în toate fișierele cu extensia .rb (fișiere ruby) din folderul / app și va imprima numărul de linii. Vă rugăm să rețineți că acest număr este doar aproximativ, deoarece liniile de comentarii vor fi incluse în aceste totaluri.

O altă opțiune mai precisă și mai informativă este folosirea sarcinii rake Rails stats care afișează un rezumat rapid al liniilor de cod, numărul de clase, numărul de metode, raportul dintre metode și clase și raportul liniilor de cod pe metodă:

bundle exec rake stats +----------------------+-------+-----+-------+---------+-----+-------+ | Name | Lines | LOC | Class | Methods | M/C | LOC/M | +----------------------+-------+-----+-------+---------+-----+-------+ | Controllers | 195 | 153 | 6 | 18 | 3 | 6 | | Helpers | 14 | 13 | 0 | 2 | 0 | 4 | | Models | 120 | 84 | 5 | 12 | 2 | 5 | | Mailers | 0 | 0 | 0 | 0 | 0 | 0 | | Javascripts | 45 | 12 | 0 | 3 | 0 | 2 | | Libraries | 0 | 0 | 0 | 0 | 0 | 0 | | Controller specs | 106 | 75 | 0 | 0 | 0 | 0 | | Helper specs | 15 | 4 | 0 | 0 | 0 | 0 | | Model specs | 238 | 182 | 0 | 0 | 0 | 0 | | Request specs | 699 | 489 | 0 | 14 | 0 | 32 | | Routing specs | 35 | 26 | 0 | 0 | 0 | 0 | | View specs | 5 | 4 | 0 | 0 | 0 | 0 | +----------------------+-------+-----+-------+---------+-----+-------+ | Total | 1472 |1042 | 11 | 49 | 4 | 19 | +----------------------+-------+-----+-------+---------+-----+-------+ Code LOC: 262 Test LOC: 780 Code to Test Ratio: 1:3.0
  • Pot extrage modele recurente în baza mea de cod?

Decuplarea în acțiune

Să începem cu un exemplu real.

Presupunem că vrem să scriem o aplicație care urmărește timpul pentru joggers. În pagina principală, utilizatorul poate vedea orele pe care le-a introdus.

Fiecare intrare are o dată, distanță, durată și informații relevante suplimentare despre „stare” (de exemplu, vreme, tip de teren etc.) și o viteză medie care poate fi calculată atunci când este necesar.

Avem nevoie de o pagină de raport care să afișeze viteza și distanța medie pe săptămână.

Dacă viteza medie pentru intrare este mai mare decât viteza medie generală, vom notifica utilizatorul cu un SMS (pentru acest exemplu vom folosi Nexmo API RESTful pentru a trimite SMS).

Pagina de pornire vă va permite să selectați distanța, data și timpul petrecut la jogging pentru a crea o intrare similară cu aceasta:

Avem și un statistics pagina care este practic un raport săptămânal care include viteza medie și distanța parcursă pe săptămână.

  • Puteți consulta eșantionul online Aici .

Codul

Structura app directorul arată ceva de genul:

⇒ tree . ├── assets │ └── ... ├── controllers │ ├── application_controller.rb │ ├── entries_controller.rb │ └── statistics_controller.rb ├── helpers │ ├── application_helper.rb │ ├── entries_helper.rb │ └── statistics_helper.rb ├── mailers ├── models │ ├── entry.rb │ └── user.rb └── views ├── devise │ └── ... ├── entries │ ├── _entry.html.erb │ ├── _form.html.erb │ └── index.html.erb ├── layouts │ └── application.html.erb └── statistics └── index.html.erb

Nu voi discuta despre User model, deoarece nu este nimic special, deoarece îl folosim cu Motto pentru a implementa autentificarea.

În ceea ce privește Entry model, conține logica de afaceri pentru aplicația noastră.

Fiecare Entry aparține unui User.

Validăm prezența distance, time_period, date_time și status atributele pentru fiecare intrare.

De fiecare dată când creăm o intrare, comparăm viteza medie a utilizatorului cu media tuturor celorlalți utilizatori din sistem și notificăm utilizatorul prin SMS folosind Nexmo (nu vom discuta despre modul în care este utilizată biblioteca Nexmo, deși am vrut să demonstrez un caz în care folosim o bibliotecă externă).

  • Eșantion esențial

Observați că | | + _ | modelul conține mai mult decât logica de afaceri. De asemenea, gestionează unele validări și apeluri de apel.

Entry are principalul CRUD acțiuni (fără actualizare). entries_controller.rb primește intrările pentru utilizatorul curent și comandă înregistrările după data creării, în timp ce EntriesController#index creează o intrare nouă. Nu este nevoie să discutați despre evident și responsabilitățile EntriesController#create :

  • Eșantion esențial

În timp ce EntriesController#destroy este responsabil pentru calcularea raportului săptămânal, statistics_controller.rb primește intrările pentru utilizatorul conectat și le grupează în funcție de săptămână, folosind StatisticsController#index metoda conținută în clasa Enumerable din Rails. Apoi încearcă să decoreze rezultatele folosind câteva metode private.

  • Eșantion esențial

Nu discutăm prea mult punctele de vedere aici, deoarece codul sursă se explică de la sine.

Mai jos este vizualizarea pentru listarea intrărilor pentru utilizatorul conectat (#group_by). Acesta este șablonul care va fi utilizat pentru a afișa rezultatele acțiunii indexului (metoda) în controlerul de intrări:

  • Eșantion esențial

Rețineți că folosim parțiale index.html.erb, pentru a extrage codul partajat într-un șablon parțial render @entries astfel încât să ne putem păstra codul USCAT și reutilizabile:

  • Eșantion esențial

Același lucru este valabil și pentru _entry.html.erb parțial. În loc să folosim același cod cu acțiuni (noi și editate), creăm un formular parțial reutilizabil:

  • Eșantion esențial

În ceea ce privește vizualizarea săptămânală a raportului, _form afișează câteva statistici și raportează performanța săptămânală a utilizatorului prin gruparea unor intrări:

  • Eșantion esențial

Și, în cele din urmă, ajutorul pentru intrări, statistics/index.html.erb, include doi asistenți entries_helper.rb și readable_time_period care ar trebui să facă atributele mai lizibile uman:

  • Eșantion esențial

Nimic extraordinar până acum.

Cei mai mulți dintre voi vor susține că refacerea acestui lucru este împotriva SĂRUT principiu și va face sistemul mai complicat.

Deci, această aplicație are nevoie într-adevăr de refactorizare?

Absolut nu , dar o vom lua în considerare doar în scop demonstrativ.

La urma urmei, dacă verificați secțiunea anterioară și caracteristicile care indică faptul că o aplicație are nevoie de refactorizare, devine evident că aplicația din exemplul nostru nu este un candidat valid pentru refactorizare.

Ciclu de viață

Deci, să începem prin a explica Rails MVC structura tiparului.

De obicei, începe prin browserul care face o cerere, cum ar fi readable_speed.

Serverul web primește cererea și folosește https://www.toptal.com/jogging/show/1 pentru a afla care routes a folosi.

Operatorii efectuează activitatea de analiză a cererilor utilizatorilor, trimiteri de date, cookie-uri, sesiuni etc., și apoi solicită controller pentru a obține datele.

model sunt clase Ruby care vorbesc cu baza de date, stochează și validează date, efectuează logica de afaceri și, în caz contrar, fac greutăți mari. Vizualizările sunt ceea ce vede utilizatorul: HTML, CSS, XML, Javascript, JSON.

Dacă dorim să arătăm secvența unui ciclu de viață al cererii Rails, ar arăta cam așa:

Șinele decuplează ciclul de viață al MVC

Ceea ce vreau să realizez este să adaug mai multă abstracție folosind obiecte vechi de rubin vechi (PORO) și să fac ca modelul să fie de genul următor pentru models acțiuni:

Diagrama șinelor creează formular

Și ceva de genul următor pentru create/update acțiuni:

Interogare listă diagramă șine

Adăugând abstracții PORO, vom asigura o separare completă între responsabilități ȘOAPĂ , ceva la care Rails nu este foarte bun.

Instrucțiuni

Pentru a obține noul design, voi folosi liniile directoare enumerate mai jos, dar vă rugăm să rețineți că acestea nu sunt reguli pe care trebuie să le respectați la T. Gândiți-vă la ele ca la linii directoare flexibile care facilitează refactorizarea.

  • Modelele ActiveRecord pot conține asociații și constante, dar nimic altceva. Deci, asta înseamnă că nu există apeluri de apel (utilizați obiecte de serviciu și adăugați apeluri de apel acolo) și nu există validări (utilizați Formați obiecte pentru a include denumirea și validările pentru model).
  • Păstrați controlerele ca straturi subțiri și apelați întotdeauna obiecte de service. Unii dintre voi v-ați întreba de ce să folosiți controlere, deoarece vrem să apelăm în continuare obiecte de serviciu pentru a conține logica? Ei bine, controlerele sunt un loc bun pentru a avea rutarea HTTP, analiza parametrilor, autentificarea, negocierea conținutului, apelarea serviciului potrivit sau obiectul editor, captarea excepțiilor, formatarea răspunsului și returnarea codului de stare HTTP corect.
  • Serviciile ar trebui să apeleze obiecte de interogare și nu ar trebui să stocheze starea. Folosiți metode de instanță, nu metode de clasă. Ar trebui să existe foarte puține metode publice în conformitate cu ȘOAPĂ .
  • Interogările trebuie făcute în obiecte de interogare. Metodele de interogare a obiectelor ar trebui să returneze un obiect, un hash sau o matrice, nu o asociere ActiveRecord.
  • Evitați să folosiți Helpers și folosiți în schimb decoratori. De ce? O capcană obișnuită a ajutoarelor Rails este că se pot transforma într-o grămadă mare de funcții non-OO, toate împărtășind un spațiu de nume și călcându-se unul pe celălalt. Dar mult mai rău este că nu există o modalitate excelentă de a utiliza orice fel de polimorfism cu ajutorul ajutoarelor Rails - oferind implementări diferite pentru diferite contexte sau tipuri, ajutători care nu se supraviețuiesc sau subclasează. Cred că clasele de asistență Rails ar trebui utilizate în general pentru metode de utilitate, nu pentru cazuri de utilizare specifice, cum ar fi formatarea atributelor modelului pentru orice tip de logică de prezentare. Păstrați-le ușoare și ușoare.
  • Evitați să folosiți preocupări și folosiți în schimb Decoratori / Delegați. De ce? La urma urmei, îngrijorările par a fi o parte esențială a Rails și pot SECA codul atunci când sunt partajate între mai multe modele. Cu toate acestea, problema principală este că preocupările nu fac obiectul model mai coeziv. Codul este mai bine organizat. Cu alte cuvinte, nu există nicio modificare reală a API-ului modelului.
  • Încercați să extrageți Obiecte de valoare din modele pentru a vă menține codul mai curat și pentru a grupa atributele conexe.
  • Treceți întotdeauna o variabilă de instanță per vizualizare.

Refactorizare

Înainte de a începe, vreau să discut încă un lucru. Când începeți refactorizarea, de obicei ajungeți să vă întrebați: „Este o refacere foarte bună?”

Dacă simți că faci mai multă separare sau izolare între responsabilități (chiar dacă asta înseamnă adăugarea mai multor coduri și fișiere noi), atunci acesta este de obicei un lucru bun. La urma urmei, decuplarea unei aplicații este o practică foarte bună și ne face mai ușor să facem teste unitare adecvate.

Nu voi discuta lucruri, cum ar fi mutarea logicii de la controlere la modele, deoarece presupun că faceți deja acest lucru și vă simțiți confortabil folosind Rails (de obicei modelul Skinny Controller și modelul FAT).

Pentru a menține acest articol strâns, nu voi discuta testarea aici, dar asta nu înseamnă că nu ar trebui să testați.

Dimpotrivă, ar trebui începe întotdeauna cu un test pentru a vă asigura că lucrurile sunt ok înainte de a merge mai departe. Acesta este un trebuie sa, mai ales la refactorizare.

Apoi putem implementa modificări și ne asigurăm că toate testele trec pentru părțile relevante ale codului.

Extragerea obiectelor de valoare

În primul rând, ce este un obiect valoric?

Martin Fowler explică:

Obiectul valoare este un obiect mic, cum ar fi un obiect cu bani sau interval de date. Proprietatea lor cheie este că urmează semantica valorică mai degrabă decât semantica de referință.

Uneori, puteți întâlni o situație în care un concept își merită propria abstractizare și a cărui egalitate nu se bazează pe valoare, ci pe identitate. Exemple ar include Ruby’s Date, URI și Pathname. Extragerea către un obiect valoric (sau model de domeniu) este o mare comoditate.

De ce sa te deranjezi?

Unul dintre cele mai mari avantaje ale unui obiect Value este expresivitatea pe care o ajută în realizarea codului dvs. Codul dvs. va tinde să fie mult mai clar sau cel puțin poate fi dacă aveți bune practici de denumire. Deoarece Obiectul Value este o abstracție, acesta conduce la un cod mai curat și la mai puține erori.

Un alt mare câștig este imuabilitate . Imuabilitatea obiectelor este foarte importantă. Când stocăm anumite seturi de date, care ar putea fi utilizate într-un obiect valoric, de obicei nu vreau ca aceste date să fie manipulate.

Când este util acest lucru?

Nu există un răspuns unic. Faceți ceea ce este mai bine pentru dvs. și ceea ce are sens în orice situație dată.

Trecând dincolo de asta, totuși, există câteva linii directoare pe care le folosesc pentru a mă ajuta să iau această decizie.

Dacă vă gândiți la un grup de metode este legat, cu obiectele Value acestea sunt mai expresive. Această expresivitate înseamnă că un obiect Value ar trebui să reprezinte un set distinct de date, pe care dezvoltatorul dvs. mediu le poate deduce pur și simplu uitându-se la numele obiectului.

Cum se face asta?

Obiectele de valoare trebuie să respecte câteva reguli de bază:

  • Obiectele de valoare trebuie să aibă mai multe atribute.
  • Atributele ar trebui să fie imuabile pe tot parcursul ciclului de viață al obiectului.
  • Egalitatea este determinată de atributele obiectului.

În exemplul nostru, voi crea un list/show obiect de valoare pentru abstract EntryStatus și Entry#status_weather atribute propriei clase, care arată cam așa:

  • Eșantion esențial

Notă: Acesta este doar un obiect de rubin vechi simplu (PORO) care nu moștenește de la Entry#status_landform. Am definit metode de citire pentru atributele noastre și le atribuim la inițializare. De asemenea, am folosit un mixin comparabil pentru a compara obiecte folosind metoda ().

Putem modifica ActiveRecord::Base model pentru a utiliza obiectul valoric pe care l-am creat:

  • Eșantion esențial

De asemenea, putem modifica Entry metoda de a utiliza noul obiect valoare în consecință:

  • Eșantion esențial

Extrageți obiecte de serviciu

Deci, ce este un obiect de serviciu?

Sarcina unui obiect de serviciu este de a păstra codul pentru un anumit bit de logică de afaceri. spre deosebire de „Model gras” stil, în care un număr mic de obiecte conțin multe, multe metode pentru toate logica necesară, folosind obiecte de serviciu rezultă multe clase, fiecare dintre ele având un singur scop.

De ce? Care sunt beneficiile?

  • Decuplare. Obiectele de serviciu vă ajută să obțineți mai multă izolare între obiecte.
  • Vizibilitate. Obiectele de serviciu (dacă sunt bine denumite) arată ce face o aplicație. Pot doar să arunc o privire peste directorul de servicii pentru a vedea ce funcții oferă o aplicație.
  • Curățați modelele și controlerele. Controlorii transformă cererea (params, sesiune, cookie-uri) în argumente, le transmit serviciului și redirecționează sau redă în funcție de răspunsul serviciului. În timp ce modelele se ocupă doar de asociații și persistență. Extragerea codului de la controlere / modele către obiecte de service ar sprijini SRP și ar face codul mai decuplat. Responsabilitatea modelului ar fi atunci doar de a face față asociațiilor și salvarea / ștergerea înregistrărilor, în timp ce obiectul de serviciu ar avea o singură responsabilitate (SRP). Acest lucru duce la un design mai bun și teste unitare mai bune.
  • USCAT și îmbrățișați schimbarea. Păstrez obiecte de service cât mai simple și mai mici. Compun obiecte de service cu alte obiecte de service și le refolosesc.
  • Curățați și accelerați suita de teste. Serviciile sunt ușor și rapid de testat, deoarece sunt obiecte mici Ruby cu un singur punct de intrare (metoda apelului). Serviciile complexe sunt compuse cu alte servicii, astfel încât să puteți împărți testele cu ușurință. De asemenea, utilizarea obiectelor de service face mai ușor să batjocorească / stub obiectele legate fără a fi nevoie să încărcați întregul mediu de șine.
  • Apelabil de oriunde. Obiectele de serviciu sunt susceptibile de a fi apelate de la controlere, precum și de la alte obiecte de serviciu, DelayedJob / Rescue / Sidekiq Jobs, sarcini Rake, consolă etc.

Pe de altă parte, nimic nu este perfect. Un dezavantaj al obiectelor de serviciu este că pot fi un exces pentru o acțiune foarte simplă. În astfel de cazuri, este posibil să sfârșiți prin a vă complica, mai degrabă decât a simplifica, codul.

Când ar trebui să extrageți obiecte de serviciu?

Nici aici nu există o regulă dură și rapidă.

În mod normal, obiectele de serviciu sunt mai bune pentru sistemele mijlocii și mari; cei cu o cantitate decentă de logică dincolo de operațiile CRUD standard.

Deci, ori de câte ori credeți că un fragment de cod ar putea să nu aparțină directorului în care urmați să-l adăugați, este probabil o idee bună să vă reconsiderați și să vedeți dacă ar trebui să meargă la un obiect de serviciu.

Iată câțiva indicatori ai timpului de utilizare a obiectelor de serviciu:

  • Acțiunea este complexă.
  • Acțiunea ajunge la mai multe modele.
  • Acțiunea interacționează cu un serviciu extern.
  • Acțiunea nu este o preocupare de bază a modelului de bază.
  • Există mai multe moduri de a efectua acțiunea.

Cum ar trebui să proiectați obiectele de service?

Proiectarea clasei pentru un obiect de serviciu este relativ simplă, deoarece nu aveți nevoie de pietre prețioase speciale, nu trebuie să învățați un DSL nou și vă puteți baza mai mult sau mai puțin pe abilitățile de proiectare software pe care le dețineți deja.

De obicei folosesc următoarele linii directoare și convenții pentru a proiecta obiectul de serviciu:

  • Nu stocați starea obiectului.
  • Folosiți metode de instanță, nu metode de clasă.
  • Ar trebui să existe foarte puține metode publice (de preferință una de susținut ȘOAPĂ .
  • Metodele ar trebui să returneze obiecte cu rezultate bogate și nu booleeni.
  • Serviciile intră sub EntryController#create director. Vă încurajez să utilizați subdirectoarele pentru domenii care conțin logică de afaceri. De exemplu, fișierul app/services va defini app/services/report/generate_weekly.rb în timp ce Report::GenerateWeekly va defini app/services/report/publish_monthly.rb.
  • Serviciile încep cu un verb (și nu se termină cu Serviciu): Report::PublishMonthly, ApproveTransaction, SendTestNewsletter.
  • Serviciile răspund la metoda apelului. Am găsit că utilizarea unui alt verb îl face puțin redundant: ApproveTransaction.approve () nu citește bine. De asemenea, metoda apelului este metoda de facto pentru lambda, procs și obiecte de metodă.

Dacă te uiți la ImportUsersFromCsv, vei observa un grup de metode (StatisticsController#index, weeks_to_date_from, weeks_to_date_to etc.) cuplate la controler. Nu este chiar bine. Luați în considerare ramificațiile dacă doriți să generați raportul săptămânal în afara avg_distance.

În cazul nostru, să creăm statistics_controller și extrageți logica raportului din Report::GenerateWeekly:

  • Eșantion esențial

Deci StatisticsController acum pare mai curat:

  • Eșantion esențial

Prin aplicarea modelului de obiect Service, grupăm codul în jurul unei acțiuni specifice, complexe și promovăm crearea unor metode mai mici și mai clare.

Teme pentru acasă: ia în considerare utilizarea Obiect valoric pentru StatisticsController#index în loc de WeeklyReport .

Extrageți obiecte de interogare din controlere

Ce este un obiect Query?

Un obiect Query este un PORO care reprezintă o interogare în baza de date. Poate fi reutilizat în diferite locuri ale aplicației, ascunzând în același timp logica interogării. De asemenea, oferă o unitate izolată bună de testat.

Ar trebui să extrageți interogări complexe SQL / NoSQL în propria clasă.

Fiecare obiect de interogare este responsabil pentru returnarea unui set de rezultate pe baza criteriilor / regulilor de afaceri.

În acest exemplu, nu avem nicio interogare complexă, deci utilizarea obiectului Query nu va fi eficientă. Cu toate acestea, în scop demonstrativ, să extragem interogarea în Struct și creați Report::GenerateWeekly#call:

  • Eșantion esențial

Și în generate_entries_query.rb, să înlocuim:

Report::GenerateWeekly#call

cu:

def call @user.entries.group_by(&:week).map do |week, entries| WeeklyReport.new( ... ) end end

Modelul obiectului de interogare vă ajută să vă păstrați logica modelului strict legată de comportamentul unei clase, păstrând în același timp controlorii dvs. subțiri. Deoarece nu sunt altceva decât clase vechi Ruby simple, obiectele de interogare nu trebuie să moștenească de la def call weekly_grouped_entries = GroupEntriesQuery.new(@user).call weekly_grouped_entries.map do |week, entries| WeeklyReport.new( ... ) end end și nu ar trebui să fie responsabile pentru nimic mai mult decât executarea interogărilor.

Extrageți Creare intrare la un obiect de serviciu

Acum, să extragem logica creării unei noi intrări într-un nou obiect de serviciu. Să folosim convenția și să creăm ActiveRecord::Base:

  • Eșantion esențial

Și acum CreateEntry este după cum urmează:

EntriesController#create

Mutați validările într-un obiect formular

Acum, aici lucrurile încep să devină mai interesante.

Amintiți-vă în ghidurile noastre, am convenit că dorim ca modelele să conțină asociații și constante, dar nimic altceva (fără validări și fără apeluri). Deci, să începem prin eliminarea apelurilor de apel și să folosim în schimb un obiect Form.

Un obiect Form este un obiect Ruby Old Plain (PORO). Acesta preia controlerul / obiectul de serviciu oriunde trebuie să vorbească cu baza de date.

De ce să folosiți obiecte Form?

Când doriți să refactorați aplicația, este întotdeauna o idee bună să țineți cont de principiul responsabilității unice (SRP).

SRP vă ajută să luați decizii de proiectare mai bune în ceea ce privește responsabilitatea unei clase.

Modelul tabelului bazei de date (un model ActiveRecord în contextul Rails), de exemplu, reprezintă o singură înregistrare a bazei de date în cod, deci nu există niciun motiv pentru care să fie preocupat de ceea ce face utilizatorul dumneavoastră.

Aici intervin obiectele Form.

Un obiect Form este responsabil pentru reprezentarea unui formular în cererea dvs. Deci, fiecare câmp de intrare poate fi tratat ca un atribut în clasă. Poate valida faptul că aceste atribute îndeplinesc unele reguli de validare și poate transmite datele „curate” către locul unde trebuie să meargă (de exemplu, modelele bazei de date sau, probabil, producătorul de interogări de căutare).

Când ar trebui să utilizați un obiect Form?

  • Când doriți să extrageți validările din modelele Rails.
  • Atunci când mai multe modele pot fi actualizate printr-un singur formular de trimitere, este posibil să doriți să creați un obiect Form.

Acest lucru vă permite să puneți întreaga logică a formularului (convenții de denumire, validări etc.) într-un singur loc.

Cum creați un obiect Form?

  • Creați o clasă simplă Ruby.
  • Includeți def create begin CreateEntry.new(current_user, entry_params).call flash[:notice] = 'Entry was successfully created.' rescue Exception => e flash[:error] = e.message end redirect_to root_path end (în Rails 3, trebuie să includeți în schimb denumirea, conversia și validările)
  • Începeți să utilizați noua clasă de formular ca și cum ar fi un model obișnuit de ActiveRecord, cea mai mare diferență fiind că nu puteți persista datele stocate în acest obiect.

Vă rugăm să rețineți că puteți utiliza reforma bijuterie, dar rămânând cu PORO vom crea ActiveModel::Model care arată astfel:

  • Eșantion esențial

Și vom modifica entry_form.rb pentru a începe să utilizați obiectul Form CreateEntry:

EntryForm

Notă: Unii dintre voi ar spune că nu este nevoie să accesați obiectul Form din obiectul Serviciu și că putem apela obiectul Form direct de la controler, ceea ce este un argument valid. Cu toate acestea, aș prefera să am un flux clar și de aceea apelez întotdeauna obiectul Form din obiectul Service.

Mutați apelurile de apel către obiectul de serviciu

După cum am convenit mai devreme, nu vrem ca modelele noastre să conțină validări și apeluri de apel. Am extras validările folosind obiecte Form. Dar folosim încă câteva apeluri de apel ( class CreateEntry ...... ...... def call @entry_form = ::EntryForm.new(@params) if @entry_form.valid? .... else .... end end end în after_create model Entry).

De ce vrem să eliminăm apelurile de apel din modele?

Dezvoltatori de șine de obicei începeți să observați durerea de apel invers în timpul testării. Dacă nu vă testați modelele ActiveRecord, veți începe să observați durerea mai târziu pe măsură ce aplicația dvs. crește și pe măsură ce este necesară mai multă logică pentru a apela sau a evita apelul invers.

compare_speed_and_notify_user apelurile de apel sunt utilizate în principal în legătură cu salvarea sau persistarea obiectului.

Odată ce obiectul este salvat, scopul (adică responsabilitatea) obiectului a fost îndeplinit. Deci, dacă mai vedem apeluri de apel invocate după ce obiectul a fost salvat, ceea ce probabil vedem este apeluri de apel care ajung în afara zonei de responsabilitate a obiectului și atunci avem probleme.

În cazul nostru, trimitem un SMS utilizatorului după ce salvăm o intrare, care nu este într-adevăr legată de domeniul de intrare.

O modalitate simplă de a rezolva problema este prin mutarea apelului invers către obiectul de serviciu aferent. La urma urmei, trimiterea unui SMS pentru utilizatorul final este legată de after_* Service Object și nu la modelul de intrare în sine.

Procedând astfel, nu mai trebuie să ne îndepărtăm de CreateEntry metodă în testele noastre. Am făcut o chestiune simplă crearea unei intrări fără a fi necesară trimiterea unui SMS și urmărim un design orientat spre obiecte, asigurându-ne că clasele noastre au o singură responsabilitate (SRP).

Deci, acum compare_speed_and_notify_user arata ceva de genul:

  • Eșantion esențial

Folosiți decoratori în loc de ajutoare

În timp ce putem folosi cu ușurință Draper colecție de modele de vizualizare și decoratori, mă voi ține de PORO de dragul acestui articol, așa cum am făcut până acum.

Ceea ce am nevoie este o clasă care va numi metode pe obiectul decorat.

Pot folosi CreateEntry pentru a implementa asta, dar voi folosi biblioteca standard a lui Ruby method_missing.

Următorul cod arată cum să utilizați SimpleDelegator pentru a implementa decoratorul nostru de bază:

SimpleDelegator

Deci, de ce % app/decorators/base_decorator.rb require 'delegate' class BaseDecorator metodă?

Această metodă acționează ca un proxy pentru contextul de vizualizare. În mod implicit, contextul de vizualizare este o instanță a unei clase de vizualizare, clasa de vizualizare implicită fiind _h. Puteți accesa asistenți pentru vizualizare după cum urmează:

ActionView::Base

Pentru a-l face mai convenabil, adăugăm un _h.content_tag :div, 'my-div', class: 'my-class' metoda de a decorate:

ApplicationHelper

Acum, putem muta module ApplicationHelper # ..... def decorate(object, klass = nil) klass ||= '#{object.class}Decorator'.constantize decorator = klass.new(object, self) yield decorator if block_given? decorator end # ..... end ajutoare pentru decoratori:

EntriesHelper

Și putem folosi # app/decorators/entry_decorator.rb class EntryDecorator și readable_time_period ca astfel:

readable_speed # app/views/entries/_entry.html.erb - +

Structura după refactorizare

Am ajuns cu mai multe fișiere, dar nu este neapărat un lucru rău (și amintiți-vă că, de la început, am recunoscut că acest exemplu a fost doar în scop demonstrativ și a fost nu neapărat un bun caz de utilizare pentru refactorizare):

- +

Concluzie

Chiar dacă ne-am concentrat pe Rails în această postare de blog, RoR nu este o dependență a obiectelor de servicii descrise și a altor PORO-uri. Puteți utiliza această abordare cu orice aplicație de cadru web, mobil sau consolă.

Prin utilizarea MVC ca arhitectură a aplicațiilor web, totul rămâne cuplat și vă face să mergeți mai lent, deoarece majoritatea modificărilor au un impact asupra altor părți ale aplicației. De asemenea, vă obligă să vă gândiți unde să puneți o logică de afaceri - ar trebui să intre în model, controler sau vedere?

Prin utilizarea PORO-urilor simple, am mutat logica de afaceri la modele sau servicii care nu moștenesc de la app ├── assets │ └── ... ├── controllers │ ├── application_controller.rb │ ├── entries_controller.rb │ └── statistics_controller.rb ├── decorators │ ├── base_decorator.rb │ └── entry_decorator.rb ├── forms │ └── entry_form.rb ├── helpers │ └── application_helper.rb ├── mailers ├── models │ ├── entry.rb │ ├── entry_status.rb │ └── user.rb ├── queries │ └── group_entries_query.rb ├── services │ ├── create_entry.rb │ └── report │ └── generate_weekly.rb └── views ├── devise │ └── .. ├── entries │ ├── _entry.html.erb │ ├── _form.html.erb │ └── index.html.erb ├── layouts │ └── application.html.erb └── statistics └── index.html.erb , ceea ce este deja un mare câștig, fără a menționa că avem un cod mai curat, care acceptă SRP și teste unitare mai rapide .

Arhitectura curată își propune să plaseze cazurile de utilizare în centrul / partea de sus a structurii dvs., astfel încât să puteți vedea cu ușurință ce face aplicația dvs. De asemenea, facilitează adoptarea modificărilor, deoarece este mult mai modulară și izolată.

Sper că am demonstrat cum folosind obiecte Plain Old Ruby și mai multe abstracții decuplează preocupările, simplifică testarea și ajută la producerea unui cod curat, care poate fi întreținut.

Legate de: Care sunt beneficiile Ruby on Rails? După două decenii de programare, folosesc șine

Cum să fotografiezi artificii ca un profesionist doar cu iPhone-ul tău

Filmare

Cum să fotografiezi artificii ca un profesionist doar cu iPhone-ul tău
Sfaturi pentru o critică de proiectare productivă

Sfaturi pentru o critică de proiectare productivă

Proces De Design

Posturi Populare
Să redesenăm Facebook: 10 exemple pentru a inspira și a vă ajuta să începeți
Să redesenăm Facebook: 10 exemple pentru a inspira și a vă ajuta să începeți
Sfârșitul muncii tradiționale - Cealaltă economie gigantică
Sfârșitul muncii tradiționale - Cealaltă economie gigantică
Grey Matter - Ce este o hartă mentală în procesul de proiectare?
Grey Matter - Ce este o hartă mentală în procesul de proiectare?
Stai la răceală: Cum să iei feedback strategic în mod strategic
Stai la răceală: Cum să iei feedback strategic în mod strategic
Prioritizarea restanțelor de produse cu mai mulți părți interesate cheie: un studiu de caz
Prioritizarea restanțelor de produse cu mai mulți părți interesate cheie: un studiu de caz
 
Un ghid cuprinzător pentru proiectarea notificărilor
Un ghid cuprinzător pentru proiectarea notificărilor
Către diagrame D3.js actualizabile
Către diagrame D3.js actualizabile
Inginer Salesforce Back-end
Inginer Salesforce Back-end
Remedierea erorii OpenSSL „Heartbleed”: un tutorial pentru administratorii Sys
Remedierea erorii OpenSSL „Heartbleed”: un tutorial pentru administratorii Sys
Sporiți-vă UX-ul cu o ierarhie vizuală clară
Sporiți-vă UX-ul cu o ierarhie vizuală clară
Categorii
Kpis Și AnalyticsFront-End WebFilmareRise Of RemoteProces De DesignTendințeEditareMod De ViataPlanificare Și PrognozăOameni Și Echipe

© 2023 | Toate Drepturile Rezervate

socialgekon.com