Posts Tagged ‘ORM’

De la bonne façon de manipuler le temps avec Coat::Persistent

Soit la situation suivante : vous avez une classe qui modélise une table de votre base de données. Cette classe possède un champ de type date.

Comment faire pour pouvoir utiliser côté Perl, un timestamp et stocker en base une date formatée pour la base de données ?

C’est le problème que je me suis posé récemment avec Coat::Persistent. Plus exactement, ce problème s’est posé presque de lui-même pendant ma présentation aux Journées Perl 2009, suite à une question du public.

En fait, je n’avais que la moitié de la solution, et depuis, je me suis pris d’un défi pour résoudre correctement ce problème avec Coat::Persistent.

Je vous propose de voir ensemble comment faire.

Objectifs

  • Utiliser le champ date comme un entier dans le code Perl
  • Ne pas avoir a se soucier de son format de stockage

Dans sa version actuelle, Coat::Persistent ne fait pas de différence entre la valeur assignée a un attribut d’un objet et celle stockée en base. Il nous est donc impossible de réaliser notre objectif de manière élégante sans modifier Coat::Persistent.

La bonne façon de permettre cette fonctionnalité serait donc de dire qu’un attribut peut avoir un type propre (isa) et un type de stockage. On aurait donc quelquechose comme ça :

has_p created_at => (
    is => 'rw',
    isa => 'Int',
    store_as => 'DateTime',
);

Un attribut ainsi déclaré serait donc conscient que sa valeur mémoire (celle de l’objet instancié) est différente de celle stockée en base. Toute la logique de conversion qu’elle soit dans un sens ou dans l’autre serait donc gérée par Coat::Persistent, et non pas par l’utilisateur.

Tout cela est réalisable en utilisant une coercition bi-directionnelle. Derrière ce mot barbare se cache un principe finalement assez simple : une valeur x doit être convertible d’un type A vers un type B, et reciproquement.

Coat permet de définir des coeriction via le mécanisme de types utilisateurs. La subtilité est donc de :

  • Avoir une coercition de définie pour pouvoir convertir une valeur du type de l’attribut vers une valeur du type de stockage (qui interviendra avant un save)
  • Avoir une coercition de définie pour pouvoir convertir une valeur du type de stockage vers une valeur du type de l’attribut (qui interviendra après un find)

Voyons maintenant comment écrire ces types et leur règle de coercition respectives pour le coupe Int,DateTime

Nous allons commencer par définir le type DateTime que nous voulons utiliser pour représenter le format de stockage des date dans une table MySQL (YYYY-MM-DD HH:MM:SS).

subtype 'DateTime'
    => as 'Str'
    => where { /^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$/ };

Un attribut de type DateTime est donc un attribut de type Str et dont la valeur respecte la regexp fournie.
Maintenant il nous faut écire la règle de conversion d’une valeur Int vers une valeur DateTime :

coerce 'DateTime'
    => from 'Int'
    => via {
        my ($sec, $min, $hour, $day, $mon, $year) =
            localtime($_);
        $year += 1900;
        $mon++;
        $day = sprintf('%02d', $day);
        $mon = sprintf('%02d', $mon);
        $hour = sprintf('%02d', $hour);
        $min = sprintf('%02d', $min);
        $sec = sprintf('%02d', $sec);
        return "$year-$mon-$day $hour:$min:$sec";
    };

Le type Int est un type standard, nous n’avons donc pas besoin de le définir. Nous avons seulement besoin de mettre en place une coercition depuis le type DateTime vers le type Int :

coerce 'Int'
    => from 'DateTime'
    => via {
        my ($year, $mon, $day, $hour, $min, $sec) =
             /^(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)$/;
        $year -= 1900;
        $mon--;
        return mktime(
            int($sec), int($min), int($hour),
            $day, $mon, $year);
    };

Bien, tout ce code est intéressant, mais est-ce réellement à l’utilisateur de l’ORM de le définir ? Je ne crois pas, sa place serait idéale dans un jeu de types prédéfinis.

Pourquoi pas proposer une module Coat::Persistent::Types avec tous les types nécessaires ? Cela me semble bien plus élégant.

Imaginons donc que les types et coercitions définis ci-dessus seraient présents dans, Coat::Persistent::Types::MySQL. Le type pourrait même se nommer 'MySQL:DateTime' au lieu de 'DateTime'.

L’utilisateur pourrait donc faire tout simplement :

package Stuff;
use Coat;
use Coat::Persistent;
use Coat::Persistent::Types::MySQL;

has_p created_at => (
    isa => 'Int',
    store_as => 'MySQL:DateTime',
);

Et le tour serait joué !

Il ne resterait alors qu’une seule chose à faire : patcher la mécanique de sauvegarde de Coat::Persistent pour que les valeurs utilisées dans le SQL puissent être converties si nécessaire.

Cela peut se faire très simplement en introduisant la notion de valeur de stockage. Cette valeur serait égale à celle de l’attribut si aucun store_as n’est défini, elle serait égale à la coercition adéquate sinon.

Bien, maintenant que ce problème est résolu, il ne me reste plus qu’à patcher Coat::Persistent et à publier une nouvelle version avec toutes ces bonnes calories intellectuelles…

Tags: , , ,
Posted in Programmation 3 Comments »

Nouvelle fonctionnalité pour Coat::Persistent : cache

Une nouvelle fonctionnalité va faire son apparition d’ici peu de temps dans Coat::Persistent : la possibilité de cacher les résultats des requêtes SQL exécutées.

Le cache est géré par l’excellent Cache::FastMmap. Coat::Persistent proposera une interface simple pour activer le cache :

Coat::Persistent->enable_cache(
    expire_time => '1h',
    cache_size  => '50m',
    share_file  => '/var/cache/apache/sites/www.monsite.cache',
);

Comme chaque option de configuration fournie par Coat::Persistent, le cache peut être activé ou non pour une classe donnée ou pour toutes les classes (ce qui veut dire qu’on peut même immaginer avoir des caches différents par classe).

Du coup, c’est Libellaris.fr qui a pris un speed.

A suivre sur le CPAN donc (c’est déjà dans le SVN).

Tags: , ,
Posted in Programmation Comments Off

Coat Persistent

La famille de Coat s’agrandit, voici un premier essai de mélange des concepts de l’ORM de Rails avec la meta-classe Coat : Coat::Persistent.

Tags: , ,
Posted in Programmation Comments Off

Get Adobe Flash playerPlugin by wpburn.com wordpress themes