The difference between __getattribute__ and __getattr__

The other day I was teaching Python meta-programming to a workmate. I think it’s a good way to learn about high order functions since meta-programming makes extensive use of closures, function builders, decorators… I was trying to make a concept probe about a very, very generic REST connector. Here is my first (and wrong) attempt:

class RESTConn(object):

  def __init__(self, entry_point):
    self.entry_point = entry_point

  def method_builder(self, method_name):
    verb, _, collection = method_name.split('_', 2)
    def do_verb(payload=None, **kwargs):
      uri = self.make_uri(collection)
      querystring = self.make_querystring(kwargs)
      print verb.upper(), self.combine(uri, querystring)
      if payload:
        print payload

    return do_verb

  def make_uri(self, collection):
    return '/'.join([self.entry_point, collection])

  def make_querystring(self, kwargs):
    return '&'.join(['='.join(pair) for pair in kwargs.iteritems()])

  def combine(self, uri, querystring):
    if querystring:
      return '&'.join([uri, querystring])

    return uri

  def __getattribute__(self, name):
    if not hasattr(self, name):
      method = self.method_builder(name)
      setattr(self, name, method)

    return super(RESTConn, self).__getattribute__(name)

Try this example by instantiating a new connector and trying to call something like:

c = RESTConn('unoyunodiez.com')
c.get_from_articles()

The program falls into an infinite recursion and do nothing before crashing. Why?

There are three problems here. First and most important is using __getattribute__(), second is using hasattr() and third is accessing self.method_builder().

The object’s method __getattribute__() is used to retrieve an attribute from an instance. It captures every attempt to access an instance attribute by using dot notation or getattr() built-in function. Unless it was overridden, the former expression is translated into object.__getattribute__(self, ‘get_from_article’). The default implementations looks into the instance’s namespace, then looks into the class namespace, then into each base’s namespace and so on. Finally, if not found, the default implementation calls the fallback __getattr__() method of the instance and it raises an AttributeError exception as default implementation.

This is not a problem by itself but if you pay attention enough you’ll notice we are trying to create the new method only if the object does not have the method yet. It is semantically the same as overriding __getattr__() because it is called only when the attribute was not found. So, even if we cannot explain the infinite recursion error yet, we can fix the class by replacing:

  def __getattribute__(self, name):
    if not hasattr(self, name):
      method = self.method_builder(name)
      setattr(self, name, method)

    return super(RESTConn, self).__getattribute__(name)

with:

  def __getattr__(self, name):
    method = self.method_builder(name)
    setattr(self, name, method)
    return getattr(self, name)

So, the difference between __getattribute__() and __getattr__() is that the first one is called unconditionally when an attribute is being retrieved from an instance while the second is called only when the attribute was not found.

But, what about the infinite recursion? Why the first example was failing?

Seguir leyendo «The difference between __getattribute__ and __getattr__»

Evil side effects of changing arguments

I’ve just realized about a creepy fact about JavaScript. Suppose you have this code:

function f(a, b, c) {
  arguments[1] = 99;
  return [a, b, c];
}
f(1, 2, 3);

What do you think the result is?

[1, 2, 3]

You guess but NO. Right answer is:

[1, 99, 3]

Unless you’re in strict mode (and you should always be in strict mode), then result is [1, 2, 3].

In non strict mode, setting the n-th item of arguments alters the value of the n-th formal parameter in the function signature. A really evil and unexpected side effect but specified in http://ecma-international.org/ecma-262/5.1/#sec-10.6 (see Note 1).

So be careful and keep yourself in strict mode where things are less unpredictable!

function f(a, b, c) {
  'use strict';
  arguments[1] = 99;
  return [a, b, c];
}
f(1, 2, 3);

Django 1.5 in a nutshell II

The first part of this tutorial introduced some interesting aspects of Django 1.5. In this second part we are diving deeper into Django features but coding a little bit more, trying always to use the framework properly. Here is the index:

  1. Modifying the model.
  2. Improving posts administration.
  3. Adding commentaries.
  4. Tweaking the administrator to manage posts and commentaries.
  5. Adding a search function.

Seguir leyendo «Django 1.5 in a nutshell II»

Django 1.5 in a nutshell I

In 2011, I wrote a quick tutorial about Django 1.3. I was going to demonstrate how to write a blog in just one hour in a live code session. This time I’m in a mentoring program teaching Python and Django to a colleague so I decided to update that tutorial. Django is now in version 1.5 and you can find what is new in the release notes of the framework. Let’s code!

Seguir leyendo «Django 1.5 in a nutshell I»

μTask

Bea's kanban in English
Bea’s kanban in English

Os presento μTask, una pequeña aplicación para gestionar tareas usando un panel kanban en fase alpha. Últimamente me ronda mucho por la cabeza las aplicaciones con modelos representables en texto plano. Como todo.txt, por ejemplo.

μTask es un pequeño parser de una notación de mi invención para gestionar tareas, pensada para interacturar con los comandos estándar cortar y pegar.

Tenéis un ejemplo de una tarea aquí mismo:

To do:
  Task 153: LMDL fixes

Y una escueta lista de características:

  • Tareas con identificador, descripción y detalles
  • Dos espacios para definir tareas: uno para reflejar el estado del kanban y otro para clasificar y detallar las tareas
  • Clasificaciones por temas y colores
  • Expresa el progreso de una tarea y su fecha límite

Podéis encontrar más información así como un lugar donde dejar vuestros comentarios en la página del proyecto:

http://unoyunodiez.com/proyectos/mtask/

El tutorial en GitHub:

https://github.com/lodr/mtask/#%CE%BCtask

ask.js

Bueno, hace tiempo que no hay actividad por el blog pero vuelvo para anunciar un nuevo proyecto que acabo de hacer entrar en fase alpha a la espera de que los usuarios me den algo de feedback.

Su nombre es ask.js y se trata de un módulo JavaScript que permite hacer búsquedas en un array de objetos utilizando la sintaxis de consultas de MongoDB.

No sé si conoceis un poco MongoDB pero se trata de una base de datos NoSQL orientada a documentos, documentos BSON, de hecho. BSON es una implementación binaria de JSON. Realizar búsquedas en Mongo se hace mediante una suerte de «objetos parciales» del tipo { name: 'Anne', 'address.city': 'Madrid', ...} aunque también admite restricciones complejas por campo como en { manufacturer: { $in: ['Ford', 'Munstang', 'Land Rover'] }, year: { $gte: 1990, $lt: 2000} }.

Una vez añadidos ask.js, puedes hacer ask.mongify() sobre un array para extender el mismo con un nuevo método find() que acepta, hasta el momento, las siguientes características de las búsquedas de MongoDB:

  • Restricciones por valor: { field: value }
  • Expresiones JS en cláusulas $where
  • Subobjetos, elementos parciales: { 'field.subfield': value } y $elemMatch
  • Comprobación de existencia $exists
  • Restricciones por expresión regular: { field: /value/i } y $regex
  • Operadores de comparación <, <=, >= y >
  • Operadores lógicos $or, $and y $nor
  • Módulo $mod
  • Comprobación de tipo $type
  • Operadores de inclusión $in, $nin y $all
  • Metaoperador $not
  • Comprobación de tamaño $size
  • Comprobación de desigualdad $ne

Además, es mi primer proyecto siguiendo TDD utilizando Jasmine como framework de testing. Algún día os hablaré de Jasmine y de cuánto me gusta su API.

Seguir leyendo «ask.js»

JavaScript no alfanumérico

Tengo una discusión con Adri acerca de si JavaScript es un buen lenguaje o no. En una de nuestras conversaciones critica el tipado débil de JS y enlaza el artículo Brainfuck beware: JavaScript is after you! de Patricio Palladino.

Os recomiendo encarecidamente su lectura. Es muy interesante.

Resumiendo, Patricio ha creado una herramienta con la que convertir un script JS a otro, equivalente, formado únicamente por símbolos +[](){}! ¿No me creeis? Probad a introducir esto es una consola JS de un navegador cualquiera (la de Firebug o la de Chrome servirán).

[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((+{}+[])[+!![]]+(![]+[])[!+[]+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[+[]]+([][+[]]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()([][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()(({}+[])[+[]])[+[]]+(!+[]+!![]+[])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[]))+(+!![]+[])+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[+[]]+([][+[]]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()([][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(![]+[])[!+[]+!![]+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+([]+[][(![]+[])[!+[]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]]+(![]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+[]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+([][+[]]+[])[!+[]+!![]+!![]]+(!![]+[])[+[]]+([][+[]]+[])[+[]]+(!![]+[])[+!![]]+([][+[]]+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!+[]+!![]]+({}+[])[+!![]]+({}+[])[!+[]+!![]+!![]+!![]+!![]]+(+{}+[])[+!![]]+(!![]+[])[+[]]+([][+[]]+[])[!+[]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+([][+[]]+[])[+!![]])())[!+[]+!![]+!![]]+([][+[]]+[])[!+[]+!![]+!![]])()(({}+[])[+[]])[+[]]+(!+[]+!![]+[])+(!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+[])))()

Seguir leyendo «JavaScript no alfanumérico»

Respeta la especificación

Todo el mundo sabe que en general, los navegadores son muy tolerantes a errores en el marcado y por eso muchos programadores / diseñadores no prestan una adecuada atención a las estructuras HTML que producen. Os voy a poner un caso que me ha ocurrido hoy para no olvidar que aunque se toleren los fallos, siempre es mejor no cometerlos:

Teníamos esta estructura:

<button>
<span> Test </span>
</button>

En un momento dado, la expandíamos añadiendo cierto marcado al interior del botón:

<button>
<span>
<button><span>Test 1</span></button>
<button><span>Test 2</span></button>
<button><span>Test 3</span></button>
</span>
</button>

Pues bien, en Firefox, los botones internos jamás recibían ningún evento. ¿Por qué? Pues porque la especificación dice del contenido de un elemento button, lo siguiente:

Content model:
Phrasing content, but there must be no interactive content descendant.

Modelo del contenido:
Contenido en forma de sentencia, mas no debe haber ningún descendiente con contenido interactivo.

¿Quiere esto explícitamente decir que los eventos que reciba el primer button no se propagarán hacia los botones contenidos en él? No, o la menos yo no he encontrado esa negación explícita. Se trata sencillamente de un comportamiento indefinido y no se puede confiar en los comportamientos indefinidos. No hay duda de que son útiles para la optimización y en este caso, Firefox no propaga eventos de interacción dentro de un elemento que no debería tener contenido interactivo.

Una reflexión sobre la interacción diseñador – desarrollador

Hace ya un tiempo escribía acerca de la Codemotion, os decía que me había parecido medianamente interesante la charla titulada  Template animation & OO CSS: Be nice with your Web Designer que trataba acerca de la interación desarrollador – diseñador.

Buscando template animation llegué al artículo Your templating engine sucks and everything you have ever written is spaghetti code (yes, you) escrito por Iain Dooley acerca de los inconvenientes de utilizar lenguajes de plantilla. Es él quien acuña el término que puede resumirse en:

Template animation: the practice of using static HTML files as a resource from within your application to be manipulated via the DOM to generate dynamic output.

Animación de plantillas: la práctica de usar archivos HTML estáticos como recursos a ser manipulados via DOM, dentro de tu aplicación, para generar salida dinámica.

En pocas palabras, usar las maquetas HTML del diseñador como bibliotecas de elementos de ejemplo para componer dinámicamente las páginas finales que se mostrarán al usuario.

Leyendo a Dooley estuve pensando seriamente en las interacciones diseñador – desarrollador. A menudo, discuto con mi novia si lo que se pide a los diseñadores web es demasiado: ¿es mucho pedir programación a un diseñador? ¿Es necesario pedirle conocer al menos un lenguaje de plantilla como Django Templates o Liquid? ¿Cuál es la frontera diseño / implementación?

Seguir leyendo «Una reflexión sobre la interacción diseñador – desarrollador»

method_missing en JavaScript

Os decía en la última entrada que podíamos solucionar en JavaScript el problema de metaprogramación de generar los comandos sobre la marcha, en el momento mismo de ser invocados y no antes. Os decía también que creía que me había quedado muy bien y en honor a la verdad debo decir que la idea la saqué del artículo The Command Pattern in JavaScript de Peter Michaux, ¡pero sólo me había leído el primer párrafo!, en especial la parte en la que dice:

In JavaScript, building our own dot operator function is one way to make indirect object function property calls and escape the built-in paradigm.

En JavaScript, construir nuestro propia función operador punto es una manera de hacer llamadas a función de manera indirecta y así escapar del paradigma por defecto.

Así que me puse a implementar este operador «punto» y cuando hube terminado me di cuenta de lo parecido con la solución propuesta por Peter.

Antes de implementar nada merece la pena entender cómo JavaScript busca los atributos de un objeto: para empezar, busca en el objeto y si ahí no está, busca en su cadena de prototipos que es otro objeto. Si ahí tampoco está, busca en la cadena de prototipos de la cadena de prototipos y así, hasta alcanzar cierto objeto cuya cadena de prototipos es null. Entonces devuelve undefined. En Firefox y en node.js, podemos consultar la cadena de prototipos de un objeto mediante el atributo especial __proto__,

La manera que tiene JavaScript de establecer la cadena de prototipos de un objeto es mediante el operador new y la propiedad prototype presente en todos los objetos. Cuando hacemos var o = new F(), aparte de construir el objeto mediante F(), hacemos que la cadena de prototipos del mismo apunte al atributo F.prototype.

Veamos una interacción en node.js* asumiendo que hemos cargado el siguiente listado:

function HTTP() {};
HTTP.prototype.get = function() {
    function makeGet(path) {
        return function(){
            return 'GET ' + this.url + '/' + path;
        };
    }

    Array.prototype.forEach.call(arguments, function(name){
        this[name] = makeGet(name);
    }, this);
};

function MyCompany() {
    this.url = 'mycompany.com';
};
MyCompany.prototype = new HTTP();
MyCompany.prototype.get('projects', 'employees', 'customers');

function YourCompany() {
    this.url = 'yourcompany.com';
}
YourCompany.prototype = new HTTP();
YourCompany.prototype.get('projects', 'employees');
&gt; mine = new MyCompany();
{ url: 'mycompany.com' }
&gt; mine.__proto__
{ projects: [Function],
employees: [Function],
customers: [Function] }
&gt; mine.__proto__.__proto__
{ get: [Function] }
&gt; mine.__proto__.__proto__.__proto__
{}
&gt; mine.__proto__.__proto__.__proto__.__proto__
null

* Si probáis esto mismo en Firefox, podéis llevaros la sorpresa de que el prototipo de mine contenga el método get(). Realmente esto no es así, lo que ocurre es que la consola de Firefox nos muestra todos los atributos de un objeto: los suyos y los disponibles a través de la cadena de prototipos. Podéis consultar si get() pertenece al objeto y no a su cadena con el método hasOwnProperty() que devolverá false para ‘get’ y true para ‘employees’, por ejemplo.

Ahora sí, hemos repasado todos los conceptos que nos hacían falta y parece, por tanto, que no hay forma de interceptar o manipular cómo se recuperan los atributos de un objeto… ¿o sí?

method_missing

Como el título de la entrada indica, la idea consiste en emular el comportamiento de method_missing de Ruby. Cuando Ruby busca un método en un objeto, busca en el propio objeto; sino está ahí, en su clase y si tampoco está, sube por la jerarquía de clases. Si aun así no se encuentra, se llama al método especial method_missing() y si este tampoco está, se lanza la excepción NoMethodError.

Voy a extender el prototipo de Object para que incluya un método similar cuya implementación por defecto consiste precisamente en lanzar una excepción con el mensaje ‘NoMethodError’. Si ya tenías cargados los listados anteriores puedes copiar y pegar los siguientes y añadirás los métodos nuevos sobre la marcha: piénsalo bien, añades los métodos en tiempo de ejecución. ¡Esa es la gracia!

Object.prototype.method_missing = function() {
    throw 'NoMethodError';
};

Como adelantábamos al comienzo de la entrada, construiremos nuestro operador ‘punto’. Una implementación podría ser:

Object.prototype.dot = function(name) {
    var args = Array.prototype.slice.call(arguments, 1);
    var f = this[name];
    if (!f) {
        args.splice(0, 0, name);
        return this.method_missing.apply(this, args);
    } else {
        return f.apply(this, args);
    }
};

Es interesante hacer notar que realmente, esto no sustituye al operador punto dado que no recupera un atributo sino que lo invoca así que sólo debería usarse para realizar llamadas a métodos. El nombre ‘dot’ es apropiado para recordar qué estamos haciendo pero resultaría más correcto que se llamase ‘send’ como ocurre en el artículo de Peter.

Podemos cargar los ejemplos anteriores y probarlos en node.js:

&gt; yours = new YourCompany();
{ url: 'yourcompany.com' }
&gt; yours.employees();
'GET yourcompany.com/employees'
&gt; yours.dot('employees');
'GET yourcompany.com/employees'
&gt; yours.customers();
TypeError: Object #&lt;HTTP&gt; has no method 'customers'
&gt; yours.dot('customers');
NoMethodError
&gt; yours.url
'yourcompany.com'
&gt; yours.dot('url')
TypeError: Object yourcompany.com has no method 'apply'

Ahora es fácil, sencillamente reimplementaremos method_missing() en el prototipo de HTTP para que, si llega a invocarse, cree un nuevo método mediante get() y lo invoque. Vale la pena recalcar que method_missing() se llama de tal manera que this hace referencia al objeto para el que se trató de invocar el método:

HTTP.prototype.method_missing = function(name) {
    this.constructor.prototype.get(name);
    return this.dot.apply(this, arguments);
};

Mediante this.constructor.prototype llegamos al prototipo del constructor que es precisamente el objeto al que apunta la cadena de prototipos del objeto. Contra lo que dicta el sentido común, esto no añade los nuevos métodos al prototipo

Ahora sí, tenemos una forma de crear métodos sobre la marcha sin preocuparnos de si existían antes o no y resuelto como lo haría Ruby. Además, las nuevas funciones extienden el prototipo de Object por lo que cualquier objeto dispondrá de ellas.

No discutiré más y con esto doy por terminadas las entradas sobre metaprogramación a la espera de vuestros comentarios y aportaciones. Espero que os haya sido tan entretenido como a mí.

Si os ha gustado la entrada, compartidla, por favor.

Edit 2012/04/09: He corregido el código de la función dot(), añadido al repositorio de github los ejemplos y he extendido la explicación sobre this.constructor.prototype dado que no era del todo correcta.

Además, creo que voy a hacer un último post con algo de discusión… no sé, ya veré.

Tenéis todo el código de los ejemplos en:
https://github.com/lodr/metaprogramming

Si os gusta la entrada, ¡comentadla y compartidla!