Skip to content
Menu
CDhistory
CDhistory

Ruby Method Auditing Using Module#prepend

Posted on december 12, 2021 by admin

Gisteren was ik aan het interviewen voor een Ruby developer positie bij een bedrijf hier in Denver toen mijn interviewer me voor een goede metaprogrammering uitdaging stelde: hij vroeg me om een geautomatiseerde methode auditor te schrijven.

Hij beschreef een klasse methode die de naam van een instance methode zou nemen en een automatisch, gelogd papieren spoor zou genereren elke keer dat die target methode wordt aangeroepen. Zoiets als dit:

Dat zou output opleveren die er als volgt uitzag:

Ik kwam met een oplossing die werkte, maar die enkele potentieel gevaarlijke hacks in Object#send inhield. Voordat ik mijn oplossing presenteerde, zei ik “dit is waarschijnlijk een slecht idee, maar…” en ik had gelijk. Goede ideeën beginnen zelden zo.

De oplossing die hij voorstelde betrof het gebruik van de vaak over het hoofd geziene Module#prepend en ik dacht dat het cool genoeg was om een write-up te rechtvaardigen.

De strategie was om het gedrag van Module#prepend te gebruiken om een dynamisch gegenereerde wrapper methode voor Messenger#share te maken.

In die wrapper laten we een “Uitvoeren” bericht afvuren voordat de body van de methode wordt uitgevoerd, de uitvoering van de methode zelf, en dan een laatste “Afsluiten” bericht zodra de uitvoering van de methode met succes is voltooid.

Maar om te begrijpen hoe Module#prepend te gebruiken om dit te doen, moeten we eerst een goed begrip hebben van hoe methode overerving werkt in Ruby.

  • Ancestor Chains
  • De module zelf
  • Bringing it all Together
  • Conclusie

Ancestor Chains

Elk Ruby object zit aan het eind van wat een Ancestor Chain wordt genoemd. Het is een lijst van de voorouder objecten waar het object van erft. Ruby gebruikt deze Ancestor Chain om te bepalen welke versie van een methode (als die er al is) wordt uitgevoerd wanneer het object een bericht ontvangt.

U kunt in feite de voorouderboom voor elk object bekijken door Module#ancestors op te roepen. Hier is bijvoorbeeld de voorouder-keten voor onze klasse Messenger:

Wanneer we #include of #prepend een module in een klasse plaatsen, brengt Ruby wijzigingen aan in de voorouder-keten van die klasse, waarbij wordt aangepast welke methoden worden gevonden en in welke volgorde. Het grote verschil tussen #include en #prepend is waar die verandering wordt aangebracht. Laten we een module met de naam Auditable maken, die (uiteindelijk) de code zal bevatten die onze controle magie uitvoert:

Als we Module#include gebruiken om de (momenteel niet-bestaande) methoden uit Auditable te importeren, zal Ruby die module erin persen als een voorouder van onze Messenger klasse. Kijk zelf maar:

Wanneer we een methode op Messenger aanroepen, zal Ruby kijken naar wat er is gedefinieerd in de Messenger klasse en – als het geen methode kan vinden die overeenkomt met de aanroep – omhoog klimmen in de voorouder keten naar Auditable om opnieuw te kijken. Als het niet vindt wat het zoekt in Auditable, gaat het verder naar Object enzovoort.

Dat is #include. Als we in plaats daarvan Module#prepend gebruiken om de inhoud van de module te importeren, krijgen we een heel ander effect:

#prepend maakt Messenger een voorouder van Auditable. Wacht. Wat?

Dit lijkt erop dat Messenger nu een superklasse is van Auditable, maar dat is niet precies wat hier gebeurt. Instanties van klasse Messenger zijn nog steeds instanties van klasse Messenger, maar Ruby zal nu zoeken naar methoden om te gebruiken voor Messenger in de Auditable module voordat het zoekt naar hen op de Messenger klasse zelf.

En dat, vrienden, is waar we gebruik van zullen maken om deze auditor te bouwen: als we een methode genaamd Auditable#share maken, zal Ruby dat vinden voordat het Messenger#share vindt. We kunnen dan een super aanroep in Auditable#share gebruiken om toegang te krijgen (en uit te voeren!) tot de oorspronkelijke methode gedefinieerd op Messenger.

De module zelf

We gaan niet echt een methode genaamd Auditable#share maken. Waarom niet? Omdat we willen dat dit een flexibel hulpprogramma wordt. Als we de methode Auditable#share hard coderen, kunnen we deze alleen gebruiken voor methoden die de methode #share implementeren. Of erger nog, we zouden dit zelfde auditor patroon opnieuw moeten implementeren voor elke methode die we ooit willen controleren. Geen dank.

Dus in plaats daarvan gaan we onze methode dynamisch definiëren en de audit_method-klasse methode om het af te vuren:

Wanneer audit_method wordt aangeroepen in een implementerende klasse, wordt er een methode aangemaakt in de Auditable-module met dezelfde naam als de te controleren methode. In ons geval wordt een methode genaamd Auditable#share aangemaakt. Zoals gezegd, Ruby zal deze methode vinden voordat het de originele methode op Messenger vindt, omdat we de Auditable module in de implementerende klasse prependen.

Dit betekent dat we een super aanroep kunnen gebruiken om de voorouder keten te bereiken en Messenger#send uit te voeren. Als we dat doen, geven we de argumenten die we hebben verzameld (*arguments) ook door.

Als we de oorspronkelijke methode hebben aangeroepen, drukken we onze exit-boodschap af en houden we op. Goed werk, jongens!

Bringing it all Together

Nu is het alleen nog een kwestie van prepend deze module in onze Messenger klasse te plaatsen en we zouden goed moeten zijn om te gaan:

En jeetje het werkt:

De implicaties hier zijn enorm voor auditing, maar er is meer aan deze truc. Je kunt prepending gebruiken om het gedrag van objecten te veranderen zonder de objecten zelf te veranderen. Dit is een aanpasbare en duidelijke manier om componenten van hogere orde te maken in Ruby. Prestatie testen, fout afhandeling. Je kunt hier veel doen.

Conclusie

Ruby modules zijn ingewikkelder dan de meeste mensen denken. Het is niet zo eenvoudig als het “dumpen” van code van de ene plaats naar de andere, en het begrijpen van die verschillen ontsluit een aantal echt leuke tools voor uw hulpriem.

Het is u misschien opgevallen dat ik het vandaag alleen over Module#include en Module#prepend heb gehad, en dat ik Module#extend niet heb aangeraakt. Dat komt omdat het heel anders werkt dan zijn neven. Ik zal binnenkort een diepgaande uitleg van Module#extend schrijven om de set compleet te maken.

Voor nu, als je meer wilt leren, raad ik je aan Ruby modules te lezen: Include vs Prepend vs Extend door Léonard Hetsch. Het was echt behulpzaam bij het samenstellen van dit alles.

Geef een antwoord Antwoord annuleren

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *

Recente berichten

  • Acela is terug: NYC of Boston voor $99
  • OMIM Entry – # 608363 – CHROMOSOME 22q11.2 DUPLICATION SYNDROME
  • Kate Albrecht’s Parents – Learn More About Her Father Chris Albrecht And Mother Annie Albrecht
  • Temple Fork Outfitters
  • Burr (roman)

Archieven

  • februari 2022
  • januari 2022
  • december 2021
  • november 2021
  • oktober 2021
  • september 2021
  • augustus 2021
  • juli 2021
  • juni 2021
  • mei 2021
  • april 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