Ruby for Newbies Saknade Metoder

Ruby är ett av de mest populära språk som används på webben. Vi kör en session här på Nettuts + som kommer att introducera dig till Ruby, liksom de stora ramar och verktyg som följer med Ruby utveckling. I det här avsnittet kommer vi att se på det för coola att vara sanna sättet att Ruby-objekt hanterar metoder som inte existerar.


Video Tutorial?


Ett problem (och en lösning)

Låt oss säga att du arbetar med ett Ruby-objekt. Och låt oss också säga att du inte är helt bekant med det här objektet. Och låt oss också säga att du ringer en metod som inte existerar på objektet.

o = Object.new o.some_method # NoMethodError: odefinierad metod 'some_method' för #

Detta är mindre än önskvärt, så Ruby har ett fantastiskt sätt att låta oss rädda oss själva från detta. Kolla in det här:

klassen OurClass def method_missing (metodnamn) sätter "det finns ingen metod som heter" # method_name "" slutet slutet o = OurClass.new o.some_method # => det finns ingen metod som heter 'some_method'

Vi kan skapa en metod som heter method_missing i vår klass. Om det objekt som vi ringer på metoden inte har metoden (och inte ärver metod från en annan klass eller modul), ger Ruby oss en chans att göra något användbart: om klassen har en method_missing metod, vi lämnar informationen om metoden cal till method_missing och låt det sortera röra ut.

Jo, det är bra; Vi får inte längre ett felmeddelande.


En bättre användning

Men sluta och tänka på detta för en sekund. Först och främst: nej, vi får inte längre ett felmeddelande, men vi får inte något användbart. Det är svårt att säga vad användbart skulle vara i det här fallet, för att ut metod namn inte föreslår någonting. För det andra är detta ganska kraftfullt, eftersom det låter dig i princip passera vilken metod som helst till ett objekt och få ett intelligent resultat.

Låt oss göra något som ger mer mening; börja med detta:

klass TutsSite attr_accessor: namn,: tutorials def initialisera namn = "", tuts = [] @name = namn @tutorials = tuts avsluta def get_tuts_about_javascript @ tutorials.select do | tut | Tut [: taggar] .include? "javascript" slutänden def get_tuts_by_jeffrey_way @ tutorials.select do | tut | tut [: author] == "Jeffrey Way" slutet slutet

Här ser du en liten klass för en handledning webbplats. När vi skapar ett nytt webbobjekt, skickar vi det ett namn och en rad handledningar. Vi förväntar oss att tutorials ska vara hash i följande formulär:

title: "Some title", författare: "författaren", taggar: ["array", "of", "tags"] # Ruby 1.9 # ELLER : title => "Some title" författaren ",: tags => [" array "," of "," tags "] # Ruby 1.8

Vi förväntar oss symboler som nycklarna; märker att om du inte använder Ruby 1.9 måste du använda det undre formatet för din hash (båda fungerar i 1,9)

Sedan har vi två hjälparfunktioner som gör det möjligt för oss att få bara handledningen som har en JavaScript-tagg eller bara handledning av Jeffrey Way. Dessa är användbara för att filtrera handledningarna? men de ger oss inte för många alternativ. Självklart kan vi göra metoder som heter get_tuts_with_tag och get_tuts_by_author som tar parametrar med taggen eller författarnamnet. Vi ska dock gå en annan väg: method_missing.

Som vi såg, method_missing får försöket metodnamn som en parameter. Vad jag inte nämnde är att det är en symbol. Även de parametrar som passerar till metoden och blocket (om en gavs) är också tillgängliga. Observera att parametrarna skickas som enskilda parametrar till method_missing, så den vanliga konventionen är att använda splatoperatören för att samla dem alla i en array:

def method_missing namn, * args, och block end

Så, eftersom vi kan få namnet på den metod som försökt, kan vi tolka det namnet och göra något intelligent med det. Till exempel, om användaren ringer något så här:

nettutsatser.get_tuts_by_jeffrey_way netts.get_tuts_about_html netuts.get_tuts_about_canvas_by_rob_hawkes nettuts.get_tuts_by_jeremy_mcpeak_about_asp_net

Så, låt oss ta itu med det; skrap de tidigare metoderna och ersätt det här här:

def method_missing namn, * args, & block tuts = @ tutorials.dup name = name.to_s.downcase om (md = / ^ get_tuts_ (by_ | om _) (\ w *?) ((av _ | _ om _) (\ w *) )? $ /. matchnamn) om md [1] == 'by_' tuts.select! | tut | tut [: författare] .downcase == md [2] .gsub ("_", "") tuts.select! | tut | Tut [: taggar] .include? md [5] .gsub ("_", "") om md [4] == '_about_' elsif md [1] == 'about_' tuts.select! | tut | Tut [: taggar] .include? md [2] .gsub ("_", "") tuts.select! | tut | tut [: author] .downcase == md [5] .gsub ("_", "") om md [4] == '_by_' slut annars tuts = "Detta objekt stöder inte objektet '#  namn "" slutändarna slutar

Oroa dig inte, vi går igenom allt detta nu. Vi börjar med att duplicera @Handledningar array; varje Ruby-objekt har a dup metod som kopierar den om vi inte gjorde det här - och sagt bara tuts = @handledning-Vi skulle arbeta med den ursprungliga matrisen, som vi inte vill göra; Vi vill behålla den uppsättningen som den är. Då kommer vi att filtrera ut de tutorials som vi inte vill ha.

Vi måste också få namnet på metoden. sedan det passerat till method_missing som en symbol konverterar vi den till en sträng med to_s och se till att det är i små bokstäver med downcase.

Nu måste vi kontrollera att metoden matchar det format vi vill ha, Det är trots allt möjligt att någon kan skicka något annat till metoden. Så, låt oss analysera det metodnamnet. Om det matchar, kommer vi att träna ut magi; Annars returnerar vi ett standardfelmeddelande:

 om (md = /^get_tuts_(by_|about_)(\w*?)((_by_|_about_)(\w*))?$/.match namn) #coming else tuts = "Detta objekt stöder inte metod "# name" "slutet

Det ser ut som en ganska skrämmande, men du borde förstå det: i grund och botten letar vi efter? Get_tuts_? följt av? by_? eller? ca_ ?; då har vi en författarens namn eller en tagg följt av? _by_? eller? _about_? och en författare eller tagg. Om det matchar, lagrar vi MatchData objekt i md; annars får vi det noll tillbaka; i det fallet ställer vi in tuts till felmeddelandet. Vi gör det så att vi ändå kan återvända tuts.

Så det vanliga uttrycket matchar, vi får en MatchData objekt. Om det använda metodenavnet var get_tuts_by_andrew_burgess_about_html, det här är de index som du har:

0. get_tuts_by_andrew_burgess_about_html 1. by_ 2. andrew_burgess 3. _about_html 4. _about_ 5. html

Jag noterar att om en av de valfria grupperna inte är fylld, har indexet ett värde på noll.

Så, de data vi vill ha är i index 2 och 5; kom ihåg att vi bara kunde få en tagg, bara en författare eller båda (i båda orderen). Så nästa måste vi filtrera bort de tuttar som inte överensstämmer med våra kriterier. Vi kan göra detta med matrisen Välj metod. Den skickar varje objekt till ett block, en efter en. Om blocket återvänder Sann, objektet hålls om den återvänder falsk, objektet kastas ut ur matrisen. Låt oss börja med detta:

om md [1] == 'by_' tuts.select! | tut | tut [: författare] .downcase == md [2] .gsub ("_", "") tuts.select! | tut | Tut [: taggar] .include? md [5] .gsub ("_", "") om md [4] == '_about_'

Om md [1] är? by_?, vi vet att författaren kom först. Därför, inuti blocken av den första Välj ring, vi får tut hashs författarnamn (downcase det) och jämföra det med md [2]. Jag använder den globala substitutionsmetoden-gsub-att ersätta alla underskrifter med ett enda utrymme. Om strängarna jämför sant hålls föremålet annars är det inte. På sekunden Välj Ring, vi kontrollerar taggen (lagrad i md [5]) i tut [: taggar] array. Arrayen inkludera? Metoden kommer att återvända Sann om objektet är i matrisen. Lägg märke till modifieraren i slutet av den raden: det gör vi bara om det fjärde indexet är strängen? _About_?.

Observera att vi faktiskt använder arrayen Välj metod: vi använder Välj! (med ett bang / utropstecken). Detta returnerar inte en ny array med endast de valda objekten; det fungerar med själva tuts array i minnet.

Nu när du förstår det borde du inte ha ett problem med de följande raderna:

elsif md [1] == 'about_' tuts.select! | tut | Tut [: taggar] .include? md [2] .gsub ("_", "") tuts.select! | tut | tut [: författare] .downcase == md [5] .gsub ("_", "") om md [4] == '_by_' slutet

Dessa linjer gör detsamma som ovan, men de är för metodnamn i omvänd situation: tagg första, valfri författare sekund.

I slutet av metoden återkommer vi tuts; Det här är antingen den filtrerade arrayen eller felmeddelandet.

Nu, låt oss testa detta:

tuts = [title: "Hur man övergår en bild från B & W till Color with Canvas", författare: "Jeffrey Way", taggar: ["javascript", "canvas"], title: "Node.js steg för steg : Blogging Application ", författare:" Christopher Roach ", taggar: [" javascript "," node "], title:" De 30 CSS Selectors du måste memorera ", författare:" Jeffrey Way " "," selectors "], title:" Responsive Web Design: En visuell guide ", författare:" Andrew Gormley ", taggar: [" html "," responsiv design "], title:" Webbutveckling från grunden : Grundläggande layout ", författare:" Jeffrey Way ", taggar: [" html "], title:" Skydda en CodeIgniter Application Against CSRF ", författare:" Ian Murray ", taggar: [" php "," codeigniter " ], title: "Hantera Cron Jobs med PHP", författare: "Nikola Malich", taggar: ["php", "cron jobb"]] netuts = TutsSite.new "Netuts +", tuts p nettuts.get_tuts_by_ian_murray # [: title => "Skydda en CodeIgniter-applikation mot CSRF",: author => "Ian Murray",: tags => ["php", "codeigniter"]] p netuts.get_tuts_about_html # tle => "Responsive Web Design: en visuell guide",: author => "Andrew Gormley",: tags => ["html", "responsiv design"], : title => "Webbutveckling från grunden: Basic Layout ",: author =>" Jeffrey Way ",: tags => [" html "]] p nettuts.get_tuts_by_jeffrey_way_about_canvas # [: title =>" Hur man övergår en bild från B & W till Color with Canvas " => "Jeffrey Way",: tags => ["javascript", "kanvas"]] p nettuts.get_tuts_about_php_by_nikola_malich # [: title => "Hantera Cron Jobs med PHP",: author => "Nikola Malich" : tags => ["php", "cron jobs"]] p netuts.submit_an_article # Det här objektet stöder inte metoden 'submit_an_article' "

Jag är p-rintar ut resultaten från dessa metoder, så du kan köra detta i en ruby-fil på kommandoraden.


En varning

Jag bör nämna att medan detta är ganska coolt är det inte nödvändigtvis rätt användning av method_missing. Det är främst som en säkerhet för att rädda dig från fel. Konventionen är dock inte dålig: den används ofta i Active klasser som är en stor del av Ruby on Rails.


En bonus

Du visste inte säkert att det fanns en liknande funktion i JavaScript: det är det __noSuchMethod__ metod på objekt. Såvitt jag vet är den bara stödd i FireFox, men det är en intressant idé. Jag har omskrivet exemplet ovan i JavaScript, och du kan kolla in det här på JSBin.


Slutsats

Det är en wrap för idag! Jag har några intressanta Ruby-saker på min ärm, kommer snart till dig. Håll ögonen på Netuts +, och om du vill ha något specifikt, låt mig veta i kommentarerna!