jueves, 7 de junio de 2007

FlatTable Behavior, unifica los resultados de un modelo y sus belongsTo

Bueno, de Behavior a Helper y, de nuevo, Behavior.

Este Behavior sirve básicamente para fusionar los resultados de asociaciones belongsTo de un modelo para que "parezca" que son parte del mismo modelo.

La idea es poder pasar esos resultados a la vista y generar una tabla simple (tengo un helper para eso). Un ejemplo lo aclara:

Tengo un modelo Recurso, que guarda información de una serie de aparatos disponibles en el servicio de Nuevas Tecnologías del colegio en que trabajo. Los Recursos se organizan en Categorías, de modo que cada Recurso pertenece (belongsTo) a una Categoría.

Lógicamente los usuarios querrían ven la lista de recursos con su Categoría (no su id), por lo que a la hora de generar la vista tendría que combinar los datos. La misión de este Behavior es proporcionarme un método general para hacerlo que no me complique mucho las vistas.

El Behavior me permite, entonces, obtener la lista de Recursos y fundir los campos de Categoria de modo que se presenten en una tabla simple.

Posibles mejoras

El behavior tendría que comprobar algunos puntos que ahora mismo quedan al buen criterio a la hora de llamar.

Estoy valorando la posibilidad de que siempre haya que activarlo explícitamente mediante el flag Model::useFlatBehavior, haciendo que este se ponga en false nada más utilizarlo. De este modo me aseguro de no obtener resultados inconsistentes en según qué situaciones.

A lo mejor hay que ponerlo en otro Behavior, pero se me ocurre que otra "fusión" útil sería fundir campos de modelos asociados mediante hasMany para que se muestren como un sólo campo del modelo principal.

Instrucciones
  1. Copia el código que está más abajo y ponlo en /app/models/behaviors/flat_table.php
  2. En el modelo, especifica Model::actsAs para que incluya FlatTable, puedes pasarle un array con los modelos y campos que quieres fusionar.
  3. Establece una variable del modelo Model::useFlatTable = false; para especificar cuándo quieres usar o no el behavior. Yo la inicializo a false para tener que activarla expresamente.
  4. Antes de solicitar los datos en el Controlador, activa el flag anterior mediante $this->Model->useFlatTable = true; sustituye Modelo por el nombre de tu modelo, claro.
La forma de especificar en Model::actAs es la siguiente:

array ('FlatTable' => array ('Modelo 1 para fusionar' => array ('campos', 'del', 'modelo 1')

Si no especificas campos, se fusionarán todos. Puedes especificar solo un campo con la estructura:

array ('Modelo 1' => 'campo del modelo')

O sólo un modelo:

array ('FlatTable' =>'Modelo 1 para fusionar')

Un ejemplo de uso en el modelo:

class Recurso extends AppModel
{
var $name = 'Recurso';

var $actsAs = array (
'Upload' => array ('imagen' => array ('ruta' => 'test')),
'FlatTable' => array ('Categoria' => 'categoria')
);
var $useFlatTable = false;
var $belongsTo = array ('Categoria');

...
} // END class Recurso



FlatTable

Pon esto en:

/app/models/behaviors/flat_table.php


/**
* Behavior para unir los datos de una relación belongsTo de modo que se devuelvan
* como una sola tabla. Básicamente se pretende facilitar el uso de tablas sencillas
* cuando realmente no se necesita más.
*
* This Behavior joins data from belongsTo associations returning the array as it was a single model.
* The point here is to pass the data to a simple table view. foreign key deleted from the
* resulting array
*
* @package default
* @author Fran Iglesias
* @version 0.1
**/
class FlatTableBehavior extends ModelBehavior {
// Array que especifica modelos/campos para unir
var $queUnir = array ();

/**
* Parse settings, a list of associated models, and fields that must be merged with primary
* Cargar e interpretar los ajustes, una lista de modelos asociados y campos que deben mezclarse con el primario
*
* @return void
* @author Frankie
**/

function setup (&$model, $settings = false) {
// Si no se pasan settings, no usar el Behavior
// Don't use behavior if no settings passed
if (!$settings) {
$model->useFlatTable = false;
return;
}
// Convertir la cadena en array si es necesario, se asume que se pasa un modelo
// String to Array conversion if needed, $settings is a Model
if (!is_array ($settings)) {
$settings = array ($settings => array ());
}
// Parse settings to ensure right format
// Revisar settings para asegurarse de que el formato es correcto
foreach ($settings as $modelo => $campos) {
if (is_numeric ($modelo)) {
$modelo = $campos;
$campos = array ();
}
if (!is_array ($campos)) {
$campos = array ($campos);
}
$this->queUnir[$modelo] = $campos;
}
}

/**
* Modifica la estructura del array de datos de modo que simula que los datos
* entregados tras una relación belongsTo son de una única tabla
*
* Flatten array as it was a proper Model array
*
* @return void
* @author Frankie
**/

function afterFind (&$model, $results, $primary) {

// Comprobar el flag useFatTable
// Check model::useFatTable
if (empty ($model->useFlatTable) ) {
return true;
}
// No hacer nada si los datos vuelven vacíos
if (empty ($results)) {
return true;
}
// No hacer nada si el modelo primaria no está
if (!isset ($results[0][$model->name])) {
return true;
}
// Obtener los modelos que podría unir con este sistema
// What models can we join?
$belongsTo = array_keys ($model->belongsTo );
$modelosEnResultado = array_keys ($results[0]);
$unir = array_intersect ($belongsTo, $modelosEnResultado);
//
$recordSet = array ();
foreach ($results as $clave => $fila) {
$record = $fila[$model->name];
foreach ($unir as $modelo) {
// Quitar las foreignKeys, strip foreignkeys (¿Settings for this?)
$foreignKey = strtolower ($modelo).'_'.'id';
unset ($record[$foreignKey]);
if (!empty ($this->queUnir[$modelo])) {
foreach ($this->queUnir[$modelo] as $campo) {
$record[$campo] = $fila[$modelo][$campo];
}
} else {
// Strip associated model id key
unset ($fila[$modelo]['id']);
$record = array_merge ($record, $fila[$modelo]);
}
}
$recordSet[][$model->name] = $record;
}
return $recordSet;
}

} // END class FlatTableBehavior extends Behavior

?>

No hay comentarios: