lunes, 21 de enero de 2008

Buscando en relaciones habtm

Supongamos un típico ejemplo de CMS con su modelo de Post y Tags, los cuales se asocian entre sí mediante una relación de tipo HasAndBelongsToMany (o muchos a muchos en otras terminologías).

Este tipo de asociaciones require una tabla íntermedia de unión (join table) que siguiendo las convenciones de CakePHP nombraríamos en este caso posts_tags. Ya sabes.

Post habtm Tag a través de posts_tags

Cuando hacemos un búsqueda en Post, CakePHP automágicamente incluye en los resultados las Tag asociados a cada Post. Pero, ¿podemos buscar los Post que estén asociados a una o varias Tag además de otras condiciones?

La respuesta es que directamente no podemos hacerlo ya que en las relaciones habtm no se hace el "join" automático de las tablas (al contrario de lo que ocurre en las relaciones hasMany o belongsTo).

Crear una query en SQL para utilizar con Model::query() es una solución, pero tiene el inconveniente de que perdemos algunas de las facilidades propias de CakePHP (básicamente los callbacks como afterFind o beforeFind) y otras ventajas.

En CakePHP 1.2 es posible hacer esto de una manera bastante sencilla aunque require "modelizar" la tabla intermedia. Primero, tenemos que crear un modelo para la tabla posts_tags y especificar sus relaciones con las tablas Post y Tag. Siguiendo las convenciones de CakePHP nos queda así:

<?php

class PostsTag extends AppModel {
var $belongsTo = array(
'Tag',
'Post');
}

?>


En el modelo Post tenemos que especificar que la relación con Tag se va a hacer "con" (with) ese modelo intermedio.

var $hasAndBelongsToMany = array (
'Tag' => array(
'className' => 'Tag',
'with' => 'PostsTag'
)
);


Hasta aquí el trabajo preparatorio. Ahora veamos un ejemplo de su uso. En el model Post tengo un método "publicados" que me devuelve los Post cuya fecha de publicación es anterior a la actual, cuyo flag de publicar está a 1 y que están etiquetados con una tag que le pasamos al método.

function publicados ($tag = false) {
$now = date ('Y-m-d');
$conditions = array (
'Tag.tag' => $tag,
'Post.publicar' => '1',
'Post.publicacion' => "<= $now"
);
return $this->PostsTag->find('all', array('conditions' => $conditions));
}


Espero que se entienda. Las condiciones no deberían tener mucha dificultad. Toda la clave es el find que hacemos sobre el modelo intermedio (PostsTag) asociado a Post, no directamente a Post. (Nota: he suprimido la parte del código que gestiona lo que hay que hacer si no se pasa ninguna $tag).

Algunas referencias que me sirvieron de punto de partida:

Modelizing HABTM join tables in CakePHP 1.2: with and auto-with models de Mariano Iglesias

Modeling relationships in CakePHP (faking Rails’ ThroughAssociation) de Felix Geisendörfer

3 comentarios:

faemino dijo...

Sólo una cosa. Gracias por este artículo, era justo lo que necesitaba.

Saludos,

PD: Por cierto, te sobra un punto y coma en el último bloque de código: 'Tag.tag' => $tag; <--- esto debe ser una coma ;-)

Frankie dijo...

De nada.

Ya he corregido el "typo".

Ricardo Pandales dijo...

Yo tengo una tabla llamada producto que tiene una relación HABTM con otras 2 tablas: secciones y categorías, como puedo recuperar los productos que están en una sección determinada y a su vez en una categoria determinada sin hacer un query personalizado.