Ruby per principianti metodi mancanti

Ruby è una delle lingue più popolari utilizzate sul web. Stiamo eseguendo una sessione qui su Nettuts + che ti introdurrà a Ruby, così come i grandi framework e strumenti che accompagnano lo sviluppo di Ruby. In questo episodio, guarderemo al modo troppo bello per essere vero che gli oggetti Ruby si occupano di metodi che non esistono.


Video Tutorial?


Un problema (e una soluzione)

Diciamo che stai lavorando con un oggetto Ruby. E diciamo anche che non hai completamente familiarità con questo oggetto. E diciamo anche che chiami un metodo che non esiste sull'oggetto.

o = Object.new o.some_method # NoMethodError: metodo undefined 'some_method' per #

Questo è meno che desiderabile, quindi Ruby ha un fantastico modo di permetterci di salvarci da questo. Controllalo:

class OurClass def method_missing (method_name) mette "non esiste un metodo chiamato '# method_name'" end end o = OurClass.new o.some_method # => non esiste un metodo chiamato 'some_method'

Possiamo creare un metodo chiamato method_missing nella nostra classe. Se l'oggetto su cui stiamo chiamando il metodo non ha il metodo (e non eredita il metodo da un'altra classe o modulo), Ruby ci darà una possibilità in più di fare qualcosa di utile: se la classe ha un method_missing metodo, passeremo le informazioni sul metodo cal a method_missing e lascia che risolva il problema.

Bene, è fantastico; non riceviamo più un messaggio di errore.


Un uso migliore

Ma fermati e pensa a questo per un secondo. Prima di tutto: no, non riceviamo più un messaggio di errore, ma non stiamo ottenendo qualcosa di utile. È difficile dire quale sarebbe utile in questo caso, perché il nome del metodo fuori non suggerisce nulla. In secondo luogo, questo è piuttosto potente, perché consente di passare praticamente qualsiasi metodo a un oggetto e ottenere un risultato intelligente.

Facciamo qualcosa che abbia più senso; inizia con questo:

classe TutsSite attr_accessor: nome,: tutorials def initialize name = "", tuts = [] @name = nome @tutorials = tuts end def get_tuts_about_javascript @ tutorials.select do | tut | tut [: tag] .include? "javascript" end end def get_tuts_by_jeffrey_way @ tutorials.select do | tut | tut [: author] == "Jeffrey Way" end end end

Qui vedi un po 'di classe per un sito web tutorial. Quando creiamo un nuovo oggetto del sito web, gli passiamo un nome e una serie di tutorial. Ci aspettiamo che i tutorial siano degli hash nella seguente forma:

title: "Some title", autore: "the author", tags: ["array", "of", "tags"] # Ruby 1.9 # OR : title => "Some title",: author => " l'autore ",: tags => [" array "," of "," tags "] # Ruby 1.8

Ci aspettiamo simboli come chiavi; si noti che se non si utilizza Ruby 1.9, sarà necessario utilizzare il formato inferiore per gli hash (entrambi funzionano in 1.9)

Quindi, abbiamo due funzioni di supporto che ci permettono di ottenere solo il tutorial che ha un tag JavaScript, o solo le esercitazioni di Jeffrey Way. Questi sono utili per filtrare i tutorial? ma non ci danno troppe opzioni. Certo, potremmo rendere i metodi denominati get_tuts_with_tag e get_tuts_by_author che prendono parametri con il tag o il nome dell'autore. Tuttavia, stiamo andando ad una strada diversa: method_missing.

Come abbiamo visto, method_missing ottiene il nome del metodo tentato come parametro. Quello che non ho menzionato è che è un simbolo. Inoltre, sono disponibili anche i parametri che vengono passati al metodo e il blocco (se ne è stato dato uno). Si noti che i parametri vengono passati come parametri individuali a method_missing, quindi la solita convenzione è usare l'operatore splat per raccoglierli tutti in un array:

def method_missing name, * args, e block end

Quindi, dato che possiamo ottenere il nome del metodo che è stato tentato, possiamo analizzare quel nome e fare qualcosa di intelligente con esso. Ad esempio, se l'utente chiama qualcosa come questo:

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

Quindi, arriviamo ad esso; scarta quei precedenti metodi e sostituiscili questo:

def method_missing name, * args, & block tuts = @ tutorials.dup nome = nome.da_s.downcase if (md = / ^ get_tuts_ (by_ | about _) (\ w *?) ((_ by_ | _about _) (\ w *) )? $ /. match name) if md [1] == 'by_' tuts.select! | tut | tut [: author] .downcase == md [2] .gsub ("_", "") tuts.select! | tut | tut [: tag] .include? md [5] .gsub ("_", "") if md [4] == '_about_' elsif md [1] == 'about_' tuts.select! | tut | tut [: tag] .include? md [2] .gsub ("_", "") tuts.select! | tut | tut [: author] .downcase == md [5] .gsub ("_", "") if md [4] == '_by_' end else tuts = "Questo oggetto non supporta l'oggetto '#  nome '"end tuts end

Non preoccuparti, passeremo tutto questo ora. Iniziamo duplicando il @tutorials matrice; ogni oggetto Ruby ha un dup metodo che lo copia; se non lo facessimo ... e lo dicessimo tuts = @tutorial-lavoreremmo con l'array originale, che non vogliamo fare; vogliamo conservare quell'array così com'è. Quindi filtreremo gli hash del tutorial che non vogliamo.

Dobbiamo anche ottenere il nome del metodo; da quando è passato a method_missing come simbolo, lo convertiamo in una stringa con to_s e quindi assicurarsi che sia in minuscolo con downcase.

Ora, dobbiamo controllare che il metodo corrisponda al formato che vogliamo; dopo tutto, è possibile che qualcuno possa passare qualcos'altro al metodo. Quindi, analizziamo il nome del metodo. Se corrisponde, lavoreremo alla magia; in caso contrario, verrà restituito un messaggio di errore predefinito:

 if (md = /^get_tuts_(by_|about_)(\w*?)((_by_|_about_)(\w*))?$/.match name) #coming else tuts = "Questo oggetto non supporta il metodo "# nome" "fine

Sembra piuttosto scoraggiante, ma dovresti capirlo: in fondo, stiamo cercando? Get_tuts_? seguito da? by_? o? about_ ?; allora, abbiamo il nome di un autore o un tag, seguito da? _by_? o? _about_? e un autore o tag. Se ciò corrisponde, memorizziamo il MatchData oggetto in md; altrimenti, otterremo zero indietro; in tal caso, imposteremo tuts al messaggio di errore. Lo facciamo in modo che, in entrambi i casi, possiamo tornare tuts.

Quindi l'espressione regolare corrisponde, otterremo un MatchData oggetto. Se il nome del metodo utilizzato era get_tuts_by_andrew_burgess_about_html, questi sono gli indici che hai:

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

Noterò che se uno dei gruppi opzionali non è compilato, il suo indice ha un valore di zero.

Quindi, i dati che vogliamo sono agli indici 2 e 5; ricorda che potremmo ottenere solo un tag, solo un autore o entrambi (in entrambi gli ordini). Quindi, dopo dobbiamo filtrare i tut che non corrispondono ai nostri criteri. Possiamo farlo con l'array selezionare metodo. Passa ogni elemento a un blocco, uno per uno. Se il blocco ritorna vero, l'oggetto è tenuto; se ritorna falso, l'oggetto viene buttato fuori dall'array. Iniziamo con questo:

if md [1] == 'by_' tuts.select! | tut | tut [: author] .downcase == md [2] .gsub ("_", "") tuts.select! | tut | tut [: tag] .include? md [5] .gsub ("_", "") if md [4] == '_about_'

Se md [1] è? by_ ?, sappiamo che l'autore è arrivato per primo. Pertanto, all'interno del blocco del primo selezionare chiama, otteniamo il tut nome dell'autore dell'hash (downcase it) e confrontarlo con md [2]. Sto usando il metodo di sostituzione globale-gsub-per sostituire tutti i caratteri di sottolineatura con un singolo spazio. Se le stringhe confrontano true, l'elemento viene mantenuto; altrimenti non lo è. Nel secondo selezionare chiama, controlliamo il tag (memorizzato in md [5]) nel tut [: tag] array. L'array includere? il metodo tornerà vero se l'elemento è nell'array. Notare il modificatore alla fine di quella linea: lo facciamo solo se il quarto indice è la stringa? _About_?.

Si noti che stiamo effettivamente utilizzando la matrice selezionare metodo: stiamo usando selezionare! (con un botto / punto esclamativo). Questo non restituisce un nuovo array con solo gli elementi selezionati; funziona con il reale tuts array in memoria.

Ora che lo capisci, non dovresti avere problemi con le righe seguenti:

elsif md [1] == 'about_' tuts.select! | tut | tut [: tag] .include? md [2] .gsub ("_", "") tuts.select! | tut | tut [: author] .downcase == md [5] .gsub ("_", "") if md [4] == '_by_' fine

Queste linee fanno lo stesso come sopra, ma sono per i nomi dei metodi nella situazione inversa: tag prima, secondo autore facoltativo.

Alla fine del metodo, torniamo tuts; questo è l'array filtrato o il messaggio di errore.

Ora proviamo questo:

tuts = [title: "Come trasformare un'immagine da bianco e nero a colore con tela", autore: "Jeffrey Way", tag: ["javascript", "canvas"], title: "Node.js Step by Step : Blogging Application ", autore:" Christopher Roach ", tags: [" javascript "," node "], title:" I 30 selettori CSS che devi memorizzare ", autore:" Jeffrey Way ", tags: [" css "," selectors "], title:" Responsive Web Design: A Visual Guide ", autore:" Andrew Gormley ", tag: [" html "," responsive design "], title:" Sviluppo Web da zero : Basic Layout ", autore:" Jeffrey Way ", tag: [" html "], title:" Proteggi una applicazione CodeIgniter contro CSRF ", autore:" Ian Murray ", tag: [" php "," codeigniter " ], title: "Gestisci lavori Cron con PHP", autore: "Nikola Malich", tag: ["php", "cron jobs"]] nettuts = TutsSite.new "Netsuts +", tuts p nettuts.get_tuts_by_ian_murray # [: title => "Proteggi un'applicazione CodeIgniter contro CSRF",: author => "Ian Murray",: tag => ["php", "codeigniter"]] p nettuts.get_tuts_about_html # [: ti tle => "Responsive Web Design: A Guide visive",: author => "Andrew Gormley",: tags => ["html", "responsive design"], : title => "Sviluppo Web da zero: base Layout ",: author =>" Jeffrey Way ",: tag => [" html "]] p nettuts.get_tuts_by_jeffrey_way_about_canvas # [: title =>" Come trasformare un'immagine da bianco e nero a colore con tela ",: autore => "Jeffrey Way",: tag => ["javascript", "canvas"]] p nettuts.get_tuts_about_php_by_nikola_malich # [: title => "Gestisci lavori Cron con PHP",: author => "Nikola Malich", : tags => ["php", "cron jobs"]] p nettuts.submit_an_article # Questo oggetto non supporta il metodo 'submit_an_article' "

sono p-rinting i risultati di questi metodi, quindi è possibile eseguire questo in un file rubino sulla riga di comando.


Un avvertimento

Dovrei dire che, mentre questo è abbastanza bello, non è necessariamente l'uso corretto di method_missing. È lì principalmente come una sicurezza per salvarti dagli errori. Tuttavia, la convenzione non è male: è ampiamente usata nel ActiveRecord classi che sono una parte importante di Ruby on Rails.


Un bonus

Probabilmente non sapevi che c'era una funzionalità simile in JavaScript: è la __noSuchMethod__ metodo sugli oggetti. Per quanto ne so, è supportato solo in FireFox, ma è un'idea interessante. Ho riscritto l'esempio sopra in JavaScript e puoi verificarlo su questo JSBin.


Conclusione

È una conclusione per oggi! Ho un po 'di roba interessante con Ruby, arrivo presto per te. Tieni d'occhio Nettuts +, e se vuoi qualcosa di specifico, fammelo sapere nei commenti!