Ruby pentru începători Metode lipsă

Ruby este una dintre cele mai populare limbi folosite pe web. Desfășurăm o sesiune aici pe Nettuts +, care vă va prezenta Ruby, precum și marile cadre și instrumente care merg împreună cu dezvoltarea Ruby. În acest episod, ne vom uita la modul prea cool-la-adevărat că obiectele Ruby se ocupă de metode care nu există.


Tutorial video?


O problemă (și o soluție)

Să spunem că lucrați cu un obiect Ruby. Și să spunem, de asemenea, că nu sunteți complet familiarizat cu acest obiect. Și să spunem, de asemenea, că numiți o metodă care nu există pe obiect.

o = Object.new o.some_method # NoMethodError: Metoda undefined 'some_method' pentru #

Acest lucru este mai puțin decât de dorit, deci Ruby are un mod minunat de a ne permite să ne salveze de la asta. Verificați acest lucru:

clasa OurClass def metoda_missing (method_name) pune "nu există nici o metodă numită" # method_name "" end end o = OurClass.new o.some_method # => nu există nici o metodă numită "some_method"

Putem crea o metodă numită method_missing in clasa noastra. Dacă obiectul pe care îl numim metoda nu are metoda (și nu moștenește metoda dintr-o altă clasă sau modul), Ruby ne va oferi încă o șansă de a face ceva util: dacă clasa are method_missing , vom transmite informații despre metoda met method_missing și lăsați-o să sorteze dezordinea.

Ei bine, este grozav; nu mai primim un mesaj de eroare.


O utilizare mai bună

Dar opriți-vă și gândiți-vă la asta pentru o secundă. Mai întâi de toate: nu, nu mai primim un mesaj de eroare, dar nu primim ceva util. Este greu de spus ce util ar fi în acest caz, deoarece numele metodei nu sugerează nimic. În al doilea rând, acest lucru este destul de puternic, deoarece vă permite să treceți practic orice metodă la un obiect și să obțineți un rezultat inteligent.

Să facem ceva care are mai mult sens; începeți cu aceasta:

clasa TutsSite attr_accessor: nume,: tutoriale def initialize name = "", tuts = [] @name = nume @tutorials = tuts end def get_tuts_about_javascript @ tutorials.select do | tut | Tut [: tag-uri] .include? "javascript" sfârșitul final def get_tuts_by_jeffrey_way @ tutorials.select nu | tut | tut [: author] == "Calea lui Jeffrey" sfârșitul final

Aici vedeți o mică clasă pentru un site de tutorial. Atunci când creați un obiect de site nou, îl transmitem un nume și o serie de tutoriale. Ne așteptăm ca tutorialele să fie hashes în următoarea formă:

title: "Unele titluri", autor: "autorul", tag-uri: ["array", "of" autorul ",: tags => [" array "," of "," tags "] # Ruby 1.8

Ne așteptăm ca simbolurile să fie cheile; observați că dacă nu utilizați Ruby 1.9, va trebui să utilizați formatul de fund pentru hash-urile dvs. (ambele funcționează în 1.9)

Apoi, avem două funcții de ajutor care ne permit să obținem doar tutorialul care are o etichetă JavaScript sau doar tutorialele lui Jeffrey Way. Acestea sunt utile pentru filtrarea tutorialelor? dar ele nu ne oferă prea multe opțiuni. Desigur, am putea face metode numite get_tuts_with_tag și get_tuts_by_author care iau parametri cu eticheta sau numele autorului. Cu toate acestea, vom merge pe un alt traseu: method_missing.

După cum am văzut, method_missing obține numele încercat al metodei ca parametru. Ceea ce nu am menționat este că este un simbol. De asemenea, sunt disponibili parametrii care au trecut la metoda și la bloc (dacă a fost dat). Rețineți că parametrii sunt transmiși ca parametri individuali la method_missing, astfel încât convenția obișnuită este utilizarea operatorului splat pentru a le colecta pe toate într-o matrice:

def method_missing name, * args, & block end

Deci, deoarece putem obține numele metodei care a fost încercată, putem parsa acest nume și facem ceva inteligent cu el. De exemplu, dacă utilizatorul a sunat astfel:

nettuts.get_tuts_by_jeffrey_way nettuts.get_tuts_about_html nettuts.get_tuts_about_canvas_by_rob_hawkes nettuts.get_tuts_by_jeremy_mcpeak_about_asp_net

Deci, hai să ajungem la asta; eliminați aceste metode anterioare și înlocuiți-le cu aceasta:

Definiți metoda de refuzare a numelui, * args, & block tuts = @ tutorials.dup name = name.to_s.downcase dacă (md = / ^ get_tuts_ (by_ | about _) (\ w *?) )? $ / numele meciului) dacă md [1] == 'by_' tuts.select! | tut | tut [: autor] .downcase == md [2] .gsub ("_", "") tuts.select! | tut | Tut [: tag-uri] .include? md [5] .gsub ("_", "") dacă md [4] == '_about_' elsif md [1] == 'about_' tuts.select! | tut | Tut [: tag-uri] .include? md [2] .gsub ("_", "") tuts.select! | tut | td [: author] .downcase == md [5] .gsub ("_", "") dacă md [4] == '_by_' sfârșește altfel tuts = name '' end end tuts

Nu vă îngrijorați, vom trece prin toate astea acum. Începem prin duplicarea @tutorials matrice; fiecare obiect Ruby are o DUP metodă care o copiază; dacă nu am făcut asta - și tocmai am spus tuts = @ tutorial-am fi lucrat cu matricea originală, pe care nu o dorim să o facem; vrem să păstrăm acea matrice așa cum este ea. Apoi, vom filtra outhash-urile tutorialului pe care nu le vrem.

De asemenea, trebuie să luăm numele metodei; de când a trecut method_missing ca simbol, îl convertim într-un șir cu to_s și apoi asigurați-vă că este în litere mici cu downcase.

Acum, trebuie să verificăm dacă metoda se potrivește cu formatul dorit; la urma urmei, este posibil ca cineva să poată trece ceva altceva în metodă. Deci, să analizăm numele metodei. Dacă se potrivește, vom rezolva magia; în caz contrar, vom returna un mesaj de eroare implicit:

 dacă numele md = /^get_tuts_(by_|about_)(\w*?)((_by_|_about_)(\w*))?$/.match) #coming else tuts = "Acest obiect nu acceptă metoda "# name '" sfârșit

Acest lucru pare a fi destul de descurajant, dar trebuie să îl înțelegeți: de fapt căutăm? Get_tuts_? urmat de? by_? sau? atunci avem un nume de autor sau o etichetă, urmată de "_by_"? sau? _about_? și un autor sau o etichetă. Dacă se potrivește, stocăm MatchData obiect în md; altfel, vom obține zero înapoi; în acest caz, vom stabili tuts la mesajul de eroare. Facem acest lucru pentru ca oricum, putem reveni tuts.

Deci, expresia regulată se potrivește, vom obține a MatchData obiect. Dacă numele metodei a fost folosit get_tuts_by_andrew_burgess_about_html, acestea sunt indicii pe care îi aveți:

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

Vom observa că dacă unul dintre grupurile opționale nu este completat, indicele său are o valoare de zero.

Deci, datele dorite sunt la indicii 2 și 5; amintiți-vă că am putea obține doar o etichetă, doar un autor sau ambele (în orice ordine). Deci, în continuare trebuie să filtrați tutu-urile care nu corespund criteriilor noastre. Putem face acest lucru cu matricea Selectați metodă. Acesta trece fiecare articol într-un bloc, unul câte unul. Dacă blocul se întoarce Adevărat, elementul este păstrat; dacă se întoarce fals, elementul este aruncat din matrice. Să începem cu acest lucru:

dacă md [1] == 'by_' tuts.select! | tut | tut [: autor] .downcase == md [2] .gsub ("_", "") tuts.select! | tut | Tut [: tag-uri] .include? md [5] .gsub ("_", "") dacă md [4] == '_about_'

Dacă md [1] este? by_ ?, știm că autorul a venit primul. Prin urmare, în interiorul blocului primului Selectați sună, primim tt numele autorului hash (downcase ) și comparați-l cu acesta md [2]. Folosesc metoda de substituție globală-gsub-pentru a înlocui toate sublinierile cu un singur spațiu. Dacă șirurile sunt comparabile, elementul este păstrat; altfel nu este. In secunda Selectați sunați, verificăm eticheta (stocată în md [5]) în tut [: tags] matrice. Matricea include? metoda va reveni Adevărat dacă elementul se află în matrice. Observați modificatorul de la sfârșitul acelei linii: facem asta doar dacă al patrulea index este șirul? _About_?.

Observați că folosim matricea Selectați : folosim Selectați! (cu un bang / semn de exclamare). Aceasta nu întoarce o matrice nouă cu numai elementele selectate; funcționează cu realitatea tuts array în memorie.

Acum că înțelegeți asta, nu ar trebui să aveți o problemă cu următoarele rânduri:

elsif md [1] == 'despre_' tuts.select! | tut | Tut [: tag-uri] .include? md [2] .gsub ("_", "") tuts.select! | tut | tut [: author] .downcase == md [5] .gsub ("_", "") în cazul în care md [4] == '_by_'

Aceste linii sunt aceleași ca în cele de mai sus, dar ele sunt pentru numele metodelor în situația inversă: etichetă în primul rând, autor opțional al doilea.

La sfârșitul metodei, ne întoarcem tuts; aceasta este fie matricea filtrată, fie mesajul de eroare.

Acum, să testați acest lucru:

tuts = [(titlu: "Cum se poate transforma o imagine de la B & W la culoare cu canvas", autor: "Jeffrey Way", tags: ["javascript", "canvas"], title: "Node.js : "Aplicația de blogare", autor: "Christopher Roach", tags: ["javascript", "nod"], title: " "," selectori "), title:" Responsabil Web Design: Un ghid vizual ", autor:" Andrew Gormley ", tag-uri:" html " : Basic Layout ", autor:" Jeffrey Way ", tag-uri: [" html "], title:" Protejați o aplicație CodeIgniter împotriva CSRF " ], title: "Gestionați Cron Jobs cu PHP", autor: "Nikola Malich", etichete: ["php", "cron jobs"]] nettuts = TutsSite.new nettuts + nettuts.get_tuts_by_ian_murray # [: title => "Protejați o aplicație CodeIgniter împotriva CSRF",: author => "Ian Murray",: tags => ["php", "codeigniter"] p nettuts.get_tuts_about_html # tle => "Responsive Web Design: Un ghid vizual",: author => "Andrew Gormley",: tags => ["html", "responsive design"] Layout ",: author =>" Jeffrey Way ",: tags => [" html "]]] p nettuts.get_tuts_by_jeffrey_way_about_canvas # [: title => => "Jeffrey Way",: tags => ["javascript", "canvas"]]] p nettuts.get_tuts_about_php_by_nikola_malich # [: : tags => ["php", "cron jobs"]]] p nettuts.submit_an_article # Acest obiect nu suporta metoda 'submit_an_article' "

Sunt p-rinting rezultatele de la aceste metode, astfel încât să puteți rula acest lucru într-un fișier rubin de pe linia de comandă.


Un avertisment

Ar trebui să menționez că, în timp ce acest lucru este destul de cool, acest lucru nu este neapărat utilizarea corectă a method_missing. Este acolo în primul rând ca o siguranță pentru a vă salva de erori. Cu toate acestea, convenția nu este rea: este folosită pe scară largă în ActiveRecord clase care sunt o mare parte din Ruby on Rails.


Un bonus

Probabil că nu știați că a existat o caracteristică similară în JavaScript: este __noSuchMethod__ metoda pe obiecte. Din câte știu, este suportat doar în FireFox, dar este o idee interesantă. Am re-scris exemplul de mai sus în JavaScript, și îl puteți verifica la acest JSBin.


Concluzie

Asta e un wrap pentru ziua de azi! Am niște lucruri interesante despre Ruby în mânecă, care vin în curând pentru tine. Păstrați-vă ochii pe Nettuts +, și dacă doriți ceva specific, spuneți-mi în comentariile!

Cod