Dans deux articles précédents, nous avons vu une introduction à CouchDB et l’utilisation de CouchDB avec Ruby.
Nous sommes donc maintenant capable d’ajouter des données dans notre base et de récupérer une liste de ceux-ci. Mais il peut parfois arriver d’avoir besoin de faire des requêtes plus complexes.

Prenons par exemple une table CouchDB générée par CouchREST. Vous y verrez, pour chacun de vos modèles, un document nommé « _design/Model ». Par exemple, « _design/File ».
Il ne s’agit en réalité par d’un document. Mais d’une vue.
Regardons l’une des vues générées par CouchREST.

{
    "all": {
        "map": "function(doc) {
            if (doc['couchrest-type'] == 'Image') {
                emit(doc['_id'],1);
            }
        }"
   }
}

Vous lisez bien, les vues CouchDB sont du javascript ! :)

Après avoir chargé cette vue, tentez de visiter la page votre_base/_design/votre_vue/_view/all.
Vous constaterez que seuls les éléments de type « Image » sont retournés.
Notre fonction javascript est exécutée sur chacun des éléments. Et seuls ceux que vous désirez (en les insérant avec « emit ») sont retournés.

Prenons le cas d’une plateforme de blog (oui cas bidon).
Supposons que nous désirions visualiser uniquement les documents ayant l’attribut « published » à true.

{
    "all": {
        "map": "function(doc) {
            if (doc['published'] == true) {
                emit(doc['_id'], doc);
            }
        }"
    }
}

De la même manière que tout à l’heure, nous ne retournons que les billets publiés dans notre blog.
Voici ainsi le contenu qui m’est retourné pour la vue précédente

{
    "total_rows":2,
    "offset":0,
    "rows":[
        {
            "id":"7c46156162a59d145cf9cf7850e6b677",
            "key":"7c46156162a59d145cf9cf7850e6b677",
            "value":{
                "_id":"7c46156162a59d145cf9cf7850e6b677",
                "_rev":"5-f726b6d7f469b686079fbe4c5f50726b",
                "title":"My First Blog Post",
                "content":"My Post Content",
                "published":true,
                "comments":[
                    {"author":"John","content":"First Comment"},
                    {"author":"James","content":"Second Comment"}
                ]
            }
        },
        {
            "id":"f14fde843b20e18561ea5e8055dbc3b3",
            "key":"f14fde843b20e18561ea5e8055dbc3b3",
            "value":{
                "_id":"f14fde843b20e18561ea5e8055dbc3b3",
                "_rev":"1-5eef22da217f5858542d175c41d2ef3d",
                "title":"My Second Post",
                "content":"Second Content",
                "published":true
            }
        }
    ]
}

Vous constatez que j’ai inséré plusieurs commentaires sur l’un de mes billets. Il ne s’agit pas de documents différents mais bien du même document.
L’attribut « comments » est un tableau de commentaires, chacun pouvant contenir les éléments que je désire.

Et la, la problématique à laquelle on pense rapidement, c’est : « Mais comment je fais pour récupérer la liste de tous mes documents ? »
Comme précédemment, avec une vue bien évidemment !

{
    "all": {
        "map": "function(doc) {
            for (var i in doc.comments) {
                emit(doc, doc.comments[i]);
            }
        }"
    }
}

Nous parcourons chacun de nos documents. Puis dans chacun de ceux-ci, nous parcourons tous les commentaires.
Et insérons au resultat tous les commentaires.
Le résultat obtenu est le suivant.

{
    "total_rows":2,
    "offset":0,
    "rows":[
        {
            "id":"7c46156162a59d145cf9cf7850e6b677",
            "key":"My First Blog Post",
            "value":{
                "author":"James",
                "content":"Second Comment"
            }
        },
        {
            "id":"7c46156162a59d145cf9cf7850e6b677",
            "key":"My First Blog Post",
            "value":{
                "author":"John",
                "content":"First Comment"
            }
        }
    ]
}

Nous avons bien la liste de tous nos commentaires. Et (vu que nous l’avons ajoutés au résultat), nous avons même le titre billet qui va avec ! :)

Et les performances dans tout ça ?
Comme c’est de l’HTTP, il y a du cache natif, que CouchDB gère parfaitement bien. Si vous chargez plusieurs fois votre vue, vous verrez donc que la page retournée est bien un 304 not modified.
Je n’ai pas testé avec énormément d’enregistrements. Mais j’ai discuté de cela avec Mirsal lundi soir lors de l’apéro Web Event Lyon. Et apparemment, même avec des milliers de billets, la chose ne posera pas trop de problèmes.
Je reviendrai là-dessus dans quelques mois si il y a matière à en dire quelque chose ;)

Quoi qu’il en soit, je trouve les vues particulièrement intéressantes. Je me retrouve régulièrement frustré par les limitations de SQL.
En développant celles-ci en Javascript, la chose devient virtuellement illimitée.

Mirsal disait également hier: « Les bases de données orientées document sont généralement ce qu’il y a de plus adapté.
C’est SQL qui devrait être utilisé uniquement dans des cas atypiques. »
Je n’irai pas jusqu’à m’avancer comme cela. Mais les possibilités sont tellement impressionnantes que cela laisse rêveur.

Je me suis un peu amusé cette semaine en développant rapidement un widget Netvibes pourRefStats (annonce).
Vous pouvez voir celui-ci et l’installer sur votre page.

Et comme c’est un composant de l’application web refstats, il est sur Github.
Analysons rapidement la chose.

Nous avons tout d’abord des balises xml. Nous définissons l’auteur du plugin, la version de l’API utilisée et le fait qu’on est pas en debug.

<meta name="author" content="Damien MATHIEU" />
<meta name="description" content="Shows the evolution of your positions on Google and any other search engine via refstats.net." />
<meta name="apiVersion" content="1.0" />
<meta name="debugMode" content="false" />

Jusqu’à maintenant, pas grand chose d’intéressant. Mais si on descends un peu en dessous de l’inclusion des CSS de netvibes, on a :

<widget:preferences>
    <preference name="apikey" type="text" label="API Key" defaultValue="69dc57d333617ffcaa136109a0ff5e3d" />
    <preference name="engine" type="list" label="Engine" defaultValue="1" >
        <option value="0" label="all" />
        <option value="1" label="google.com" />
        <option value="2" label="google.fr" />
        <option value="4" label="google.ch" />
        <option value="5" label="google.be" />
        <option value="6" label="google.ca" />
        <option value="3" label="Yahoo!" />
    </preference>
</widget:preferences>

Pour comprendre ce que nous faisons, regardez ce que donne le widget (voir le lien « voir celui-ci » plus haut).
Et cliquez sur le bouton « Editer ». Vous constatez que nous avons les champs tels que définis dans les préférences ici.

Nous définissons ici un champ texte pour la clé d’API et une liste pour le moteur à utiliser.
Mais il existe d’autres types : text, boolean, hidden, password, range, list.
Ces options sont sauvegardées automatiquement lorsque vous validez et accessibles par la suite en javascript avec la méthode

widget.getValue('option_name');

Puisque nous parlons de javascript, attaquons-nous y.
Vous pouvez voir un

widget.onLoad = function() {
[...]
}

Cette méthode est exécutée lors du chargement du widget. Et à chaque modification des paramètres de celui-ci.
Les autres méthodes utilisées ici sont des méthodes internes au script. Je vous laisse les analyser vous même si cela vous amuse.

Vous constaterez cependant deux choses :
Les appels ajax. L’API RefStats retourne du json. Je fais donc appel à la méthode UWA.Data.getJson, qui prends deux arguments : l’url de la page à appeller.
Et la méthode à appeller avec les données une fois celles-ci reçues.
A vous après de traiter vos données, que cela soit du json avec getJson, du xml avec getXml ou du texte avec getText.

Vous constaterez également l’utilisation de la méthode widget.setBody();.
Cette méthode permet de définir le contenu du widget de manière dynamique. Ici, je redéfinis entièrement le contenu à chaque fois en me basant sur les données que j’ai en variables.
Rien ne vous empêche cependant de manipuler le dom de manière plus fine. L’objet widget possède une méthode createElement.

Bon, cet article est particulièrement concis. Mais il deviendrait trop long si je commencais à détailler entièrement ce qui est déjà indiqué dans la documentation Netvibes. Je vous laisse donc vous y référer.
Mais je pense que cela fait déjà un bon début. Rien qu’avec ça, il est déjà possible de faire deux ou trois trucs sympa :)

J’ai déjà parlé plusieurs fois ici de tests unitaires. En Ruby ou en PHP.
Cependant toute application qui se respecte aura également une couche navigateur, permettant d’ajouter de l’ajax par exemple.
Ces fonctionnalités, tout comme le code serveur, sont testables.

Voyons un petit peu comment cela fonctionne avec QUnit, le framework de tests unitaires intégré avec JQuery.

Pour cela, je vais reprendre mon exemple d’il y a quelques mois ou je proposais de signaler si un pseudonyme est déjà utilisé lors d’une inscription, et de tester cela.
Cependant aujourd’hui, j’ai fait la chose en PHP, afin d’épargner la création d’un projet RoR. On a au final, dans cet exemple, besoin que de deux fichiers :

check_user.php, qui vérifie si notre pseudonyme est disponible ou non. Et retourne ‘OK’ si il l’est et ‘NOT’ sinon.
On ne s’embête pas ici. Voici ce que j’ai dans mon document :

<?php if ($_GET['u'] == 'oh') echo 'OK';
else echo 'NOT'; ?>

Puis dans notre index.php, nous allons placer le champ de texte du nom d’utilisateur.

<html>
    <head>
        <title></title>
        <script src="http://www.google.com/jsapi"></script>
        <script type="text/javascript>
            google.load("jquery", "1.3.2");
        </script>
        <script type="text/javascript" src="code.js"></script>
    </head>
    <body>
        <input type="text" id="username" name="username" />
        <div id="availability"></div>
    </body>
</html>

Et nous appellons notre document javascript qui fera un appel ajax à check_user.php.

$(document).ready(function() {
    $('#username').blur(function () {
        username = $('#username').val();
        if (username == '') {
            $('#availability').html('');
            return false;
        }

        $.ajax({
            url: 'check_user.php?u='+username,
            success: function(data) {
                    if (data == 'OK') {
                        $('#availability').html('Disponible');
                        $('#availability').css('color', 'green');
                    } else {
                        $('#availability').html('Non disponible');
                        $('#availability').css('color', 'red');
                    }
            },
            error: function(data) {
                $('#availability').html('');
            }
        });
    })
});

Voila ! Maintenant, nous avons notre test de validité de l’utilisateur de fonctionnel. Pour l’instant du moins car pour être sur qu’il le reste, nous allons ajouter des tests.
Commençons par implémenter QUnit dans notre index.php.

<html>
    <head>
        <title></title>

        <script src="http://www.google.com/jsapi"></script>
        <script type="text/javascript">
            google.load("jquery", "1.3.2");
        </script>
        <script type="text/javascript" src="code.js"></script>
    </head>
    <body>

        <input type="text" id="username" name="username" />
        <div id="availability"></div>

        <link rel="stylesheet" href="http://github.com/jquery/qunit/raw/master/qunit/qunit.css" type="text/css" media="screen" /
	<script type="text/javascript" src="http://github.com/jquery/qunit/raw/master/qunit/qunit.js"></script>
        <script type="text/javascript" src="tests.js"></script>
        <h2 id="qunit-banner">Qunit Testing</h2>
	<h2 id="qunit-userAgent"></h2>
	<ol id="qunit-tests"></ol>
    </body>
</html>

Vous pouvez constater que nous ajoutons divers tags html en fin de page. Ceux-ci seront automatiquement remplacés par l’interface de visualisation des tests.

Et enfin dans le fichier tests.js inclus précédemment, nous plaçons tous nos tests.

$.ajaxSetup({
  async: false
});

module("username")

test("availability div shouldn't have any value by default", function() {
    equals($('#availability').html(), '', "availability div doesn't have any value by default");
});

test("should allow inexistant username", function() {
    $('#username').val('oh');
    $('#username').blur();

    equals($('#availability').html(), 'Disponible', "the username is available");
    equals($('#availability').css('color'), 'green', "the username is available");
});

test("should not allow existant username", function() {
    $('#username').val('doh');
    $('#username').blur();

    equals($('#availability').html(), 'Non disponible', "the username is not available");
    equals($('#availability').css('color'), 'red', "the username is not available");
});

test("should hide the message if username is empty", function() {
    $('#username').val('');
    $('#username').blur();

    equals($('#availability').html(), '', "the username is empty");
})

La méthode « module » permet de signaler que nous sommes dans la partie de vérification du pseudonyme de la page.
Chaque méthode test définit un test.

Quant au ajaxSetup, il nous permet de forcer les requêtes ajax à ne pas être asynchrone. Si elles l’étaient, nos tests seraient exécutés avant le retour de la requête ajax et échoueraient donc.

Si vous rechargez votre page, vous pouvez maintenant voir que nous testons convenablement (et avec succès) la vérification de la disponibilité de notre pseudonyme.

Pour finir, vous me direz rapidement que c’est bien gentil de tester ça. Mais que vous n’allez pas charger chacune des pages de votre site à chaque fois que vous changez une ligne de code.
La solution à cela, qui est cependant trop complexe pour tenir dans cet article, est d’utiliser Selenium, un outil permettant de tester le frontend d’une application web sur de multiples navigateurs.

Pour finir, vous pouvez voir toutes les portions de code données dans cet article avec mise en couleur du code.

Oui je sais, je lague à faire des tutoriaux pour débutants de choses qui datent de mathusalem.
Cependant il ne me semble pas en avoir vu de sympa qui utilisent Rails et JQuery.

Commençons d’abord par vérifier l’existence (ou non) du pseudonyme.
Dans notre modèle utilisateur, nous plaçons la fonction suivante :
def nick_available
render :text => 'OK' and return unless User.find_by_login params[:id]
render :text => 'NOT'
end

Par la suite, si vous appelez votre méthode nick_available, vous verrez s’afficher le texte ‘OK’ si le pseudonyme est disponible. Et NOT sinon.

Passons maintenant à notre formulaire d’inscription. Nous avons un champ texte pour le pseudonyme et un div vide qui nous indiquera si le pseudonyme est disponible ou non.
<%= f.text_field :login %>
<span id="availability"></span>

Enfin, nous devons faire un appel ajax à cette méthode lorsque l’utilisateur a fini de taper son identifiant (et pas à chaque fois que celui-ci change. Sauf si nous cherchons à saturer la connexion de l’utilisateur et notre serveur).
<script type="text/javascript">
$(document).ready(function() {
$('#user_login').blur(function () {
username = $('#user_login').val();
if (username == '') {
$('#availability').html('');
return false;
}
$.get('/users/nick_available/'+username, function(data) {
if (data == 'OK') {
$('#availability').html('Disponible');
$('#availability').css('color', 'green');
} else {
$('#availability').html('Non disponible');
$('#availability').css('color', 'red');
}
});
})
});

Ainsi, lorsque le pseudonyme est utilisé, le div ayant pour id « availability » prendra la couleur rouge et la valeur « Non disponible ».
Dans le cas contraire, il affichera en vert « Disponible ».

Lorsque l’on aime regarder des photos, on voudrait pouvoir en avoir tout le temps sur son écran.
Pas super pratique d’avoir une photo lorsque l’on est en même temps en train de surfer sur internet …

C’est à ce problème que remédie Pickrfox, puisque l’extension ouvre une barre latérale contenant diverses photos sur flickr. Celles de vos amis, de vos contacts, les votres ou plus simplement toutes les dernières photos postées …
Et vous pouvez même, pour chacune des photos affichées, mettre celle-ci en favoris et/ou poster un commentaire dessus :-)
Cool non ?

Taizé

 
Fork me on GitHub