Mostrando entradas con la etiqueta find. Mostrar todas las entradas
Mostrando entradas con la etiqueta find. Mostrar todas las entradas

martes, 11 de enero de 2011

Custom find en Behaviors

Trabajando en un Behavior me surgió la pregunta de si es posible definir Custom Finds en un Behavior para que sean usados por el modelo.

El caso es que encontré este código de Nick Baker, en el cual se encuentra una técnica para lograrlo. Una vez vista es obvia (pero había que verla, claro). Copio el código de Nick para explicarlo. En este caso, define una búsqueda que será find('range'), para lo cual hay que crear un método _findRange() en el Behavior.

La técnica tiene dos pasos:

El primero es mapear el método del behavior con un método para el modelo, lo cual se hace con la siguiente línea en las propiedades del Behavior:


var $mapMethods = array('/^_findRange$/' => '_findRange');

En el método setup hay que añadir la una clave al array de _findMethods del modelo que se pasa:

$Model->_findMethods['range'] = true;

Finalmente, la signatura del método es un poco distinta, pues hay que considerar dos parámetros extra

public function _findSearch(&$model, $method, $state, $query, $results = array())

Es decir, CakePHP pasa automáticamente los parámetros $model y $method, antes de los parámetros habituales del find.

A partir de ahora podremos usar el método model->find('range') en el modelo al que hayamos asociado el Behavior que lo contiene.

viernes, 11 de diciembre de 2009

Querys CakePHP con operaciones bitwise

Otro de esos títulos "sólo para iniciados"...

Hoy me he encontrado con un problemilla curioso. Necesitaba definir una condición para un find en la que hay una operación bitwise en la base de datos. En concreto, un & de una máscara binaria contra un campo del modelo.

Bueno, pues la forma en que he conseguido que funcione es algo así:

...
array('conditions' => array('12 & Rule.precedence'):
...

Que debe dar un WHERE más o menos así:

WHERE 12 & `Rule`.`precedence`

Es decir, primero pongo el valor de la máscara, luego la operación binaria y luego el campo.

Si pongo primero el campo, CakePHP se empeña en hacer no sé qué y no aparece la comparación en el WHERE.

lunes, 25 de agosto de 2008

Paginate y Find personalizado

Soy un "fan" de la sintaxis "nuevo estilo" del método Model::find($type, $options), que permite crear búsquedas personalizadas. Personalmente me resulta muy útil y cómoda.

Lo que me tenía preocupado hasta ahora era la posibilidad de combinar esto con Controller::paginate, como forma de descargar el Controller de la preparación de condiciones de búsqueda cuando no quieres un findAll puro y duro de los datos.

Estudiando el código en el api resulta que sí es posible indicarle a Paginate que utilice una de estas personalizaciones, aunque para lograr que funcione el código que voy a presentar tienes que bajarte una nighty build o bien el código de controller.php más reciente. Me parece que la cosa está un pelín verde todavía, pero a mí me ha funcionado de esta manera:


function admin_index($mode = 'all') {
$this->User->recursive = 0;
switch ($mode) {
case 'pending':
$this->paginate['User'][0] = 'pending';
break;

default:
# code...
break;
}
$this->set('users', $this->paginate());
}


La clave está en la asignación de un valor al variable $this->paginate['nombre_del_modelo'][0] = 'find_personalizado'. Indicándolo así, Controller::paginate recabará los datos usando esa búsqueda en lugar de findAll.

Recuerda que la versión RC2 todavía no soporta esta capacidad.

miércoles, 26 de marzo de 2008

Personalizando Find y adelgazando el Controller

Aprendí esta técnica gracias a un post de Cakebaker que, a su vez, se basa en uno de Nate Abele más general sobre buenas prácticas en programación MVC con CakePHP.

La idea, una vez conocida, parece obvia. Desde la última beta de CakePHP se ha cambiado la sintaxis del método Model->find, de modo que ahora se llama con dos parámetros, uno indicando el tipo de búsqueda y otro con un array de opciones, que incluye, entre otros, las condiciones de búsqueda, límites, ordenación, etc.

Esta sintaxis a mi me ha resultado muy práctica, ya que no tienes que andar pendiente de si pasas los parámetros en el orden adecuado, etc.

Pero la técnica consiste en ir un poco más allá y reescribir el método a fin de disponer de más tipos de búsquedas personalizadas para nuestras necesidades en cada modelo. La reescritura sería más o menos así:

function find ($type, $options = array()) {
switch ($type) {
case 'tipoPersonalizado':
// Código
break;
default:
return parent::find($type, $options);
break;
}
}


Fíjate que para seguir utilizando los tipos predefinidos por Cake no tienes más que llamar a parent::find con los parámetros adecuados.

Hasta ahora podías (y se sigue pudiendo, claro) escribir métodos del modelo especializados en buscar resultados encapsulando condiciones. Por ejemplo, en un modelo Post, podríamos tener métodos como recientes(), valorados(), destacados(). (Espero que te hagas una idea de cuál sería su funcionamiento).

Con la nueva sintaxis los podríamos reescribir así: find('recientes'), find('valorados'), find('destacados').

Superficialmente esta reescritura no ofrece ninguna ventaja sobre esta forma de trabajar, pero si lo piensas un poco te darás cuenta que puede ser muy útil en ciertas situaciones.

En particular, cuando los diversos tipos de búsqueda comparten ciertas condiciones por defecto. En el ejemplo, podría ser que tu modelo Post sólo deba devolver registros que tengan el campo "publicar" en 1, y las fechas de "publicación" y "caducidad" adecuadas. Evitarías tener que repetir esas condiciones comunes en cada método, centrándote sólo en las específicas .

Además, puedes usar el parámetro $options para pasar opciones que no están contempladas por CakePHP. Por ejemplo, yo estoy usando esta técnica para pasar a find, un rango de fechas con las que seleccionar eventos de un calendario. Pero también podría ser el usuario actual para seleccionar Posts a los que tiene derecho a acceder y un largo etcétera de posibilidades.

Acabo de utilizar esta técnica, como he comentado, con una aplicación de Agenda o Calendario, en la que he movido toda la lógica de búsqueda de eventos al modelo. He añadido hasta siete nuevos tipos de búsqueda (días sueltos, esta semana, hoy, rango de fechas...). Ahora el modelo es un auténtico "fat model", mientras que las acciones del controlador han adelgazado enormemente y son mucho más comprensibles.

En la mayor parte de los casos, los nuevos tipos que he definido lo único que hacen es preparar condiciones y opciones específicas para llamar finalmente a parent::find('all', $options). Es decir, no es que haya que reescribir el método find por completo o construir querys personalizadas, pero podemos simplificar muchísimo la llamada desde el Controller, incluso definiendo nuevas opciones más significativas.

Por ejemplo, en mi modelo Evento he definido un tipo de búsqueda que he denominado 'week', y que sirve para obtener los eventos activos desde hoy hasta dentro de 7 días. En el método personalizado lo único que hago es determinar la fecha de hoy y la fecha de 7 más adelante:

case 'week':
$options['to'] = date('Y-m-d', strtotime( "+ 7 Day"));
$options['from'] = date('Y-m-d');
break;


Luego, inserto esos valores en el array de condiciones que voy a enviar al método parent::find('all')

$conditions = array(
'Evento.fecha_inicio' => '<= '.$options['to'],
'or' => array(
'Evento.fecha_fin' => '>= '.$options['from'],
'and' => array(
'Evento.fecha_fin' => null,
'Evento.fecha_inicio' => '>= '.$options['from']
)
),
'Evento.publicar' => 1
);


if (!isset($options['conditions'])) {
$options['conditions'] = $conditions;
} else {
$options['conditions'] = array_merge($options['conditions'], $conditions);
}

return parent::find('all', $options);


En este caso, te hago notar que no hago la búsqueda en el "case", sólo establezco los valores límite de las fechas, pero podría perfectamente hacer una llamada a parent::find si ya tengo todo lo que necesito.

Otros tipos de búsqueda hacen algo similar, partiendo de algunas opciones que paso (o que no paso, por lo que toman ciertos valores por defecto), genero las fechas límite en las que busco los eventos.

Espero haberme explicado. De todos modos, lo mejor es leer los artículos originales que enlazo al principio.

Ahora estaría bien si Controller-->paginate() llega a soportar esta sintaxis. De momento es un punto un poco oscuro para mí, coordinar paginación y búsquedas.