miércoles, 29 de octubre de 2014

Ejemplos de Triggers en MySQL

En esta entrada voy a poner ejemplos prácticos de casos que podemos resolver con triggers en MySQL. Los triggers son pequeños programas que ejecuta la base de datos respondiendo a acciones sobre la misma.  En este caso no me voy a enrollar explicando ni teoría, ni código... voy a partir directamente de los ejemplos que son autoexplicativos.

Para verlo partiremos de una base de datos con la siguiente estructura:


Qué tenemos aquí?

Pues es bien sencillo, tenemos una base de datos de una filmoteca personal, en la que podemos asociar cada vídeo a una tabla de actores (varios actores por vídeo) y a las categorías a las que pertenece la peli (que también pueden ser más de una, p.ej. animación / terror). Además tanto actores como categorías tienen un contador que nos indica el número vídeos que tienen relacionados.

Borrado en cascada

Imaginamos que borramos un vídeo. Deberíamos también borrar las tablas de relaciones con actores y categorías. Esto lo podríamos hacer por código desde luego, pero lo podemos poner también como un trigger de la tabla video. Esto tiene la ventaja de que reduce accesos al servidor. Además la Base de Datos trabaja más rápido ya que se entiende consigo misma. Si además borramos vídeos desde el gestor de base de datos phpMyAdmin, ya no tendremos que preocuparnos por las relaciones.

Esto sería así:

DROP TRIGGER IF EXISTS `deleteVideo`;
DELIMITER //
CREATE TRIGGER `deleteVideo` AFTER DELETE ON `Video`
 FOR EACH ROW begin
DELETE FROM relVideoCategory WHERE relVideoCategory.idVideo = old.idVideo;
        DELETE FROM relVideoActor WHERE relVideoActor.idVideo = old.idVideo;
END
//
DELIMITER ;

Actualización de un registro

Cada vez que relacionamos un vídeo con un actor, creamos una entrada en RelVideoActor. Podemos utilizar este hecho para que un nuevo registro en RelVideoActor, aumente el contador de actores. Y es más podemos hacer que una baja de ese registro disminuya el contador. 

delimiter |
CREATE TRIGGER inserRelVideoCategory AFTER INSERT ON relVideoCategory
  FOR EACH ROW
  BEGIN
     UPDATE categories SET categoryCount = categoryCount + 1 WHERE idCategory = NEW.idCategory;
  END;
|

delimiter ;


delimiter |
CREATE TRIGGER deleteRelVideoCategory AFTER DELETE ON relVideoCategory
  FOR EACH ROW
  BEGIN
     UPDATE categories SET categoryCount = categoryCount - 1 WHERE idCategory = OLD.idCategory;
  END;
|

delimiter ;

Autodestrucción

Quizá querríamos también mantener nuestra tabla de actores de manera dinámica. Es decir, los actores sólo permanecerían en la tabla mientras hubiera películas en las que aparecieran. Si su contador llegase a cero, el actor, ejecutaría su mejor papel suicidándose.

Pues bien... podría poneros aquí el código y demás y no daría error... pero al ejecutarlo nos diría que nones, que lo del suicidio no está bien visto por los ojos del señor, ya que no podemos borrar un registro de la misma tabla que dispara el trigger.

Así que deberemos acudir al trigger que elimina la relación y decrementa el contador y contratar ahí a nuestro sicario:

delimiter |
CREATE TRIGGER deleteRelVideoActor AFTER DELETE ON relVideoActor
  FOR EACH ROW
  BEGIN
     UPDATE actor SET actorCount = actorCount - 1 WHERE idActor = OLD.idActor;
     DELETE FROM actor WHERE idActor = OLD.idActor AND actorCount = 0;
  END;
|

delimiter ;

Disable Triggers

Otro caso... imaginemos que queremos borrar categorías, pero ya tenemos vídeos asignados a las mismas.  Podemos  (y no es propaganda del coletas):
  • Comprobar si hay vídeos asignados a la categoría, y si ese es el caso impedir la baja.
  • Borrar la categoría y también todos les registros de RelVideoCategory que la contengan. (que es lo que vamos a hacer)
Relamente no hace falta que ponga el código, sería como en el primer ejemplo de este artículo, el borrado en cascada aunque no funcionaría... ¿y por qué?

Pensad en lo que haríamos:
  1. Borraríamos la categoría cosa que dispararía el borrado de RelVideoCategory
  2. Borraríamos RelVideoCategory cosa que dispararía el borrado de categorías...    :-)
Es decir, la cosa está en que deberíamos impedir que al borrarse RelVideoCategory se disparara su trigger... y para eso necesitamos un comando disable triggers que MySQL no tiene...  :-)

Resolveremos el trigger con el siguiente trick:

DELIMITER ;;
CREATE TRIGGER deleteCategory AFTER DELETE ON category
  FOR EACH ROW begin
        SET @disable_triggers = 1;
  DELETE FROM relVideoCat WHERE relVideoCat.idCategory = old.idCategory;
                SET @disable_triggers = NULL;
END ;;
        
DELIMITER ;

DELIMITER ;;
CREATE TRIGGER deleteRelVideoCat AFTER DELETE ON relVideoCategory
       FOR EACH ROW
        BEGIN
             IF @disable_triggers IS NULL THEN
        UPDATE category SET categoryCount = categoryCount - 1 WHERE idCategory = OLD.idCategory;
             end if;
        END ;;
DELIMITER ;




Y hasta aquí estos pequeños apuntes sobre disparadores. Como siempre esperando a que a alguien le sea útil.

Os dejo que tengo prácticas de tiro.

domingo, 5 de octubre de 2014

Codeigniter Multilenguaje

Codeigniter soporta por sí mismo el multilenguaje, sin embargo tiene un problema en cuanto a los motores de búsqueda, y es que sólo indexarán vuestra página en el lenguaje por defecto. 

Para que indexen en cada uno de los lenguajes que tengamos disponibles, deberíamos crear la típica estructura url amigable que estáis hartos de ver navegando por ahí:

Ej:

http://mytube.honor.es/es/login    ==>  'Español'
http://mytube.honor.es/en/login   ==>  'Inglés'
http://mytube.honor.es/ca/login   ==>  'Catalán'


Internationalization (i18n) for CodeIgniter 2.x

Esta utilidad que reside aquí, permite hacer exactamente esto. Podéis seguir las instrucciones en inglés o bien descargaros los archivos ya creados aquí y seguir leyendo en castellano cómo configurarlo. 

Los programas

Si no lo habéis hecho en el párrafo anterior, nos descargamos los programas de aquí.

Hay tres archivos:

MY_Lang.php
MY_Config.php
MY_language_helper.php   (Este programa falta en muchos paquetes que os podáis descargar por ahí)

Copiaremos los dos primeros en:

./application/core

y el helper en:

./application/helpers


No obstante, deberemos realizar una pequeña modificación en el fichero MY_Lang.php que consistirá en indicar en qué carpeta alojaremos cada lengua.


Siguiendo el ejemplo de las URL amigables al principio del artículo la modificación a realizar justo al principio del archivo, sería:

// languages

var $languages = array(

'en' => 'en',
'ca' => 'ca',
'es' => 'es'
);


Es importante reseñar que las abreviaciones de las lenguas no son triviales y corresponden a una normativa ISO, así que busca primero aquí cómo se representan las lenguas que vayas a usar.

Quitar el index.php

Si os fijáis, en las estructuras de ejemplo que hemos puesto arriba no aparece el index.php por ninguna parte, cosa que favorece la indexación en los motores de búsqueda. Hay mucha información en la web sobre esto y no voy a profundizar al no ser el cometido de este artículo. Yo lo he resuelto creando un archivo de nombre .htaccess en la raíz de mi site.

<Ifmodule mod_rewrite.c> 
     RewriteEngine On 
     RewriteCond %{REQUEST_FILENAME} !-f 
     RewriteCond %{REQUEST_FILENAME} !-d 
     RewriteRule ^(.*)$ /index.php/$1 [L] 
</IfModule>



<Ifmodule mod_rewrite.c> 

    ErrorDocument 404 /index.php
</IfModule>

Modificar archivos de configuración

Por una parte debemos realizar una modificación en el archivo config.php ubicado en la carpeta ./application/config consistente en precisamente eliminar el archivo index.php.

$config['index_page'] = '';

Por otro lado debemos modificar el archivo routes.php también ubicado en la misma carpeta, añadiendo a lo que tengamos las líneas siguientes con el fin de redireccionar la carpeta correspondiente. Fijaos que se refieren a los lenguajes que vamos a usar y seguimos utilizando la normativa ISO.


// URLs como '/es/login' -> usarán el controlador 'login'.

$route['^(es|ca|en)/(.+)$'] = "$2";



// '/es', '/ca' y '/en' URLs -> usarán el controlador por defecto.

$route['^(es|ca|en)$'] = $route['default_controller'];

Crear los archivos de lenguas

A partir de ahora debemos pensar en que ya no volveremos a usar textos en nuestras views, deberemos referenciar en su lugar una variable única para cada uno de los textos y crear un fichero  de traducción para cada lengua.

Por lo tanto, primero creamos la estructura de carpetas que vamos a usar dentro de ./application/language. 

En el ejemplo de las tres lenguas que usamos en el artículo, deberíamos crear:

./application/language/es/
./application/language/en/
./application/language/ca/

Según las características de nuestra aplicación nos puede convenir más crear un archivo de traducciones para cada controlador, uno general o hacer una cosa mixta. En el ejemplo crearemos uno general al que llamaremos global_lang.php. El nombre que le demos es indiferente con la única excepción de que debe terminar con el sufijo _lang. Este fichero lo debemos copiar en todas las carpetas de language.

./application/language/es/global_lang.php
./application/language/en/global_lang.php
./application/language/ca/global_lang.php

Este fichero deberá contener una entrada por variable con la siguiente estructura:

$lang['identificador_texto']='Texto_en_el_idioma_correspondiente';

Volviendo a nuestro ejemplo:

./application/language/es/global_lang.php

<?php
$lang['usuario']='Usuario';

$lang['registro']='Regístrate gratis';

?>

./application/language/en/global_lang.php


<?php
$lang['usuario']='User';
$lang['registro']='Sign up for free';
?>


./application/language/ca/global_lang.php


<?php
$lang['usuario']='Usari';
$lang['registro']='Registre gratuït';
?>



Creación del controlador

Sólo necesitaremos asegurarnos de llamar al helper language y cargar el formato del fichero antes de llamar al view. Esto sería un ejemplo para un posible login, usando nuestro fichero global_lang.php anterior:

function index()
 {

  $this->load->helper('language');
  $this->load->helper('url');

  // Carga el fichero de idioma. ATENCIÓN!!!  NO PONEMOS EL SUFIJO _lang!!!
  $this->lang->load('global');

  $this->load->view('login/login_view');

 }


Uso en las vistas

Es muy sencillo. Allí donde pondríamos el texto de nuestro ejemplo 'Regístrate Gratis', pondremos en su lugar:

<?php echo lang('registro'); ?>

Fijaos que es el nombre de la variable definida en el fichero global_lang.php


Remate final

Con todo lo que hemos visto ya funcionará el multilenguaje sólo!!!!

Pero como apunte final y colofón, dos utilidades que nos pueden ser (y lo serán) útiles.

Para saber en algún momento en qué lenguaje estamos:

$lenguaje = $this->lang->lang()

Esto lo podemos usar, por ejempo, para definir la lengua en nuestra vista:

<html lang="<?php echo $this->lang->lang();?>">

Otra utilidad interesante a tener en cuenta es el enlace necesario, para cambiar de lengua. Por ejemplo en una banderita con la bandera de la pérfida albión pondríamos:

anchor($this->lang->switch_uri('en'),'Muestra la página actual en inglés');