def spectre_member_name

Anti-ce? Probabil pare a fi mult mai complicat decât este. În ultimele decenii, programatorii au reușit să identifice o selecție utilă de tipare "de proiectare" care au apărut frecvent pe parcursul soluțiilor lor de cod. În timp ce au rezolvat probleme similare, au reușit să "clasifice" soluțiile care le împiedicau să reinventeze roata tot timpul. Este important să rețineți că aceste modele ar trebui văzute mai mult ca descoperiri decât invențiile unui grup de dezvoltatori avansați.

Dacă aceasta este mai degrabă nouă și vă vedeți ca fiind mai mult pe partea de început a tuturor lucrurilor Ruby / Rails, atunci aceasta este scrisă exact pentru dvs. Cred că e mai bine dacă te gândești la asta ca la o scurtă scurtă trecere într-un subiect mult mai profund a cărui stăpânire nu se va întâmpla peste noapte. Cu toate acestea, cred cu tărie că începând să intrăm în această perioadă devreme, vor beneficia în mod deosebit începătorii și mentorii lor.

AntiPatterns - așa cum sugerează și numele - reprezintă aproape opusul tiparelor. Ele sunt descoperiri de soluții la probleme pe care ar trebui să le eviți. Ele reprezintă deseori lucrarea unor coderi neexperimentați care nu știu încă ce nu știu. Mai rău, acestea ar putea fi rezultatul unei persoane leneșiste, care ignoră cele mai bune practici și cadre de instrumente pentru niciun motiv bun - sau cred că nu au nevoie de ele. Ceea ce ar putea spera să câștige economiile de timp la început prin ciocnirea unor soluții rapide, lenești sau murdare îi va bântui sau pe un succesor rău în cursul ciclului de viață al proiectului.

Nu subestimați implicațiile acestor decizii proaste - vor să vă placă, indiferent de ce.

Subiecte

  • Modele de grăsime
  • Lipsa suitei de testare
  • Modele voyeuriste
  • Legea lui Demeter
  • Spaghetti SQL

Modele de grăsime

Sunt sigur că ați auzit "modelele de grăsimi, controlorii slabi" cântând cântece de ori când ați început cu Rails. OK, uitați asta acum! Desigur, logica de afaceri trebuie rezolvată în stratul model, dar nu ar trebui să te simți înclinată să faci totul acolo fără sens pentru a evita trecerea liniilor în teritoriul controlerului.

Iată o nouă țintă pe care ar trebui să o urmăriți: "Modele nesănătoase, controale slabe". S-ar putea să întrebați: "Ei bine, cum ar trebui să aranjăm codul pentru a realiza acest lucru - este, în definitiv, un joc cu sumă zero?" Bun punct! Numele jocului este compoziția, iar Ruby este bine echipat pentru a vă oferi o mulțime de opțiuni pentru a evita obezitatea modelului.

În majoritatea aplicațiilor web (Rails) bazate pe baze de date, majoritatea atenției și muncii dvs. vor fi centrate în jurul stratului de model - având în vedere faptul că lucrați cu designeri competenți care sunt capabili să-și implementeze propriile lucruri în vizualizare, vreau să spun. Modelele dvs. vor avea în mod inerent mai multă "gravitate" și vor atrage mai multă complexitate.

Întrebarea este cum intenționați să gestionați această complexitate. Înregistrarea activă vă oferă o mulțime de frânghii pentru a vă închide în timp ce faceți viața incredibil de ușoară. Este o abordare tentantă de a proiecta stratul dvs. de model urmând doar calea cea mai mare comoditate imediată. Cu toate acestea, o arhitectură care dă dovadă de viitor are mai multă atenție decât cultivarea unor clase uriașe și umplerea tuturor obiectelor Active Record.

Problema reală cu care vă confruntați aici este complexitatea - în mod inutil, aș spune. Clasele care acumulează tone de coduri devin complexe doar după mărimea lor. Ele sunt mai greu de întreținut, dificil de analizat și înțeleg, și din ce în ce mai greu de schimbat, deoarece compoziția lor este probabil lipsită de decuplare. Aceste modele depășesc adesea capacitatea recomandată de a se ocupa de o singură responsabilitate și sunt mai degrabă peste tot. Cel mai rău caz, ei devin ca niște camioane de gunoi, manipulând toate gunoaiele care le-au aruncat leneș.

Putem face mai bine! Dacă credeți că complexitatea nu este o afacere mare - la urma urmei, sunteți special, inteligent și tot gândiți-vă din nou! Complexitatea este cel mai notoriu criminal din serialul de proiect acolo - nu cartierul tău prietenos "Dark Defender".

Modelele "Skinnier" realizează un lucru pe care oamenii avansați în activitatea de codificare (probabil mult mai multe profesii decât codul și designul) apreciază și ceea ce ar trebui să ne străduim cu toții pentru simplitate! Sau cel puțin mai mult, ceea ce reprezintă un compromis echitabil dacă complexitatea este greu de eradicat.

Ce instrumente oferă Ruby pentru a ne face viața mai ușoară în această privință și lasă-ne să tăiem grăsimea din modelele noastre? Simple, alte clase și module. Identificați codul coerent pe care l-ați putea extrage într-un alt obiect și construind astfel un strat model care constă din agenți de dimensiuni rezonabile care au propriile responsabilități unice și distinctive.

Gândiți-vă la asta în termeni de interpreți talentați. În viața reală, o astfel de persoană ar putea să rap, să rupă, să scrie și să producă propriile melodii. În programare, preferați dinamica unei trupe - aici cu cel puțin patru membri distinctiv - în care fiecare persoană este responsabilă de cât mai puține lucruri posibil. Vrei să construiești o orchestră de clase care să se ocupe de complexitatea compozitorului - nu este o clasă de maeștri de geniali micromaniști din toate meseriile.

Să aruncăm o privire asupra unui model de grăsime și să jucăm cu câteva opțiuni pentru a ne ocupa de obezitate. Exemplul este unul fals, desigur, și spunând această poveste gooasă puțin sper că va fi mai ușor de digerat și de urmărit pentru începători.

Avem o clasă Spectre care are prea multe responsabilități și, prin urmare, a crescut inutil. Pe lângă aceste metode, cred că este ușor să ne imaginăm că un astfel de specimen deja a acumulat o mulțime de alte lucruri, precum și reprezentate de cele trei puncte mici. Spectrul este bine pe cale să devină o clasă a lui Dumnezeu. (Șansele sunt destul de scăzute pentru a formula o astfel de propoziție din nou în curând!)

"Spectrul clasic rubinic < ActiveRecord::Base has_many :spectre_members has_many :spectre_agents has_many :enemy_agents has_many :operations

...

def turn_mi6_agent (enemy_agent) pune "agentul MI6 # enemy_agent.name avansat la Spectre" end

def turn_cia_agent (enemy_agent) pune "agentul CIA # enemy_agent.name avansat la Spectre" sfârșit

def turn_mossad_agent (enemy_agent) pune "agentul Mossad # enemy_agent.name avansat la Spectre" sfârșitul

def kill_double_o_seven (spectre_agent) spectre_agent.kill_james_bond sfârșitul

def dispose_of_cabinet_member (număr) spectre_member = SpectreMember.find_by_id (număr)

pune "un anumit vinovat a eșuat integritatea absolută a acestei fraternități. Actul potrivit este să fumezi numărul # numeric în scaunul lui.] Serviciile sale nu vor fi ratate" spectre_member.die end 

def print_assignment (operație) pune "Obiectivul operației # operation.name este de a # operation.objective." end

privat

def enemy_agent #clever end code

def spectre_agent #clever end code

Definiție operațiune #clever end code

...

Sfârșit

"

Spectrul transformă diverse tipuri de agenți inamici, delegații ucigând 007, grilă pe membrii cabinetului Spectre când eșuează și imprimă și misiuni operaționale. Un caz clar de micromanagement și cu siguranță o încălcare a principiului "principiului responsabilității unice". Metodele private sunt de asemenea stivuite rapid.

Această clasă nu are nevoie să cunoască majoritatea lucrurilor care sunt în prezent în ea. Vom împărți această funcție în câteva clase și vom vedea dacă complexitatea de a avea mai multe clase / obiecte merită liposucția.

"rubin

clasa Spectre < ActiveRecord::Base has_many :spectre_members has_many :spectre_agents has_many :enemy_agents has_many :operations

...

def turn_enemy_agent Interrogator.new (enemy_agent) .turn sfârșit

privat

def enemy_agent self.enemy_agents.last sfârșitul final

clasa de interogator attr_reader: enemy_agent

def initialize (enemy_agent) @enemy_agent = finalul inamicului_agent

def întoarce enemy_agent.turn sfârșitul final

clasa EnemyAgent < ActiveRecord::Base belongs_to :spectre belongs_to :agency

def turnul pune "După spălarea creierului extinse, tortura și hoards de numerar ..." sfârșitul sfârșitul

clasa MI6Agent < EnemyAgent def turn super puts “MI6 agent #name turned over to Spectre” end end

clasa CiaAgent < EnemyAgent def turn super puts “CIA agent #name turned over to Spectre” end end

clasa MossadAgent < EnemyAgent def turn super puts “Mossad agent #name turned over to Spectre” end end

clasa NumberOne < ActiveRecord::Base def dispose_of_cabinet_member(number) spectre_member = SpectreMember.find_by_id(number)

pune "un anumit vinovat a eșuat integritatea absolută a acestei fraternități. Actul potrivit este să fumezi numărul # numeric în scaunul lui.] Serviciile sale nu vor fi ratate" spectre_member.die end end 

clasă < ActiveRecord::Base has_many :spectre_agents belongs_to :spectre

def print_assignment afișează "Obiectivul operației # name este la obiectivul # objective."

clasa SpectreAgent < ActiveRecord::Base belongs_to :operation belongs_to :spectre

def kill_james_bond pune "domnul Bond, mă aștept să mori! "

clasa SpectreMember < ActiveRecord::Base belongs_to :spectre

Def die pune "Nooo, nooo, nu a fost meeeeeeeee! ZCHUNK! "Sfârșitul final

"

Cred că cea mai importantă parte pe care ar trebui să o acorzi este că am folosit o clasă simplă de Ruby interogator să se ocupe de întoarcerea agenților de la diferite agenții. Exemplele din lumea reală ar putea reprezenta un convertor care, spre exemplu, transformă un document HTML într-un pdf și invers. Dacă nu aveți nevoie de funcționalitatea completă a claselor Active Record, de ce să le folosiți dacă o clasă Ruby simplă poate face și trucul? Un pic mai puțin frânghie să ne agățăm.

Clasa Spectrelor lasă să devină afacerea urâtă de a transforma agenții în interogator clasă și doar deleagă la ea. Aceasta are acum singura responsabilitate de a tortura și de a spăla creierul agenți capturați.

Până acum, bine. Dar de ce am creat clase separate pentru fiecare agent? Simplu. În loc de a extrage direct diferitele metode de rotire cum ar fi turn_mi6_agent până la interogator, le-am dat o casă mai bună în clasa lor.

Ca rezultat, putem folosi eficient polimorfismul și nu ne pasă de cazurile individuale pentru agenții de cotitură. Spunem acestor obiecte diferite de agenți să se întoarcă și fiecare dintre ei știe ce să facă. interogator nu are nevoie să știe specificul despre modul în care fiecare agent se transformă.

Deoarece toți acești agenți sunt obiecte Active Record, am creat o generică, EnemyAgent, care are un sentiment general despre ceea ce înseamnă răsucirea unui agent și încapăm acel bit pentru toți agenții într-un singur loc prin subclasarea acestuia. Facem uz de această moștenire furnizând viraj metodele diferitelor agenți cu super, și, prin urmare, avem acces la afacerile de spălare a creierului și tortură, fără a se suprapune. Responsabilitățile unice și nici o dublare nu reprezintă un bun punct de pornire pentru a trece mai departe.

Celelalte clase Active Record își asumă diverse responsabilități pe care Specter nu trebuie să le pese. "Numărul unu", de obicei, face grilling-ul propriu-zis al membrilor cabinetului Spectre, deci de ce să nu lăsați un obiect dedicat să manipuleze electrocutarea? Pe de altă parte, membrii Spectre care nu reușesc să știe să se muri când fumează în scaun Numarul unu. operație acum își tipărește misiunile în sine - nu este nevoie să pierdeți timpul Spectrului cu arahidele de genul acesta.

Nu în ultimul rând, uciderea lui James Bond este de obicei încercată de un agent în domeniu, deci kill_james_bond este acum o metodă SpectreAgent. Goldfinger s-ar fi confruntat cu asta altfel, bineînțeles - trebuie să se joace cu chestia asta cu laser dacă ai unul, cred.

După cum puteți vedea clar, acum avem zece clase în care am avut anterior doar una. Nu este prea mult? Poate fi sigur. Este o problemă pe care va trebui să o luptați cu cea mai mare parte a timpului atunci când împărțiți astfel de responsabilități. Cu siguranță puteți exagera acest lucru. Dar privirea la un alt unghi ar putea ajuta:

  • Ne-am separat preocupările? Absolut!
  • Avem clase ușoare și slabe care sunt mai potrivite pentru a gestiona responsabilitățile singulare? Destul de sigur!
  • Spunem o "poveste", pictăm o imagine mai clară despre cine este implicat și este responsabil pentru anumite acțiuni? așa sper!
  • Este mai ușor de digerat ceea ce face fiecare clasă? Desigur!
  • Am redus numărul metodelor private? Da!
  • Aceasta reprezintă o calitate mai bună a programării orientate pe obiecte? Deoarece am folosit compoziția și ne-am referit la moștenire numai acolo unde este necesar pentru înființarea acestor obiecte, pariezi!
  • Se simte mai curat? da!
  • Suntem mai bine pregătiți pentru a ne schimba codul fără a face mizerie? Lucru sigur!
  • A meritat? Tu ce crezi?

Nu presupun că aceste întrebări trebuie să fie verificate de fiecare dată de pe lista dvs., dar acestea sunt lucrurile pe care ar trebui probabil să le începeți să vă întrebați în timp ce vă slăbiți modelele.

Proiectarea modelelor subțiri poate fi dificilă, dar este o măsură esențială pentru a menține aplicațiile sănătoase și agile. Acestea nu sunt singurele moduri constructive de a trata modelele grase, dar sunt un început bun, mai ales pentru începători.

Lipsa suitei de testare

Acesta este probabil cel mai evident AntiPattern. Venind din partea testată a lucrurilor, atingerea unei aplicații mature care nu are acoperire de testare poate fi una dintre cele mai dureroase experiențe de întâlnit. Dacă vrei să urăști lumea și propria ta profesie mai mult decât orice, trebuie doar să petreci șase luni la un astfel de proiect și vei învăța cât de mult de un misantrope este potențial în tine. Kidding, desigur, dar mă îndoiesc că te va face mai fericit și că vrei să o faci din nou - vreodată. Poate o săptămână va face și ea. Sunt sigur că cuvântul tortură va apărea în mintea ta mai des decât crezi.

Dacă testarea nu a făcut parte din procesul dvs. până acum și acel fel de durere este normal pentru munca dvs., poate că ar trebui să vă gândiți că testarea nu este atât de rea, și nici nu este dușmanul vostru. Atunci când nivelurile de bucurie legate de cod sunt mai mult sau mai puțin constant peste zero și vă puteți schimba fără teamă codul, atunci calitatea generală a muncii dvs. va fi mult mai mare comparativ cu rezultatul care este afectat de anxietate și suferință.

Îți supraestimez? Chiar nu cred! Doriți să aveți o acoperire foarte vastă a testelor, nu numai pentru că este un instrument de design excelent pentru scrierea doar a codului de care aveți nevoie, ci și pentru că va trebui să vă schimbați codul la un moment dat în viitor. Veți fi mult mai bine pregătit pentru a vă implica în codul dvs. - și mult mai încrezător - dacă aveți un ham de testare care ajută și îndrumă refactorizări, întreținere și extensii. Acestea se vor întâmpla cu siguranță în jos pe drum, cu zero îndoieli în legătură cu asta.

Acesta este și punctul în care o suită de testare începe să plătească a doua rundă de dividende, deoarece viteza crescută cu care puteți efectua în siguranță aceste schimbări de calitate nu poate fi obținută printr-o lovitură lungă în aplicații care sunt făcute de oameni care gândesc la testele de scris este nonsens sau durează prea mult timp.

Modele voyeuriste

Acestea sunt modele care sunt super nasy și doresc să adune prea multe informații despre alte obiecte sau modele. Acest lucru este în contrast puternic cu una dintre cele mai fundamentale idei în programarea orientată pe obiecte-încapsulare. Dorim mai degrabă să ne străduim pentru clasele și modelele proprii, care să-și administreze afacerile interne cât mai mult posibil. În ceea ce privește conceptele de programare, aceste modele voyeuriste încalcă în principiu "Principiul celor mai mici cunoștințe", adică "Legea lui Demeter" - oricum doriți să-l pronunți.

Legea lui Demeter

De ce este o problemă? Este o formă de duplicare - una subtilă - și, de asemenea, duce la un cod care este mult mai fragil decât era anticipat.

Legea lui Demeter este destul de mult cel mai fiabil cod miros pe care îl puteți ataca întotdeauna fără a fi îngrijorat de posibilele dezavantaje.

Cred că a numi aceasta "lege" nu a fost la fel de pretențioasă cum ar putea suna la început. Dig în acest miros, pentru că veți avea nevoie de ea foarte mult în proiectele dvs. În principiu, afirmă că, în termeni de obiecte, poți să apelezi metode pe prietenul obiectului tău, dar nu pe prietenul prietenului tău.

Aceasta este o modalitate obișnuită de ao explica și totul se reduce la utilizarea a cel mult un singur punct pentru apelurile dvs. de metode. Apropo, este bine să utilizați mai multe puncte sau apeluri de metode atunci când vă ocupați cu un singur obiect care nu încearcă să atingă mai mult de atât. Ceva asemănător cu @ arme.find_by_name (formula "Poison dart") este bine. Finders poate aduna cateva puncte uneori. Incapsuirea lor în metode dedicate este totuși o idee bună.

Legea încălcărilor Demeter

Să examinăm câteva exemple rele din clasele de mai sus:

"rubin

@ operation.spectre_agents.first.kill_james_bond

@ spectre.operations.last.spectre_agents.first.name

@ spectre.enemy_agents.last.agency.name

"

Pentru a obține o clipă, iată câteva câteva ficționale:

"rubin

@ quartermaster.gizmos.non_lethal.favorite

@ mi6.operation.agent.favorite_weapon

@ mission.agent.name

"

Banane, nu? Nu arată bine, nu-i așa? După cum puteți vedea, aceste metode solicită să privească prea mult în afacerea altor obiecte. Cea mai importantă și evidentă consecință negativă este schimbarea unei mulțimi de astfel de apeluri de metode peste tot în cazul în care structura acestor obiecte trebuie să se schimbe - pe care o vor avea în cele din urmă, deoarece singura constantă în dezvoltarea software-ului se schimbă. De asemenea, arată foarte urât, nu ușor pe ochi. Când nu știți că aceasta este o abordare problematică, Rails vă permite să luați acest lucru foarte departe oricum - fără a vă striga. O mulțime de funii, amintiți-vă?

Deci, ce putem face despre asta? La urma urmei, vrem să obținem informațiile în felul ăsta. Pentru un singur lucru, putem să ne compunem obiectele pentru a se potrivi nevoilor noastre și putem folosi în mod inteligent delegarea pentru a păstra în același timp modelele noastre subțiri. Să ne aruncăm într-un anumit cod pentru a vă arăta ce vreau să spun.

"rubin

clasa SpectreMember < ActiveRecord::Base has_many :operations has_many :spectre_agents

...

Sfârșit

clasă < ActiveRecord::Base belongs_to :spectre_member

...

Sfârșit

clasa SpectreAgent < ActiveRecord::Base belongs_to :spectre_member

...

Sfârșit

@ spectre_member.spectre_agents.all @ spectre_member.operations.last.print_assignment @ spectre_member.spectre_agents.find_by_id (1) .name

@ operation.spectre_member.name @ operation.spectre_member.number @ operation.spectre_member.spectre_agents.first.name

@ spectre_agent.spectre_member.number

"

"rubin

clasa SpectreMember < ActiveRecord::Base has_many :operations has_many :spectre_agents

...

def list_of_agents spectre_agents.all end

def print_operation_details operation = Operation.last operation.print_operation_details end end

clasă < ActiveRecord::Base belongs_to :spectre_member

...

def spectre_member_name spectre_member.name sfârșitul

def spectre_member_number spectre_member.number end

def print_operation_details pune "Obiectivul acestei operații este # objective. Ținta este # # target "sfârșitul final

clasa SpectreAgent < ActiveRecord::Base belongs_to :spectre_member

...

def superior_in_charge pune "Șeful meu este numărul # spectre_member.number" sfârșitul final

@ spectre_member.list_of_agents @ spectre_member.print_operation_details

@ operation.spectre_member_name @ operation.spectre_member_number

@ spectre_agent.superior_in_charge

"

Acesta este cu siguranță un pas în direcția cea bună. După cum puteți vedea, am împachetat informațiile pe care vrem să le achiziționăm într-o grămadă de metode de împachetare. În loc să ajungem direct la multe obiecte în mod direct, am abstracționat aceste poduri și lăsăm-o pe modelele respective pentru a vorbi cu prietenii despre informațiile de care avem nevoie.

Dezavantajul acestui demers este acela de a avea toate aceste metode suplimentare de înfășurare situate în jur. Uneori este bine, dar într-adevăr ne dorim să evităm menținerea acestor metode într-o grămadă de locuri dacă un obiect se schimbă.

Dacă este posibil, locul dedicat pentru a le schimba este pe obiectul lor - și numai pe obiectul lor. Politizarea obiectelor cu metode care nu au nimic de-a face cu propriul model propriu-zis este, de asemenea, un lucru de luat în seamă, deoarece acesta este întotdeauna un pericol potențial pentru aplatizarea unor responsabilități unice.

Putem face mai bine decât asta. Unde este posibil, permiteți delegarea metodei de a solicita direct obiectele lor responsabile și încercați să reduceți la maximum metodele de împachetare cât de mult putem. Rails știe ce avem nevoie și ne oferă cu ușurință delega metoda de clasă pentru a spune prietenilor obiectului nostru ce metode avem nevoie.

Să examinăm ceva din exemplul anterior al codului și să vedem unde putem folosi delegarea corect.

"rubin

clasă < ActiveRecord::Base belongs_to :spectre_member

delegat: nume,: număr, la:: spectre_member, prefix: true

...

def spectre_member_name

# spectre_member.name # end

def spectre_member_number

# spectre_member.number # end

...

Sfârșit

@ operation.spectre_member_name @ operation.spectre_member_number

clasa SpectreAgent < ActiveRecord::Base belongs_to :spectre_member

delegat: numărul, la:: spectre_member, prefix: true

...

def superior_in_charge pune "Șeful meu este numărul # spectre_member_number"

...

Sfârșit

"

După cum puteți vedea, am putea simplifica lucrurile puțin folosind delegarea metodei. Am scăpat Funcționare # spectre_member_name și Funcționare # spectre_member_number complet, și SpectreAgent nu are nevoie să sune număr pe spectre_member mai-număr este delegat înapoi direct la clasa de "origine" SpectreMember.

În cazul în care acest lucru este puțin confuz la început, cum funcționează acest lucru exact? Spuneți delegaților care : METHOD_NAME ar trebui să delege la: care :numele clasei (mai multe nume de metode sunt bine prea). prefix: true parte este opțională.

În cazul nostru, a prefixat numele clasei șarsei primite din clasa primită înainte de numele metodei și ne-a permis să sunăm operation.spectre_member_name în loc de potențial ambiguu operation.name-dacă nu am fi utilizat opțiunea de prefix. Acest lucru funcționează într-adevăr frumos cu aparține lui și are unul asociațiile.

Pe are multe partea de lucruri, totuși, muzica se va opri și veți avea probleme. Aceste asociații vă oferă un proxy de colectare care va arunca numele dvs. de nume sau NoMethodErrors la dvs. când delegați metode la aceste "colecții".

Spaghetti SQL

Pentru a încheia acest capitol despre modelul AntiPatterns în Rails, aș dori să-mi petrec puțin timp pe ce să evit atunci când este implicat SQL. Asociațiile active de înregistrări oferă opțiuni care vă fac viața mult mai ușoară atunci când sunteți conștienți de ce ar trebui să vă îndepărtați. Metodele de găsire sunt un subiect integral pe cont propriu - și nu le vom acoperi în profunzime - dar am vrut să menționez câteva tehnici comune care vă ajută chiar și atunci când scrieți foarte simplu.

Lucrurile pe care ar trebui să ni-l îngrijoreze mai multe dintre ceea ce am învățat până acum. Vrem să găsim metode de descoperire a intențiilor, simple și rezonabile pentru a găsi lucruri în modelele noastre. Hai să ne aruncăm în cod.

"rubin

clasă < ActiveRecord::Base

has_many: agenți

...

Sfârșit

Agent de clasă < ActiveRecord::Base

belongs_to: operație

...

Sfârșit

clasa OperationsController < ApplicationController

def index @operation = Operare.find (params [: id]) @agents = Agent.where (end_code: operation_id: @ operation.id, license_to_kill: true)

"

Pare inofensiv, nu? Căutăm doar o mulțime de agenți care au licența de a ucide pentru pagina noastră de operațiuni. Mai gandeste-te. De ce ar trebui să OperationsController săpați în interiorul lui Agent? De asemenea, este cu adevărat cel mai bun lucru pe care îl putem face pentru a încapsula un căutător Agent?

Dacă vă gândiți că puteți adăuga o metodă de clasă cum ar fi Agent.find_licence_to_kill_agents care încapsulează logica căutătorului, faceți cu siguranță un pas în direcția cea bună - însă nu este suficient.

"rubin

Agent de clasă < ActiveRecord::Base

belongs_to: operație

def self.find_licence_to_kill_agents (operațiune) unde (operation_id: operation.id, license_to_kill: true) sfârșitul ...

Sfârșit

clasa OperationsController < ApplicationController

def index @operation = Operare.find (params [: id]) @agents = Agent.find_licence_to_kill_agents (@operation) end end

"

Trebuie să fim mai puțin implicați decât asta. În primul rând, acest lucru nu folosește asociațiile în avantajul nostru, iar încapsularea este, de asemenea, suboptimală. Asociații cum ar fi are multe vin cu beneficiul pe care îl putem adăuga la matricea proxy pe care o primim. Am fi putut face asta în schimb:

"rubin

clasă < ActiveRecord::Base

has_many: agenți

def find_licence_to_kill_agents self.agents.where (license_to_kill: true) sfârșitul ...

Sfârșit

clasa OperationsController < ApplicationController

def index @operation = Operare.find (params [: id]) @agents = @ operation.find_licence_to_kill_agents end end

"

Acest lucru funcționează, sigur, dar este, de asemenea, doar un mic pas în direcția cea bună. Da, controlorul este puțin mai bun și folosim bine asociațiile de model, dar ar trebui să fii suspicios de ce operație este preocupat de punerea în aplicare a găsirii unui anumit tip de Agent. Această responsabilitate aparține Agent modelul însuși.

Domeniile numite vin destul de la îndemână cu asta. Scopes definește metode clasificabile-foarte importante pentru modelele dvs. și astfel vă permit să specificați interogări utile pe care le puteți utiliza ca apeluri de metode suplimentare pe lângă asociațiile dvs. de model. Următoarele două abordări pentru definirea domeniului Agent sunt indiferenți.

"rubin

Agent de clasă < ActiveRecord::Base belongs_to :operation

domeniul de aplicare: licenced_to_kill, -> where (license_to_kill: true) sfârșit

Agent de clasă < ActiveRecord::Base belongs_to :operation

def self.licenced_to_kill unde (license_to_kill: true) se termină

clasa OperationsController < ApplicationController

def index @operation = Operare.find (params [: id]) @agents = @ operation.agents.licenced_to_kill sfârșitul final

"

Este mult mai bine. În cazul în care sintaxa de domenii este nouă pentru tine, ele sunt doar lambda - nu este foarte important să le privești imediat, apropo - și sunt calea potrivită pentru a numi domenii deoarece Rails 4. Agent este acum responsabil de gestionarea propriilor parametri de căutare, iar asociațiile pot doar să-și pună ce trebuie să găsească.

Această abordare vă permite să obțineți interogări ca apeluri SQL unice. Îmi place personal să o folosesc domeniu pentru explicabilitatea sa. Scopes sunt, de asemenea, foarte util la lanț în interiorul bine-numit metode finder - în acest fel ele cresc posibilitatea de a reutiliza cod și DRY-ing cod. Să presupunem că avem ceva mai mult implicat:

"rubin

Agent de clasă < ActiveRecord::Base belongs_to :operation

Domeniul de aplicare: licenced_to_kill, -> where (license_to_kill: true) Domeniul de aplicare: womanizer, -> where (womanizer: true) Domeniul de aplicare: bond, -> where (name: James Bond) > unde (gambler: true) se termină

"

Acum putem folosi toate aceste domenii pentru a construi proprietăți mai complexe.

"rubin

clasa OperationsController < ApplicationController

def index @operation = Operare.find (params [: id]) @double_o_agents = @ operation.agents.licenced_to_kill end

def show @operation = Operațiunea.find (params [: id]) @bond = @ operation.agents.womanizer.gambler.licenced_to_kill end

… Sfârșit

"

Sigur, asta funcționează, dar aș vrea să vă sugerez să mergeți cu un pas mai departe.

"rubin

Agent de clasă < ActiveRecord::Base belongs_to :operation

Domeniul de aplicare: licenced_to_kill, -> where (license_to_kill: true) Domeniul de aplicare: womanizer, -> where (womanizer: true) Domeniul de aplicare: bond, -> where (name: James Bond) > unde (jucător: adevărat)

def self.find_licenced_to_kill licensed_to_kill sfârșitul

def self.find_licenced_to_kill_womanizer womanizer.licenced_to_kill end

def self.find_gambling_womanizer sfârșitul gambler.womanizer

...

Sfârșit

clasa OperationsController < ApplicationController

def index @operation = Operare.find (params [: id]) @double_o_agents = @ operation.agents.find_licenced_to_kill end

def show @operation = Operațiunea.find (params [: id]) @bond = @ operation.agents.find_licenced_to_kill_womanizer #or @bond = @ operation.agents.bond end

...

Sfârșit

"

După cum puteți vedea, prin această abordare obținem beneficii de încapsulare adecvată, asociații de modele, reutilizarea codului și denumirea expresivă a metodelor - și toate acestea în timp ce facem interogări SQL unice. Nu mai aveți cod spaghetti, minunat!

Dacă sunteți îngrijorat de încălcarea Legii lui Demeter thingie, veți fi încântați să auzim că, din moment ce nu adăugăm puncte atingând modelul asociat, ci legându-i numai pe propriul obiect, nu comitem nici un fel de infracțiuni de la Demeter.

Gândurile finale

Din perspectiva unui începător, cred că ați învățat multe despre o mai bună manevrare a modelelor Rails și cum să le modelați mai robust, fără a chema un hangman.

Nu vă lăsați păcăliți, însă, gândindu-vă că nu mai sunt multe de învățat pe acest subiect. Ți-am prezentat câteva AntiPatternuri pe care cred că noii bătrâni sunt capabili să înțeleagă și să o manipuleze cu ușurință pentru a se proteja mai devreme. Dacă nu știți ce nu știți, este disponibilă o grămadă de funii pentru a vă bucura în jurul gâtului.

Deși acest lucru a fost un început solid în acest subiect, nu sunt doar mai multe aspecte ale AntiPatterns în modelele Rails, dar și mai multe nuanțe pe care va trebui să le explorați, de asemenea. Acestea au fost elementele de bază - foarte esențiale și importante - și ar trebui să vă simțiți îndeplinite pentru puțin timp în care nu ați așteptat

Cod