Ruby er et av de mest populære språkene som brukes på nettet. Vi driver en sesjon her på Nettuts + som vil introdusere deg til Ruby, i tillegg til de store rammeverkene og verktøyene som følger med Ruby-utviklingen. I denne episoden skal vi se på den altfor kule-til-være-sanne måten Ruby-objektene håndterer metoder som ikke eksisterer.
La oss si at du jobber med en Ruby-gjenstand. Og la oss også si at du ikke er helt kjent med dette objektet. Og la oss også si at du kaller en metode som ikke eksisterer på objektet.
o = Object.new o.some_method # NoMethodError: udefinert metode 'some_method' for #
Dette er mindre enn ønskelig, så Ruby har en fantastisk måte å tillate oss å redde oss fra dette. Sjekk ut dette:
klasse OurClass def method_missing (method_name) setter "det er ingen metode som kalles '# method_name'" ende ende o = OurClass.new o.some_method # => det er ingen metode kalt 'some_method'
Vi kan lage en metode som kalles method_missing
i vår klasse. Hvis objektet vi ringer metoden på, ikke har metoden (og ikke arver metoden fra en annen klasse eller modul), vil Ruby gi oss en sjanse til å gjøre noe nyttig: hvis klassen har en method_missing
metode, vil vi gi informasjon om metoden cal til method_missing
og la det sortere rotet ut.
Vel, det er flott; Vi får ikke lenger en feilmelding.
Men stopp og tenk på dette i et sekund. Først av alt: nei, vi får ikke en feilmelding lenger, men vi får ikke noe nyttig. Det er vanskelig å si hva som er nyttig i dette tilfellet, fordi utmetodenavnet ikke antyder noe. For det andre er dette ganske kraftig, fordi det lar deg i utgangspunktet overføre hvilken som helst metode til et objekt og få et intelligent resultat.
La oss gjøre noe som gir mer mening; begynn med dette:
klasse TutsSite attr_accessor: navn,: opplæringsprogram def initialize name = "", tuts = [] @name = navn @tutorials = tuts ende def get_tuts_about_javascript @ tutorials.select do | tut | tut [: tags] .include? "javascript" slutten slutt def get_tuts_by_jeffrey_way @ tutorials.select do | tut | tut [: forfatter] == "Jeffrey Way" slutten slutten
Her ser du en liten klasse for en opplæringswebside. Når vi oppretter et nytt nettstedobjekt, sender vi det et navn og en rekke opplæringsprogrammer. Vi forventer at opplæringen skal være hash i følgende form:
title: "Some title", forfatter: "the author", tags: ["array", "av", "tags"] # Ruby 1.9 # OR : title => "Some title" forfatteren ",: tags => [" array "," of "," tags "] # Ruby 1.8
Vi forventer symboler som nøklene; Legg merke til at hvis du ikke bruker Ruby 1.9, må du bruke bunnformatet for hashes (begge arbeider i 1,9)
Deretter har vi to hjelperfunksjoner som tillater oss å få bare opplæringen som har en JavaScript-tag, eller bare veiledningene av Jeffrey Way. Disse er nyttige for filtrering av veiledningene? men de gir oss ikke for mange alternativer. Selvfølgelig kunne vi lage metoder som heter get_tuts_with_tag
og get_tuts_by_author
som tar parametere med taggen eller forfatternavnet. Vi skal imidlertid gå en annen vei: method_missing
.
Som vi så, method_missing
får forsøksmetoden som parameter. Det jeg ikke nevnte er at det er et symbol. Også, parametrene som blir sendt til metoden og blokken (hvis en ble gitt) er også tilgjengelig. Merk at parametrene er bestått som individuelle parametere til method_missing
, så den vanlige konvensjonen er å bruke splatoperatøren til å samle dem alle sammen i en matrise:
def method_missing navn, * args, og block end
Så, siden vi kan få navnet på metoden som ble forsøkt, kan vi analysere det navnet og gjøre noe intelligent med det. For eksempel, hvis brukeren ringer noe slikt:
netts.get_tuts_by_jeffrey_way nettuts.get_tuts_about_html netuts.get_tuts_about_canvas_by_rob_hawkes nettuts.get_tuts_by_jeremy_mcpeak_about_asp_net
Så, la oss komme til det; skrap de tidligere metodene og erstatt dette på denne måten:
def method_missing navn, * args, og block tuts = @ opplæring.dup navn = navn.to_s.downcase hvis (md = / ^ get_tuts_ (by_ | om _) (\ w *?) ((av _ | _ om _) (\ w *) )? $ /. kampnavn) hvis md [1] == 'by_' tuts.select! | tut | tut [: forfatter] .downcase == md [2] .gsub ("_", "") tuts.select! | tut | tut [: tags] .include? md [5] .gsub ("_", "") hvis md [4] == '_about_' elsif md [1] == 'about_' tuts.select! | tut | tut [: tags] .include? md [2] .gsub ("_", "") tuts.select! | tut | tut [: author] .downcase == md [5] .gsub ("_", "" hvis md [4] == '_by_' end else tuts = "Dette objektet støtter ikke objektet '# navn '' ende tuts avslutter
Ikke vær bekymret, vi går gjennom alt dette nå. Vi begynner med å duplisere @tutorials
matrise; hver Ruby-gjenstand har a DUP
metode som kopierer den hvis vi ikke gjorde dette - og sa bare tuts = @veiledning
-vi ville jobbe med det opprinnelige matrisen, som vi ikke vil gjøre; Vi ønsker å bevare den gruppen som den er. Deretter vil vi filtrere ut opplæringspakken vi ikke vil ha.
Vi må også få navnet på metoden; siden det er gått til method_missing
Som et symbol, konverterer vi det til en streng med to_s
og sørg for at den er i små bokstaver med downcase
.
Nå må vi sjekke for å se at metoden samsvarer med formatet vi vil ha; Tross alt er det mulig at noen kan passere noe annet til metoden. Så la oss analysere det metodiske navnet. Hvis det matcher, vil vi trene ut magi; Ellers returnerer vi en standard feilmelding:
hvis (md = /^get_tuts_(by_|about_)(\w*?)((_by_|_about_)(\w*))?$/.matchnavn) #coming else tuts = "Dette objektet støtter ikke metode '# name' "slutten
Det ser ut som en ganske skremmende, men du bør forstå det: I utgangspunktet leter vi etter? Get_tuts_? etterfulgt av? by_? eller? om_ ?; da har vi en forfatters navn eller en tag, etterfulgt av? _by_? eller? _about_? og en forfatter eller tagger. Hvis det passer, lagrer vi Match
objekt i md
; ellers får vi det nil
tilbake; i så fall vil vi stille inn Tuts
til feilmeldingen. Vi gjør dette slik at vi enten kan komme tilbake Tuts
.
Så det vanlige uttrykket samsvarer, vi får en Match
gjenstand. Hvis metodenavnet brukt var get_tuts_by_andrew_burgess_about_html
, Dette er indeksene du har:
0. get_tuts_by_andrew_burgess_about_html 1. by_ 2. andrew_burgess 3. _about_html 4. _about_ 5. html
Jeg merker at hvis en av de valgfrie gruppene ikke er fylt, har indeksen en verdi på nil
.
Så, dataene vi ønsker er i indeksene 2 og 5; husk at vi bare kunne få en tagg, bare en forfatter eller begge deler (i begge rekkefølge). Så, neste må vi filtrere ut tutene som ikke samsvarer med kriteriene våre. Vi kan gjøre dette med matrisen å velge
metode. Den overfører hvert element til en blokk, en etter en. Hvis blokken returnerer ekte
, varen holdes hvis den kommer tilbake falsk
, elementet er kastet ut av matrisen. La oss starte med dette:
hvis md [1] == 'by_' tuts.select! | tut | tut [: forfatter] .downcase == md [2] .gsub ("_", "") tuts.select! | tut | tut [: tags] .include? md [5] .gsub ("_", "") hvis md [4] == '_about_'
Hvis md [1]
er? by_?, vi vet at forfatteren kom først. Derfor, inne i blokken av den første å velge
ring, vi får tut
hashs forfatternavndowncase
det) og sammenligne det med md [2]
. Jeg bruker den globale substitusjonsmetoden-gsub
-å erstatte alle underskrifter med en enkelt plass. Hvis stringene sammenligner sant, blir varen holdt; ellers er det ikke. I den andre å velge
ring, vi sjekker for taggen (lagret i md [5]
) i tut [: tags]
array. Arrayet inkludere?
metoden kommer tilbake ekte
hvis varen er i matrisen. Legg merke til modifikatoren på slutten av linjen: vi gjør bare dette hvis den fjerde indeksen er strengen? _About_?.
Legg merke til at vi faktisk bruker arrayet å velge
metode: vi bruker å velge!
(med et bang / utropstegn). Dette returnerer ikke et nytt utvalg med bare de valgte elementene; det fungerer med selve Tuts
array i minnet.
Nå som du forstår det, bør du ikke ha et problem med de neste linjene:
elsif md [1] == 'about_' tuts.select! | tut | tut [: tags] .include? md [2] .gsub ("_", "") tuts.select! | tut | tut [: forfatter] .downcase == md [5] .gsub ("_", "") hvis md [4] == '_by_' end
Disse linjene gjør det samme som ovenfor, men de er for metodenavn i omvendt situasjon: tag først, valgfri forfatter sekund.
På slutten av metoden vender vi tilbake Tuts
; Dette er enten den filtrerte gruppen eller feilmeldingen.
Nå, la oss teste dette:
Tuts = [title: "Hvordan overføre et bilde fra B & W til Color with Canvas", forfatter: "Jeffrey Way", tags: ["javascript", "lerret"], title: "Node.js Step by Step : Blogging Application ", forfatter:" Christopher Roach ", tags: [" javascript "," node "], title:" De 30 CSS Selectors du må huske ", forfatter:" Jeffrey Way ", tags: [" css "," selectors "], title:" Responsive Web Design: En visuell guide ", forfatter:" Andrew Gormley ", tags: [" html "," responsive design "], title:" Webutvikling fra grunnen : Grunnleggende layout ", forfatter:" Jeffrey Way ", tags: [" html "], title:" Beskytt en CodeIgniter Application Against CSRF ", forfatter:" Ian Murray ", tags: [" php "," codeigniter " ], title: "Administrer Cron Jobs med PHP", forfatter: "Nikola Malich", tagger: ["php", "cron jobs"]] nettuts = TutsSite.new "Nettuts +", tuts p nettuts.get_tuts_by_ian_murray # [: title => "Beskytt en CodeIgniter-applikasjon mot CSRF",: forfatter => "Ian Murray",: tags => ["php", "codeigniter"]] p nettuts.get_tuts_about_html # tle => "Responsive Web Design: En visuell guide",: forfatter => "Andrew Gormley",: tags => ["html", "responsiv design"], : title => "Webutvikling fra grunnen: Grunnleggende Layout ",: author =>" Jeffrey Way ",: tags => [" html "]] p nettuts.get_tuts_by_jeffrey_way_about_canvas # [: title =>" Slik overfører du et bilde fra B & W til Color with Canvas " => "Jeffrey Way",: tags => ["javascript", "lerret"]] p nettuts.get_tuts_about_php_by_nikola_malich # [: title => "Administrer Cron Jobs med PHP",: forfatter => "Nikola Malich" : tags => ["php", "cron jobber"]] p nettuts.submit_an_article # Dette objektet støtter ikke metoden 'submit_an_article' "
Jeg er p
-rinting ut resultatene fra disse metodene, slik at du kan kjøre dette i en ruby-fil på kommandolinjen.
Jeg bør nevne at, mens dette er ganske kult, er dette ikke nødvendigvis riktig bruk av method_missing
. Det er først og fremst som en sikkerhet for å redde deg fra feil. Konvensjonen er imidlertid ikke dårlig: den er mye brukt i Active
klasser som er en stor del av Ruby on Rails.
Du visste sikkert ikke at det var en lignende funksjon i JavaScript: det er det __noSuchMethod__
metode på objekter. Så vidt jeg vet, støttes den bare i FireFox, men det er en interessant ide. Jeg har skrevet om eksemplet ovenfor i JavaScript, og du kan sjekke det ut på dette JSBin.
Det er en wrap for i dag! Jeg har noen interessante Ruby ting opp i ermet min, kommer snart. Hold øye med Nettuts +, og hvis du vil ha noe spesifikt, gi meg beskjed i kommentarene!