Ruby para novatos métodos que faltan

Ruby es uno de los idiomas más populares utilizados en la web. Estamos ejecutando una sesión aquí en Nettuts + que le presentará a Ruby, así como a los excelentes marcos y herramientas que acompañan el desarrollo de Ruby. En este episodio, vamos a ver la manera demasiado genial de ser real que los objetos de Ruby tratan con métodos que no existen..


Video Tutorial?


Un problema (y una solución)

Digamos que estás trabajando con un objeto Ruby. Y digamos también que no estás completamente familiarizado con este objeto. Y digamos también que se llama un método que no existe en el objeto.

o = Object.new o.some_method # NoMethodError: método no definido 'some_method' para #

Esto es menos que deseable, por lo que Ruby tiene una manera increíble de permitirnos rescatarnos de esto. Mira esto:

la clase OurClass def method_missing (method_name) pone "no hay ningún método llamado '# method_name'" end end o = OurClass.new o.some_method # => no hay ningún método llamado 'some_method'

Podemos crear un método llamado método_missing en nuestra clase. Si el objeto en el que estamos llamando al método no tiene el método (y no hereda el método de otra clase o módulo), Ruby nos dará una oportunidad más para hacer algo útil: si la clase tiene un método_missing método, le daremos la información sobre el método cal para método_missing y deja que solucione el desorden.

Bueno, eso es genial; Ya no estamos recibiendo un mensaje de error.


Un mejor uso

Pero detente y piensa en esto por un segundo. En primer lugar: no, ya no recibimos un mensaje de error, pero no estamos obteniendo algo útil. Es difícil decir qué utilidad sería en este caso, porque el nombre del método no sugiere nada. En segundo lugar, esto es bastante poderoso, ya que básicamente le permite pasar cualquier método a un objeto y obtener un resultado inteligente..

Hagamos algo que tenga más sentido; empieza con esto:

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

Aquí ves una pequeña clase para un sitio web tutorial. Al crear un nuevo objeto de sitio web, le pasamos un nombre y una serie de tutoriales. Esperamos que los tutoriales sean hashes en la siguiente forma:

title: "Some title", author: "the author", tags: ["array", "of", "tags"] # Ruby 1.9 # OR : title => "Some title",: author => " el autor ",: tags => [" array "," of "," tags "] # Ruby 1.8

Esperamos símbolos como las claves; Observe que si no está utilizando Ruby 1.9, tendrá que usar el formato inferior para sus hashes (ambos trabajos en 1.9)

Luego, tenemos dos funciones de ayuda que nos permiten obtener solo el tutorial que tiene una etiqueta de JavaScript, o solo los tutoriales de Jeffrey Way. ¿Son útiles para filtrar los tutoriales? Pero no nos dan demasiadas opciones. Por supuesto, podríamos hacer métodos nombrados. get_tuts_with_tag y get_tuts_by_author que toman parámetros con la etiqueta o el nombre del autor. Sin embargo, vamos a ir por una ruta diferente: método_missing.

Como vimos, método_missing obtiene el nombre del método intentado como un parámetro. Lo que no mencioné es que es un símbolo. Además, los parámetros que se pasan al método y al bloque (si se dio uno) también están disponibles. Tenga en cuenta que los parámetros se pasan como parámetros individuales a método_missing, por lo que la convención habitual es utilizar el operador splat para recopilarlos todos en una matriz:

def method_missing name, * args, & block end

Entonces, ya que podemos obtener el nombre del método que se intentó, podemos analizar ese nombre y hacer algo inteligente con él. Por ejemplo, si el usuario llama algo como esto:

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

Vamos a por ello; Deseche esos métodos anteriores y reemplácelo este:

def method_missing name, * args, & block tuts = @ tutorials.dup name = name.to_s.downcase if (md = / ^ get_tuts_ (by_ | about _) (\ w *?) ((_ by_ | _about _) (\ w *) )? $ /. coincide con el nombre) si md [1] == 'by_' tuts.select! | tut | tut [: author] .downcase == md [2] .gsub ("_", "") tuts.select! | tut | tut [: tags] .include? md [5] .gsub ("_", "") if 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 ("_", "") if md [4] == '_by_' end else tuts = "Este objeto no admite el objeto '#  nombre '"end tuts end

No te preocupes, caminaremos por todo esto ahora. Comenzamos por duplicar el @tutoriales formación; cada objeto Ruby tiene una dup método que lo copia; Si no hiciéramos esto - y solo dijimos tuts = @tutorial-estaríamos trabajando con la matriz original, lo que no queremos hacer; Queremos preservar esa matriz como es. Luego, filtraremos los hashes tutoriales que no queremos.

También tenemos que conseguir el nombre del método; ya que ha pasado a método_missing Como símbolo, lo convertimos en una cadena con to_s y luego asegúrese de que está en minúsculas con downcase.

Ahora, tenemos que verificar que el método coincida con el formato que queremos; después de todo, es posible que alguien pueda pasar algo más al método. Entonces, analicemos ese nombre de método. Si coincide, trabajaremos magia; De lo contrario, devolvemos un mensaje de error predeterminado:

 if (md = /^get_tuts_(by_|about_)(\w*?)((_by_|_about_)(\w*))?$/.match name) #coming else tuts = "Este objeto no es compatible con método '# nombre' "fin

Eso parece bastante desalentador, pero deberías entenderlo: básicamente, estamos buscando? Get_tuts_? seguido de? by_? o? about_ ?; entonces, tenemos un nombre de autor o una etiqueta, seguido de? _by_? o? _about_? y un autor o etiqueta. Si eso coincide, almacenamos el MatchData objeto en Maryland; de lo contrario, obtendremos nulo atrás; en ese caso, vamos a establecer tuts al mensaje de error. Hacemos esto para que de cualquier manera, podamos volver. tuts.

Así que la expresión regular coincide, obtendremos una MatchData objeto. Si el nombre del método utilizado era get_tuts_by_andrew_burgess_about_html, Estos son los índices que tienes:

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

Observaré que si uno de los grupos opcionales no se llena, su índice tiene un valor de nulo.

Entonces, los datos que queremos están en los índices 2 y 5; recuerde que solo podemos obtener una etiqueta, solo un autor o ambos (en cualquier orden). Entonces, a continuación tenemos que filtrar los tuts que no coinciden con nuestros criterios. Podemos hacer esto con la matriz. seleccionar método. Pasa cada elemento a un bloque, uno por uno. Si el bloque vuelve cierto, el artículo se mantiene; si vuelve falso, el elemento se tira fuera de la matriz. Vamos a empezar con esto:

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

Si md [1] es? by_ ?, sabemos que el autor vino primero. Por lo tanto, dentro del bloque de la primera seleccionar llama, tenemos el gesto de desaprobación nombre del autor del hash (downcase ) y compararlo con md [2]. Estoy usando el método de sustitución global.-gsub-Para reemplazar todos los guiones bajos con un solo espacio. Si las cadenas se comparan a verdadero, el elemento se mantiene; de lo contrario no lo es. En el segundo seleccionar llamada, comprobamos la etiqueta (almacenada en md [5]) en el tut [: tags] formación. La matriz incluir? el método regresará cierto si el elemento está en la matriz. Observe el modificador al final de esa línea: solo hacemos esto si el cuarto índice es la cadena? _About_?.

Observe que en realidad estamos usando la matriz seleccionar método: estamos usando seleccionar! (con una explosión / signo de exclamación). Esto no devuelve una nueva matriz con solo los elementos seleccionados; funciona con lo real tuts matriz en la memoria.

Ahora que lo entiende, no debería tener problemas con las siguientes líneas:

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

Estas líneas hacen lo mismo que arriba, pero son para nombres de métodos en la situación inversa: etiqueta primero, segundo autor opcional.

Al final del método, volvemos. tuts; esto es la matriz filtrada o el mensaje de error.

Ahora, vamos a probar esto:

tuts = [title: "Cómo pasar una imagen de B&W a Color with Canvas", autor: "Jeffrey Way", etiquetas: ["javascript", "canvas"], title: "Node.js Step by Step : Aplicación de blogs ", autor:" Christopher Roach ", etiquetas: [" javascript "," nodo "], título:" Los 30 selectores de CSS que debes memorizar ", autor:" Jeffrey Way ", etiquetas: [" css "," selectores "], título:" Diseño web responsivo: Una guía visual ", autor:" Andrew Gormley ", etiquetas: [" html "," diseño responsivo "], título:" Desarrollo web desde cero : Diseño básico ", autor:" Jeffrey Way ", etiquetas: [" html "], título:" Proteger una aplicación CodeIgniter contra CSRF ", autor:" Ian Murray ", etiquetas: [" php "," codeigniter " ], título: "Manage Cron Jobs with PHP", autor: "Nikola Malich", etiquetas: ["php", "cron jobs"]] nettuts = TutsSite.new "Nettuts +", tuts p nettuts.get_tuts_by_ian_murray # [: title => "Proteger una aplicación CodeIgniter contra CSRF",: author => "Ian Murray",: tags => ["php", "codeigniter"]] p nettuts.get_tuts_about_about_about_html # [: ti tle => "Diseño web sensible: una guía visual",: author => "Andrew Gormley",: tags => ["html", "diseño responsivo"], : title => "Desarrollo web desde cero: Básico Layout ",: author =>" Jeffrey Way ",: tags => [" html "]] p nettuts.get_tuts_by_jeffrey_way_about_canvas # [: title =>" Cómo hacer una transición de una imagen de B&W al color con lienzo ",: autor => "Jeffrey Way",: tags => ["javascript", "canvas"]] p nettuts.get_tuts_about_php_by_nikola_malich # [: title => "Manage Cron Jobs with PHP",: author => "Nikola Malich", : tags => ["php", "cron jobs"]] p nettuts.submit_an_article # Este objeto no admite el método 'submit_an_article' "

soy pag-Imprimiendo los resultados de estos métodos, para que pueda ejecutar esto en un archivo ruby ​​en la línea de comandos.


Una advertencia

Debería mencionar que, aunque esto es bastante bueno, este no es necesariamente el uso correcto de método_missing. Es principalmente como una seguridad para rescatarte de los errores. Sin embargo, la convención no es mala: es ampliamente utilizada en el ActiveRecord Clases que son una gran parte de Ruby on Rails.


Un bono

Probablemente no sabías que había una característica similar en JavaScript: es la __noSuchMethod__ Método sobre los objetos. Que yo sepa, solo se admite en Firefox, pero es una idea interesante. He reescrito el ejemplo anterior en JavaScript, y puedes comprobarlo en este JSBin.


Conclusión

¡Eso es una envoltura para hoy! Tengo algunas cosas interesantes de Ruby en la manga, pronto por ti. Fíjate en Nettuts +, y si quieres algo específico, avísame en los comentarios.!