Skip to content
Menu
CDhistory
CDhistory

Ruby Method Auditing Using Module#prepend

Posted on decembrie 12, 2021 by admin

Ieri dădeam un interviu pentru un post de dezvoltator Ruby la o companie din Denver, când intervievatorul meu mi-a pus o bună provocare de metaprogramare: mi-a cerut să scriu un auditor automat de metode.

El a descris o metodă de clasă care să ia numele unei metode de instanță și să genereze o urmă de hârtie automată, înregistrată, de fiecare dată când acea metodă țintă este apelată. Ceva de genul acesta:

Ceea ce ar produce o ieșire care ar arăta astfel:

Am venit cu o soluție care a funcționat, dar care a implicat niște hack-uri potențial periculoase pentru Object#send. Înainte de a-mi prezenta soluția, am spus „probabil că aceasta este o idee proastă, dar…” și am avut dreptate. Ideile bune rareori încep așa.

Soluția pe care a propus-o a implicat folosirea deseori trecută cu vederea Module#prepend și am crezut că este suficient de mișto pentru a justifica o prezentare.

Strategia a fost de a folosi comportamentul lui Module#prepend pentru a crea o metodă de înfășurare generată dinamic pentru Messenger#share.

În acel wrapper, vom avea un mesaj „Performing” care se va declanșa înainte de executarea corpului metodei, execuția propriu-zisă a metodei și apoi un mesaj final „Exiting” odată ce execuția metodei s-a încheiat cu succes.

Dar pentru a înțelege cum să folosim Module#prepend pentru a face acest lucru, avem nevoie mai întâi de o bună înțelegere a modului în care funcționează moștenirea metodelor în Ruby.

  • Ancestor Chains
  • Modulul în sine
  • Consumând totul
  • Concluzie

Ancestor Chains

Care obiect Ruby se află la capătul a ceea ce se numește un Ancestor Chain. Acesta este o listă a obiectelor strămoșești de la care obiectul moștenește. Ruby folosește acest lanț al strămoșilor pentru a determina ce versiune a unei metode (dacă există) este executată atunci când obiectul primește un mesaj.

De fapt, puteți vizualiza arborele strămoșilor pentru orice obiect prin apelarea Module#ancestors. De exemplu, iată lanțul strămoșilor pentru clasa noastră Messenger:

Când #include sau #prepend un modul într-o clasă, Ruby face schimbări în lanțul strămoșilor acelei clase, modificând ce metode sunt găsite și în ce ordine. Marea diferență între #include și #prepend este unde se face această modificare. Să creăm un modul numit Auditable care va conține (în cele din urmă) codul care face magia auditului nostru:

Dacă folosim Module#include pentru a importa metodele (în prezent inexistente) din Auditable, Ruby va strecura acel modul ca strămoș al clasei noastre Messenger. Aici, vedeți cu ochii voștri:

Când apelăm o metodă din Messenger, Ruby se va uita la ceea ce este definit în clasa Messenger și – dacă nu găsește o metodă care să corespundă apelului – urcă în lanțul de strămoși până la Auditable pentru a căuta din nou. Dacă nu găsește ceea ce caută la Auditable, trece la Object și așa mai departe.

Aceasta este #include. Dacă în schimb folosim Module#prepend pentru a importa conținutul modulului, obținem un efect total diferit:

#prepend face din Messenger un strămoș al lui Auditable. Așteptați. Ce?

Aceasta ar putea părea că Messenger este acum o superclasă a lui Auditable, dar nu este exact ceea ce se întâmplă aici. Instanțele clasei Messenger sunt tot instanțe ale clasei Messenger, dar Ruby va căuta acum metode de utilizat pentru Messenger în modulul Auditable înainte de a le căuta în clasa Messenger însăși.

Și de asta, prieteni, vom profita pentru a construi acest auditor: dacă vom crea o metodă numită Auditable#share, Ruby o va găsi înainte de a o găsi pe Messenger#share. Putem folosi apoi un apel super în Auditable#share pentru a accesa (și executa!) metoda originală definită pe Messenger.

Modulul în sine

Nu vom crea de fapt o metodă numită Auditable#share. De ce nu? Pentru că dorim ca acesta să fie un utilitar flexibil. Dacă vom codifica în forță metoda Auditable#share, vom putea să o folosim doar pe metodele care implementează metoda #share. Sau, mai rău, va trebui să reimplementăm același model de auditor pentru fiecare metodă pe care vrem să o auditam. Nu, mulțumesc.

Așa că, în schimb, vom defini metoda noastră în mod dinamic și metoda clasei audit_method pentru a o declanșa:

Când audit_method este apelată într-o clasă care o implementează, se creează o metodă în modulul Auditable cu același nume ca și metoda de auditat. În cazul nostru, se va crea o metodă numită Auditable#share. După cum am menționat, Ruby va găsi această metodă înainte de a găsi metoda originală de pe Messenger, deoarece preapreciem modulul Auditable în clasa de implementare.

Acest lucru înseamnă că putem folosi un apel super pentru a ajunge în susul lanțului de strămoși și a executa Messenger#send. Când facem acest lucru, transmitem și argumentele pe care le-am colectat (*arguments) în susul lanțului.

După ce am apelat metoda originală, tipărim mesajul nostru de ieșire și ne încheiem ziua. Bună treabă, gașcă!

Consumând totul

Acum este doar o chestiune de a prependa introduce acest modul în clasa noastră Messenger și ar trebui să putem începe:

Și, Doamne ferește, funcționează:

Implicațiile de aici sunt uriașe pentru audit, dar există mai multe lucruri în acest truc. Puteți folosi prepending-ul pentru a schimba comportamentul obiectelor fără a schimba obiectele în sine. Acesta este un mod adaptabil și clar de a crea componente de ordin superior în Ruby. Testarea performanței, gestionarea erorilor. Puteți face multe aici.

Concluzie

Modulele Ruby sunt mai complicate decât cred majoritatea oamenilor. Nu este la fel de simplu ca și cum ai „arunca” codul dintr-un loc în altul, iar înțelegerea acestor diferențe deblochează niște instrumente foarte bune pentru centura ta de utilități.

Ați observat poate că astăzi am vorbit doar despre Module#include și Module#prepend și că nu am atins Module#extend. Acest lucru se datorează faptului că funcționează foarte diferit față de verii săi. Voi scrie în curând o explicație în profunzime despre Module#extend pentru a completa setul.

Pentru moment, dacă doriți să aflați mai multe vă recomand să citiți modulele Ruby: Include vs Prepend vs Extend de Léonard Hetsch. A fost foarte utilă pentru a pune toate acestea cap la cap.

.

Lasă un răspuns Anulează răspunsul

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *

Articole recente

  • Acela s-a întors: NYC sau Boston pentru 99 de dolari
  • Părinții lui Kate Albrecht – Aflați mai multe despre tatăl ei, Chris Albrecht, și despre mama ei, Annie Albrecht
  • Temple Fork Outfitters
  • Burr (roman)
  • Trek Madone SLR 9 Disc

Arhive

  • februarie 2022
  • ianuarie 2022
  • decembrie 2021
  • noiembrie 2021
  • octombrie 2021
  • septembrie 2021
  • august 2021
  • iulie 2021
  • iunie 2021
  • mai 2021
  • aprilie 2021
  • DeutschDeutsch
  • NederlandsNederlands
  • SvenskaSvenska
  • DanskDansk
  • EspañolEspañol
  • FrançaisFrançais
  • PortuguêsPortuguês
  • ItalianoItaliano
  • RomânăRomână
  • PolskiPolski
  • ČeštinaČeština
  • MagyarMagyar
  • SuomiSuomi
  • 日本語日本語
©2022 CDhistory | Powered by WordPress & Superb Themes