<?xml version="1.0" encoding="UTF-8"?> <rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
><channel><title>Choses à faire &#187; Développement</title> <atom:link href="http://www.chosesafaire.fr/category/developpement/feed/" rel="self" type="application/rss+xml" /><link>http://www.chosesafaire.fr</link> <description>24h dans une journée, et tant de choses à faire !</description> <lastBuildDate>Tue, 02 Mar 2010 10:13:22 +0000</lastBuildDate> <generator>http://wordpress.org/?v=2.9.2</generator> <language>en</language> <sy:updatePeriod>hourly</sy:updatePeriod> <sy:updateFrequency>1</sy:updateFrequency> <item><title>Easyb, Behavior Driven Development</title><link>http://www.chosesafaire.fr/2010/03/easyb-behavior-driven-development/</link> <comments>http://www.chosesafaire.fr/2010/03/easyb-behavior-driven-development/#comments</comments> <pubDate>Tue, 02 Mar 2010 10:13:22 +0000</pubDate> <dc:creator>Killian Ebel</dc:creator> <category><![CDATA[Développement]]></category> <category><![CDATA[agile]]></category> <category><![CDATA[bdd]]></category> <category><![CDATA[groovy]]></category> <category><![CDATA[java]]></category> <category><![CDATA[tdd]]></category> <category><![CDATA[tests]]></category><guid isPermaLink="false">http://www.chosesafaire.fr/?p=310</guid> <description><![CDATA[Pour rappel, le concept de Behavior Driven Development propose de se focaliser sur le comportement attendu de l&#8217;application plutôt que sur les tests. Ces comportements peuvent être décrits soit sous la forme de spécifications, soit sous la forme d&#8217;histoires !Installation d&#8217;Easyb
Easyb est un outil permettant d&#8217;implémenter le BDD dans un projet Java, bien que le [...]Articles liés :<ol><li><a href='http://www.chosesafaire.fr/2009/10/vraptor-le-framework-qui-a-les-crocs/' rel='bookmark' title='Permanent Link: VRaptor, le framework qui a les crocs'>VRaptor, le framework qui a les crocs</a></li><li><a href='http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/' rel='bookmark' title='Permanent Link: Mocks et Multithreading'>Mocks et Multithreading</a></li></ol>]]></description> <content:encoded><![CDATA[<p>Pour rappel, le concept de <a href="http://fr.wikipedia.org/wiki/Behavior_Driven_Development">Behavior Driven Development</a> propose de se focaliser sur le comportement attendu de l&#8217;application plutôt que sur les tests. Ces comportements peuvent être décrits soit sous la forme de spécifications, soit sous la forme d&#8217;histoires !</p><p><span id="more-310"></span></p><h2>Installation d&#8217;Easyb</h2><p>Easyb est un outil permettant d&#8217;implémenter le BDD dans un projet Java, bien que le code à écrire pour rédiger vos spécifications sera du <a href="http://fr.wikipedia.org/wiki/Groovy_%28langage%29">Groovy</a>, dont la syntaxe est plus légère (plus agile) que Java. Il est comparable à l&#8217;outil <a href="http://code.google.com/p/specs/">specs</a> en <a href="http://fr.wikipedia.org/wiki/Scala_%28langage%29">Scala</a> ou encore <a href="http://rspec.info/">RSpec</a> en <a href="http://fr.wikipedia.org/wiki/Ruby">Ruby</a>.</p><p>Pour <a href="http://www.easyb.org/running.html">l&#8217;intégrer à votre projet</a>, vous disposez de multiples possibilités : ligne de commande, <a href="http://fr.wikipedia.org/wiki/Apache_Ant">Ant</a>, <a href="http://fr.wikipedia.org/wiki/Apache_Maven">Maven</a> ou encore l&#8217;intégration avec les <a href="http://fr.wikipedia.org/wiki/Environnement_de_d%C3%A9veloppement_int%C3%A9gr%C3%A9">IDE</a> <a href="http://www.jetbrains.com/idea/">IntelliJ</a> et <a href="http://www.eclipse.org/">Eclipse</a>.</p><p>Il m&#8217;a été impossible d&#8217;installer le plugin Eclipse, l&#8217;adresse du plugin donnée sur le site officiel n&#8217;ayant plus l&#8217;air d&#8217;exister ! Je me suis donc penché sur la solution Maven. Créons donc le projet :</p><pre class="brush: shell">
mvn archetype:create -DgroupId=fr.chosesafaire -DartifactId=easyb -Dpackagename=fr.chosesafaire
</pre><p>Editons le fichier de projet <strong>pom.xml</strong> pour y inclure Easyb, qui sera appelée lors de la phase <strong>test</strong> :</p><pre class="brush: xml">
&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;

&lt;properties&gt;
    &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
&lt;/properties&gt;

&lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
&lt;groupId&gt;fr.chosesafaire&lt;/groupId&gt;
&lt;artifactId&gt;easyb&lt;/artifactId&gt;
&lt;packaging&gt;jar&lt;/packaging&gt;
&lt;name&gt;easyb&lt;/name&gt;
&lt;version&gt;0.0.1&lt;/version&gt;
&lt;description&gt;Test de Easyb&lt;/description&gt;
&lt;url&gt;http://www.chosesafaire.fr&lt;/url&gt;

&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;source&gt;1.5&lt;/source&gt;
                &lt;target&gt;1.5&lt;/target&gt;
                &lt;encoding&gt;UTF-8&lt;/encoding&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.easyb&lt;/groupId&gt;
            &lt;artifactId&gt;maven-easyb-plugin&lt;/artifactId&gt;
            &lt;version&gt;0.9.6&lt;/version&gt;
            &lt;executions&gt;
                &lt;execution&gt;
                    &lt;goals&gt;
                        &lt;goal&gt;test&lt;/goal&gt;
                    &lt;/goals&gt;
                &lt;/execution&gt;
            &lt;/executions&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;

&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;junit&lt;/groupId&gt;
        &lt;artifactId&gt;junit&lt;/artifactId&gt;
        &lt;version&gt;4.7&lt;/version&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;

&lt;/project&gt;
</pre><p>Nous développerons dans cet exemple une classe <strong>HelloMachine</strong>, qui n&#8217;a pour but que de renvoyer &laquo;&nbsp;Hello&nbsp;&raquo; et de stocker le fait d&#8217;avoir été appelée. Voici le scenario, à placer le fichier <strong>src/test/easby/HelloMachineStory.groovy</strong>, répertoire qui peut être facilement paramétré depuis le fichier Maven :</p><pre class="brush: groovy">
import fr.chosesafaire.HelloMachine

/**
 * Calling the HelloMachine !
 */
scenario "calling the hellomachine", {
    given "an hellomachine", {
        machine = new HelloMachine()
        hello = "Hello"
    }

    when "sayHello is called", {
        sayHello = machine.sayHello()
    }

    then "hello should be returned", {
        sayHello.shouldBe hello
    }

    and "then the machine should have been called", {
        machine.isCalled().shouldBe true
    }
}
</pre><p>Et enfin notre classe métier :</p><pre class="brush: java">
package fr.chosesafaire;

public class HelloMachine {
    private Boolean called = Boolean.FALSE;

    public String sayHello() {
        called = Boolean.TRUE;
        return "Hello";
    }

    public Boolean isCalled() {
        return called;
    }
}
</pre><p>Il ne reste qu&#8217;à lancer la compilation et les tests :</p><pre class="brush: shell">
mvn compile test
</pre><p>Si vous n&#8217;avez pas Groovy d&#8217;installé, Maven le fera pour vous, car c&#8217;est une dépendance d&#8217;Easyb. Le résultat dans la console devrait être le suivant (bien qu&#8217;il soit possible de générer des rapports HTML !) :</p><pre class="brush: shell">
[java] Running hello machine story (HelloMachineStory.groovy)
[java] Scenarios run: 1, Failures: 0, Pending: 0, Time Elapsed: 0.313 sec
[java] 1 behavior run with no failures
</pre><p>Ceci n&#8217;est qu&#8217;une brève introduction, j&#8217;espère que vous comprendrez l&#8217;intérêt d&#8217;un tel outil en étudiant plus en détail les fonctionnalités ! Voici une liste de liens utiles :</p><ul><li><a href="http://www.easyb.org/dsls.html">La documentation officielle</a></li><li><a href="http://www.easyb.org/maven-easyb-plugin/index.html">Maven Easyb Plugin</a></li><li><a href="http://code.google.com/p/easyb/w/list">Le wiki d&#8217;Easyb</a></li><li><a href="http://behaviour-driven.org/">Quelques détails concernant le Behavior Driven Development</a></li></ul><div class="sexy-bookmarks sexy-bookmarks-center"><ul class="socials"><li class="sexy-blogmarks"> <a href="http://blogmarks.net/my/new.php?mini=1&amp;simple=1&amp;url=http://www.chosesafaire.fr/2010/03/easyb-behavior-driven-development/&amp;title=Easyb%2C+Behavior+Driven+Development" rel="nofollow" title="Marquez-le sur BlogMarks">Marquez-le sur BlogMarks</a></li><li class="sexy-google"> <a href="http://www.google.com/bookmarks/mark?op=add&amp;bkmk=http://www.chosesafaire.fr/2010/03/easyb-behavior-driven-development/&amp;title=Easyb%2C+Behavior+Driven+Development" rel="nofollow" title="Ajoutez-le à Google Bookmarks">Ajoutez-le à Google Bookmarks</a></li><li class="sexy-delicious"> <a href="http://del.icio.us/post?url=http://www.chosesafaire.fr/2010/03/easyb-behavior-driven-development/&amp;title=Easyb%2C+Behavior+Driven+Development" rel="nofollow" title="Partagez-le sur del.icio.us">Partagez-le sur del.icio.us</a></li><li class="sexy-reddit"> <a href="http://reddit.com/submit?url=http://www.chosesafaire.fr/2010/03/easyb-behavior-driven-development/&amp;title=Easyb%2C+Behavior+Driven+Development" rel="nofollow" title="Partagez-le sur Reddit">Partagez-le sur Reddit</a></li><li class="sexy-stumbleupon"> <a href="http://www.stumbleupon.com/submit?url=http://www.chosesafaire.fr/2010/03/easyb-behavior-driven-development/&amp;title=Easyb%2C+Behavior+Driven+Development" rel="nofollow" title="Tomber sur un bon truc ? Partagez cet article sur StumbleUpon">Tomber sur un bon truc ? Partagez cet article sur StumbleUpon</a></li><li class="sexy-technorati"> <a href="http://technorati.com/faves?add=http://www.chosesafaire.fr/2010/03/easyb-behavior-driven-development/" rel="nofollow" title="Partagez-le sur Technorati">Partagez-le sur Technorati</a></li><li class="sexy-twitter"> <a href="http://twitter.com/home?status=Easyb%2C+Behavior+Driven+Development+-+http://tinyurl.com/yhksbyf+(via+@dandelionmood)" rel="nofollow" title="Tweetez-le !">Tweetez-le !</a></li><li class="sexy-comfeed"> <a href="http://www.chosesafaire.fr/2010/03/easyb-behavior-driven-development/feed" rel="nofollow" title="S'abonner aux commentaires de cet article ?">S'abonner aux commentaires de cet article ?</a></li></ul><div style="clear:both;"></div></div><p>Articles liés :</p><ol><li><a href='http://www.chosesafaire.fr/2009/10/vraptor-le-framework-qui-a-les-crocs/' rel='bookmark' title='Permanent Link: VRaptor, le framework qui a les crocs'>VRaptor, le framework qui a les crocs</a></li><li><a href='http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/' rel='bookmark' title='Permanent Link: Mocks et Multithreading'>Mocks et Multithreading</a></li></ol></p>]]></content:encoded> <wfw:commentRss>http://www.chosesafaire.fr/2010/03/easyb-behavior-driven-development/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>cachedLoad(), un plugin de cache pour jQuery</title><link>http://www.chosesafaire.fr/2010/01/cachedload-un-plugin-de-cache-pour-jquery/</link> <comments>http://www.chosesafaire.fr/2010/01/cachedload-un-plugin-de-cache-pour-jquery/#comments</comments> <pubDate>Tue, 26 Jan 2010 14:00:17 +0000</pubDate> <dc:creator>Pierre Quillery</dc:creator> <category><![CDATA[Développement]]></category> <category><![CDATA[cache]]></category> <category><![CDATA[jquery]]></category> <category><![CDATA[plugin]]></category><guid isPermaLink="false">http://www.chosesafaire.fr/?p=282</guid> <description><![CDATA[Aujourd&#8217;hui, pour changer un peu de notre habituel programme sur Ruby on Rails, je me propose de revenir sur un article que j&#8217;ai publié il y a quelques mois qui parlait des propriétés window.sessionStorage et window.localStorage présentes dans la spécification HTML 5. Nous allons exploiter la première pour améliorer de façon transparente les performances d&#8217;un [...]Articles liés :<ol><li><a href='http://www.chosesafaire.fr/2009/09/file-get-contents-un-plugin-wordpress-pour-inclure-un-fichier-dans-vos-posts/' rel='bookmark' title='Permanent Link: file-get-contents : Un plugin Wordpress pour inclure un fichier dans vos posts'>file-get-contents : Un plugin Wordpress pour inclure un fichier dans vos posts</a></li><li><a href='http://www.chosesafaire.fr/2009/09/localstorage-la-suite-logique-de-google-gears/' rel='bookmark' title='Permanent Link: LocalStorage, la suite logique de Google Gears ?'>LocalStorage, la suite logique de Google Gears ?</a></li></ol>]]></description> <content:encoded><![CDATA[<p>Aujourd&#8217;hui, pour changer un peu de notre habituel programme sur Ruby on Rails, je me propose de revenir sur un article que j&#8217;ai publié il y a quelques mois qui parlait des propriétés <code>window.sessionStorage</code> et <code>window.localStorage</code> présentes dans la spécification HTML 5. Nous allons exploiter la première pour améliorer de façon transparente les performances d&#8217;un site dans les navigateurs compatibles (<em>webkit</em> / <em>gecko</em> pour l&#8217;instant).</p><p><span id="more-282"></span></p><p><strong>L&#8217;idée est donc de créer un plugin pour jQuery qui va se substituer au chargement Ajax classique et stocker dans la mémoire du navigateur un bout de page pendant la durée de la session. </strong> Le site que j&#8217;ai équipé de ce système charge de cette façon 3 ou 4 éléments sur chaque page visitée (le pied de page, un bloc de connexion, un moteur de recherche, etc.). À l&#8217;instar des <em>cookies</em>, ce cache n&#8217;est accessible que sur un seul <em>vhost</em>.</p><p>Bien sûr, les navigateurs qui ne supportent pas cette propriété utiliseront un chargement classique, de la même façon qu&#8217;ils le faisaient auparavant. Concrètement, <strong>il suffit généralement de remplacer votre appel à la méthode <code>ajax</code> de jQuery pour bénéficier du cache</strong>.</p><p>Ainsi <code>$('#element').ajax('http://www.google.fr');</code> va devenir <code>$('#element').cachedLoad('http://www.google.fr');</code>. Vous pouvez également passer les paramètres habituels de la fonction <code>load()</code>.</p><p>Enfin, pour supprimer volontairement le cache associé à un div, il vous suffit d&#8217;appeler la méthode <code>cachedLoad()</code> sans aucun paramètre.</p><p>Voilà, j&#8217;espère que ce petit <em>plugin</em> vous sera utile <img src='http://www.chosesafaire.fr/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> !</p><pre class="brush: javascript;">
/**
	cachedLoad 1.0
	Pierre Quillery
    http://www.chosesafaire.fr/2010/01/cachedload-un-plugin-de-cache-pour-jquery/
 
	@param url string 
		Url à charger
	@param data object
		Paramètres à passer à la requête
	@param callback function 
		Fonction de callback à appeler éventuellement après le chargement
	
	Ce plugin permet d'utiliser la fonctionnalité html5 sessionStorage disponible 
	dans les navigateurs récents et ainsi éviter de multiplier les requêtes inutiles
	sur le serveur pour les bouts de page statiques (pied de page, formulaires de
	connexion etc.).
	
	*Si aucun paramètre n'est passé*, cela a pour effet de supprimer le cache
	associé à l'élément visé par le sélecteur.
	
	Voici la documentation &quot;mozilla&quot; :
	https://developer.mozilla.org/en/DOM/Storage#sessionStorage
	
	Pour les navigateurs n'implémentant pas cette fonctionnalité, on fait un 
	chargement classique.
	
	L'utilisation est calquée sur la méthode &quot;load()&quot; de jQuery
	(http://api.jquery.com/load/) : 
	
	$('#mon_div').cachedLoad('http://www.google.fr', function() { 
		alert('ok!'); 
	});
	
	$('#mon_div').cachedLoad('http://www.google.fr', {data:&quot;data&quot;}, function() { 
		alert('ok avec des paramètres!'); 
	});
	
	// Pour supprimer le cache
	$('#mon_div').cachedLoad();
	
	
	ATTENTION 
	==============================================================================
	Il est nécessaire que l'attribut ID du conteneur soit défini, car il est 
	utilisé pour définir une clé unique dans le tableau du sessionStorage. S'il 
	n'est pas	défini, on fera un appel Ajax classique et les données ne seront
	tout simplement pas mises en cache.
	==============================================================================
	
*/
jQuery.fn.cachedLoad = function(url, data, callback) {

	// Pour chaque appel du plugin ...
  return this.each(function() {
  	
  	// On récupère le contexte
		var self = $(this);
		var storage = window.sessionStorage; 

		// Est-ce qu'une URL a bien été passée en paramètre ?
		if(url) {
	
			// On autorise la syntaxe sans &quot;data&quot;, comme jQuery
			if(typeof(data) == 'function') { callback = data; data = {}; }
			// Options par défaut
			data 			= data 			|| null;
			callback 	= callback 	|| function(){};
	
			// Si le sessionStorage est disponible et que l'élément a bien un id ...
			if(storage &amp;&amp; self.attr('id') != '') {
		
				// On cherche le bout de page dans le cache
				var cache = storage['cachedLoad_' + self.attr('id')];

				// Le bout de page existait, on va l'afficher puis exécuter le callback
				if(cache) {
					self.html(cache);
					if(window.console &amp;&amp; console.log) 
						console.log(&quot;cachedLoad : &quot;, url);
					callback();
			
				// Le bout de page n'était pas en cache
				} else {
					// On le charge, on le met en cache, puis on appelle le callback
					self.load(url, data, function(r) {
						storage['cachedLoad_' + self.attr('id')] = r;
						callback();
					});
				}
	
			// Si le sessionStorage n'est pas disponible, on fait un appel classique
			// à la méthode load() de jQuery.
			} else {
				self.load(url, data, callback);
			}
		
		// Si aucune URL n'a été passée en paramètre, cela signifie que l'on
		// doit supprimer le cache
		} else {
		
			if(window.sessionStorage &amp;&amp; self.attr('id') != '') {
				// On supprime l'entrée du cache pour forcer le futur rechargement
				delete storage['cachedLoad_' + self.attr('id')];
				if(window.console &amp;&amp; console.log) 
					console.log(&quot;cachedLoad supprimé pour : &quot;, self.attr('id'));
			}
			
		}
			
	});
};

</pre><div class="sexy-bookmarks sexy-bookmarks-center"><ul class="socials"><li class="sexy-blogmarks"> <a href="http://blogmarks.net/my/new.php?mini=1&amp;simple=1&amp;url=http://www.chosesafaire.fr/2010/01/cachedload-un-plugin-de-cache-pour-jquery/&amp;title=cachedLoad%28%29%2C+un+plugin+de+cache+pour+jQuery" rel="nofollow" title="Marquez-le sur BlogMarks">Marquez-le sur BlogMarks</a></li><li class="sexy-google"> <a href="http://www.google.com/bookmarks/mark?op=add&amp;bkmk=http://www.chosesafaire.fr/2010/01/cachedload-un-plugin-de-cache-pour-jquery/&amp;title=cachedLoad%28%29%2C+un+plugin+de+cache+pour+jQuery" rel="nofollow" title="Ajoutez-le à Google Bookmarks">Ajoutez-le à Google Bookmarks</a></li><li class="sexy-delicious"> <a href="http://del.icio.us/post?url=http://www.chosesafaire.fr/2010/01/cachedload-un-plugin-de-cache-pour-jquery/&amp;title=cachedLoad%28%29%2C+un+plugin+de+cache+pour+jQuery" rel="nofollow" title="Partagez-le sur del.icio.us">Partagez-le sur del.icio.us</a></li><li class="sexy-reddit"> <a href="http://reddit.com/submit?url=http://www.chosesafaire.fr/2010/01/cachedload-un-plugin-de-cache-pour-jquery/&amp;title=cachedLoad%28%29%2C+un+plugin+de+cache+pour+jQuery" rel="nofollow" title="Partagez-le sur Reddit">Partagez-le sur Reddit</a></li><li class="sexy-stumbleupon"> <a href="http://www.stumbleupon.com/submit?url=http://www.chosesafaire.fr/2010/01/cachedload-un-plugin-de-cache-pour-jquery/&amp;title=cachedLoad%28%29%2C+un+plugin+de+cache+pour+jQuery" rel="nofollow" title="Tomber sur un bon truc ? Partagez cet article sur StumbleUpon">Tomber sur un bon truc ? Partagez cet article sur StumbleUpon</a></li><li class="sexy-technorati"> <a href="http://technorati.com/faves?add=http://www.chosesafaire.fr/2010/01/cachedload-un-plugin-de-cache-pour-jquery/" rel="nofollow" title="Partagez-le sur Technorati">Partagez-le sur Technorati</a></li><li class="sexy-twitter"> <a href="http://twitter.com/home?status=cachedLoad%28%29%2C+un+plugin+de+cache+pour+jQuery+-+http://tinyurl.com/y9sqcps+(via+@dandelionmood)" rel="nofollow" title="Tweetez-le !">Tweetez-le !</a></li><li class="sexy-comfeed"> <a href="http://www.chosesafaire.fr/2010/01/cachedload-un-plugin-de-cache-pour-jquery/feed" rel="nofollow" title="S'abonner aux commentaires de cet article ?">S'abonner aux commentaires de cet article ?</a></li></ul><div style="clear:both;"></div></div><p>Articles liés :</p><ol><li><a href='http://www.chosesafaire.fr/2009/09/file-get-contents-un-plugin-wordpress-pour-inclure-un-fichier-dans-vos-posts/' rel='bookmark' title='Permanent Link: file-get-contents : Un plugin Wordpress pour inclure un fichier dans vos posts'>file-get-contents : Un plugin Wordpress pour inclure un fichier dans vos posts</a></li><li><a href='http://www.chosesafaire.fr/2009/09/localstorage-la-suite-logique-de-google-gears/' rel='bookmark' title='Permanent Link: LocalStorage, la suite logique de Google Gears ?'>LocalStorage, la suite logique de Google Gears ?</a></li></ol></p>]]></content:encoded> <wfw:commentRss>http://www.chosesafaire.fr/2010/01/cachedload-un-plugin-de-cache-pour-jquery/feed/</wfw:commentRss> <slash:comments>2</slash:comments> </item> <item><title>Mocks et Multithreading</title><link>http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/</link> <comments>http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/#comments</comments> <pubDate>Fri, 22 Jan 2010 12:45:09 +0000</pubDate> <dc:creator>Killian Ebel</dc:creator> <category><![CDATA[Développement]]></category> <category><![CDATA[java]]></category> <category><![CDATA[mock]]></category> <category><![CDATA[multithread]]></category> <category><![CDATA[tests]]></category><guid isPermaLink="false">http://www.chosesafaire.fr/?p=273</guid> <description><![CDATA[Afin de compléter la série d&#8217;articles sur les tests logiciels, voici une méthode permettant de tester efficacement une application &#171;&#160;concurrente&#160;&#187;.Java et Multithreading
Premièrement, il est important de comprendre le fonctionnement de l&#8217;interface Callable en Java ; une interface similaire à Runnable, à la différence près qu&#8217;un thread Callable renvoie une valeur lors de son exécution.
Ensuite, la [...]Articles liés :<ol><li><a href='http://www.chosesafaire.fr/2009/10/mocks-stubs-la-suite/' rel='bookmark' title='Permanent Link: Mocks &amp; Stubs : La suite'>Mocks &amp; Stubs : La suite</a></li><li><a href='http://www.chosesafaire.fr/2009/12/le-probleme-du-singleton/' rel='bookmark' title='Permanent Link: Le problème du Singleton'>Le problème du Singleton</a></li><li><a href='http://www.chosesafaire.fr/2009/10/mocks-stubs-leurres-des-tests/' rel='bookmark' title='Permanent Link: Mocks &amp; Stubs : Leurres des Tests'>Mocks &amp; Stubs : Leurres des Tests</a></li></ol>]]></description> <content:encoded><![CDATA[<p>Afin de compléter la série d&#8217;articles sur <a href="http://www.chosesafaire.fr/2009/10/mocks-stubs-leurres-des-tests/">les tests logiciels</a>, voici une méthode permettant de tester efficacement une application &laquo;&nbsp;concurrente&nbsp;&raquo;.</p><p><span id="more-273"></span></p><h2>Java et Multithreading</h2><p>Premièrement, il est important de comprendre le fonctionnement de l&#8217;interface <a href="http://kamleshkr.wordpress.com/2009/10/02/java-threads-callable-and-future/">Callable</a> en Java ; une interface similaire à <a href="http://java.sun.com/javase/6/docs/api/java/lang/Runnable.html">Runnable</a>, à la différence près qu&#8217;un thread <a href="http://java.sun.com/javase/6/docs/api/java/util/concurrent/Callable.html">Callable</a> renvoie une valeur lors de son exécution.</p><p>Ensuite, la classe utilitaire <a href="http://java.sun.com/javase/6/docs/api/java/util/concurrent/Executors.html">Executors</a>, utile pour créer des <a href="http://java.sun.com/javase/6/docs/api/java/util/concurrent/ExecutorService.html">pool de Threads</a>, des <a href="http://java.sun.com/javase/6/docs/api/java/util/concurrent/ScheduledExecutorService.html">pool de Threads périodique</a> ou encore transformer un <strong>Runnable</strong> en <strong>Callable</strong>.</p><p>Enfin, l&#8217;interface <a href="http://java.sun.com/javase/6/docs/api/java/util/concurrent/Future.html">Future</a>, qui permet de stocker le futur résultat d&#8217;un appel asynchrone. Ce résultat peut être obtenu en appelant la méthode <strong>get()</strong> de votre objet, appel qui peut être bloquant si le résultat n&#8217;a pas encore été reçu. Il est possible de spécifier un <strong>timeout</strong> qui lèvera une exception si la durée de l&#8217;appel est supérieur.</p><p>Dans notre exemple, nous utiliserons un <a href="http://java.sun.com/javase/6/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool%28int%29">pool de taille fixe</a> (nombre maximal de Threads défini), qui instanciera des <strong>Future</strong> stockant le résultat d&#8217;un calcul.</p><h2>Mocking et Multithreading</h2><p>C&#8217;est <a href="http://easymock.org/">EasyMock</a> qui sera utilisé, de par sa <a href="http://easymock.org/api/easymockclassextension/2.4/org/easymock/classextension/EasyMock.html#makeThreadSafe%28java.lang.Object,%20boolean%29">capacité</a> à rendre l&#8217;exécution d&#8217;un Mock <a href="http://fr.wikipedia.org/wiki/Threadsafe">Threadsafe</a>. <a href="http://mockito.org/">Mockito</a> permet aussi de créer des Mocks Threadsafe ; voici une brève comparaison entre <a href="http://mockito.org/">EasyMock et Mockito</a>, mais qui donne clairement l&#8217;avantage à Mockito du point de vue syntaxique !</p><h2>Mise en pratique</h2><p>Premièrement, la classe principale :</p><pre class="brush: java;">
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class CalculMultiThread {
    private static final int THREAD_TIME_OUT = 10;
    private static final int POOL_SIZE = 5;

    private ExecutorService pool = null;

    public CalculMultiThread() {
        pool = Executors.newFixedThreadPool(POOL_SIZE);
    }

    public void setPool(ExecutorService pool) {
        this.pool = pool;
    }

    public void calculsParalleles(Integer op1, Integer op2, Integer op3, Integer op4) {
        final AdditionCallable additionCallable = new AdditionCallable();
        additionCallable.setOperande1(op1);
        additionCallable.setOperande2(op2);

        final SoustractionCallable soustractionCallable = new SoustractionCallable();
        soustractionCallable.setOperande1(op3);
        soustractionCallable.setOperande2(op4);

        final Future&lt;Integer&gt; additionFuture = pool.submit(additionCallable);
        final Future&lt;Integer&gt; soustractionFuture = pool.submit(soustractionCallable);

        try {
            final Integer resultatAddition = additionFuture.get(THREAD_TIME_OUT, TimeUnit.SECONDS);
            final Integer resultatSoustraction = soustractionFuture.get(THREAD_TIME_OUT, TimeUnit.SECONDS);

            System.out.println(op1 + " + " + op2 + " = " + resultatAddition);
            System.out.println(op3 + " + " + op4 + " = "  + resultatSoustraction);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }

    public static void main(String[] args) {
        CalculMultiThread calculMultiThread = new CalculMultiThread();
        calculMultiThread.calculsParalleles(5, 6, 8, 3);
    }
}

abstract class CalculCallable implements Callable&lt;Integer&gt; {
    private Integer operande1 = null;
    private Integer operande2 = null;

    public void setOperande1(Integer operande1) {
        this.operande1 = operande1;
    }

    public Integer getOperande1() {
        return this.operande1;
    }

    public void setOperande2(Integer operande2) {
        this.operande2 = operande2;
    }

    public Integer getOperande2() {
        return this.operande2;
    }
}

class AdditionCallable extends CalculCallable {
	public Integer call() throws Exception {
		return getOperande1() + getOperande2();
	}
}

class SoustractionCallable extends CalculCallable {
	public Integer call() throws Exception {
		return getOperande1() - getOperande2();
	}
}
</pre><p>Le résultat de l&#8217;opération :</p><pre class="brush: shell;">
5 + 6 = 11
8 - 3 = 5
</pre><p>Enfin, les tests :</p><pre class="brush: java;">
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;

public class CalculMultiThreadTest {
    private ExecutorService executorServiceMock;
    private CalculMultiThread calculMultiThread;

    @Before
    public void setUp() throws Exception {
        executorServiceMock = EasyMock.createMock(ExecutorService.class);
        calculMultiThread = new CalculMultiThread();
        EasyMock.makeThreadSafe(executorServiceMock, true);
    }

    @SuppressWarnings("unchecked")
    @Test
    public void testCallTasksInParallel() throws Exception {
        calculMultiThread.setPool(executorServiceMock);

        Future&lt;Integer&gt; additionFutureMock = (Future&lt;Integer&gt;) EasyMock.createMock(Future.class);
        EasyMock.expect(additionFutureMock.get(EasyMock.anyLong(), EasyMock.isA(TimeUnit.class))).andReturn(11);
        EasyMock.replay(additionFutureMock);

        Future&lt;Integer&gt; soustractionFutureMock = (Future&lt;Integer&gt;) EasyMock.createMock(Future.class);
        EasyMock.expect(soustractionFutureMock.get(EasyMock.anyLong(), EasyMock.isA(TimeUnit.class))).andReturn(5);
        EasyMock.replay(soustractionFutureMock);

        EasyMock.expect(executorServiceMock.submit(EasyMock.isA(AdditionCallable.class))).andReturn(additionFutureMock);
        EasyMock.expect(executorServiceMock.submit(EasyMock.isA(SoustractionCallable.class))).andReturn(soustractionFutureMock);
        EasyMock.replay(executorServiceMock);

        calculMultiThread.calculsParalleles(5, 6, 8, 3);

        EasyMock.verify(additionFutureMock);
        EasyMock.verify(soustractionFutureMock);
        EasyMock.verify(executorServiceMock);
    }
}
</pre><p>Voilà, ce n&#8217;est pas plus compliqué de tester du code multithreadé. Pour plus d&#8217;informations, vous pouvez toujours consulter les liens qui suivent, merci !</p><ul><li><a href="http://merereflections.wordpress.com/2010/01/20/unit-testing-multi-threaded-code-with-easymock/">Source d&#8217;inspiration</a></li><li><a href="http://www.sizovpoint.com/2009/03/java-mock-frameworks-comparison.html">Comparaison des frameworks de Mock</a></li><li><a href="http://java.sun.com/docs/books/tutorial/essential/concurrency/">Java et applications concurrentes</a></li></ul><div class="sexy-bookmarks sexy-bookmarks-center"><ul class="socials"><li class="sexy-blogmarks"> <a href="http://blogmarks.net/my/new.php?mini=1&amp;simple=1&amp;url=http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/&amp;title=Mocks+et+Multithreading" rel="nofollow" title="Marquez-le sur BlogMarks">Marquez-le sur BlogMarks</a></li><li class="sexy-google"> <a href="http://www.google.com/bookmarks/mark?op=add&amp;bkmk=http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/&amp;title=Mocks+et+Multithreading" rel="nofollow" title="Ajoutez-le à Google Bookmarks">Ajoutez-le à Google Bookmarks</a></li><li class="sexy-delicious"> <a href="http://del.icio.us/post?url=http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/&amp;title=Mocks+et+Multithreading" rel="nofollow" title="Partagez-le sur del.icio.us">Partagez-le sur del.icio.us</a></li><li class="sexy-reddit"> <a href="http://reddit.com/submit?url=http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/&amp;title=Mocks+et+Multithreading" rel="nofollow" title="Partagez-le sur Reddit">Partagez-le sur Reddit</a></li><li class="sexy-stumbleupon"> <a href="http://www.stumbleupon.com/submit?url=http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/&amp;title=Mocks+et+Multithreading" rel="nofollow" title="Tomber sur un bon truc ? Partagez cet article sur StumbleUpon">Tomber sur un bon truc ? Partagez cet article sur StumbleUpon</a></li><li class="sexy-technorati"> <a href="http://technorati.com/faves?add=http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/" rel="nofollow" title="Partagez-le sur Technorati">Partagez-le sur Technorati</a></li><li class="sexy-twitter"> <a href="http://twitter.com/home?status=Mocks+et+Multithreading+-+http://tinyurl.com/yjokfno+(via+@dandelionmood)" rel="nofollow" title="Tweetez-le !">Tweetez-le !</a></li><li class="sexy-comfeed"> <a href="http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/feed" rel="nofollow" title="S'abonner aux commentaires de cet article ?">S'abonner aux commentaires de cet article ?</a></li></ul><div style="clear:both;"></div></div><p>Articles liés :</p><ol><li><a href='http://www.chosesafaire.fr/2009/10/mocks-stubs-la-suite/' rel='bookmark' title='Permanent Link: Mocks &amp; Stubs : La suite'>Mocks &amp; Stubs : La suite</a></li><li><a href='http://www.chosesafaire.fr/2009/12/le-probleme-du-singleton/' rel='bookmark' title='Permanent Link: Le problème du Singleton'>Le problème du Singleton</a></li><li><a href='http://www.chosesafaire.fr/2009/10/mocks-stubs-leurres-des-tests/' rel='bookmark' title='Permanent Link: Mocks &amp; Stubs : Leurres des Tests'>Mocks &amp; Stubs : Leurres des Tests</a></li></ol></p>]]></content:encoded> <wfw:commentRss>http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>La couche Modèle de Rails (Partie 3)</title><link>http://www.chosesafaire.fr/2010/01/la-couche-modele-de-rails-partie-3/</link> <comments>http://www.chosesafaire.fr/2010/01/la-couche-modele-de-rails-partie-3/#comments</comments> <pubDate>Sun, 10 Jan 2010 14:32:26 +0000</pubDate> <dc:creator>Pierre Quillery</dc:creator> <category><![CDATA[Développement]]></category> <category><![CDATA[modèle]]></category> <category><![CDATA[rails]]></category><guid isPermaLink="false">http://www.chosesafaire.fr/?p=265</guid> <description><![CDATA[Aujourd&#8217;hui, je me propose d&#8217;achever cette série par quelques explications concernant les fichiers de garniture (aussi appelés fixtures) ainsi que l&#8217;utilisation des filtres (on parle de named scope) pour faciliter la sélection de données.Cet article fait partie d&#8217;une série sur le framework Ruby on Rails dont vous pouvez consulter le plan sur mon ancien site.
Il [...]Articles liés :<ol><li><a href='http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/' rel='bookmark' title='Permanent Link: La couche Modèle de Rails (Partie 2)'>La couche Modèle de Rails (Partie 2)</a></li><li><a href='http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/' rel='bookmark' title='Permanent Link: La couche Modèle de Rails (Partie 1)'>La couche Modèle de Rails (Partie 1)</a></li></ol>]]></description> <content:encoded><![CDATA[<p>Aujourd&#8217;hui, je me propose d&#8217;achever cette série par quelques explications concernant les fichiers de garniture (aussi appelés <em>fixtures</em>) ainsi que l&#8217;utilisation des filtres (on parle de <em>named scope</em>) pour faciliter la sélection de données.</p><p><span id="more-265"></span></p><p>Cet article fait partie d&#8217;une série sur le framework <em>Ruby on Rails</em> dont vous pouvez <a href="http://dandelionmood.com/Ruby-on-Rails-lancez-vous.html">consulter le plan</a> sur mon ancien site.</p><p>Il est également le dernier de la série de trois articles que j&#8217;ai publié sur l&#8217;implémentation de la couche <strong>Modèle</strong> de <em>Ruby on Rails</em>. Vous pouvez lire <a href="http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/">le premier article</a> ou <a href="http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/">le second</a>, si vous ne l&#8217;avez pas fait.</p><h2>Les filtres (<em>named_scope</em>)</h2><p>Ces filtres sont à mon avis une fonctionnalité très intéressante de Rails : <strong>ils permettent d&#8217;ajouter simplement un jeu de critères nommé à une requête SQL</strong>. Si on traduit le terme Anglais, il s&#8217;agit littéralement de &laquo;&nbsp;points de vue nommés&nbsp;&raquo; ; ce n&#8217;est pas très explicite, et c&#8217;est pourquoi je préfère le terme de &laquo;&nbsp;filtre&nbsp;&raquo; &#8211; il ne faut cependant pas les confondre avec les filtres de Contrôleur <a href="http://dandelionmood.com/Ruby-on-Rails-Filtres-Flashes.html">dont je parlais dans un précédent article</a>.</p><p>Par exemple, imaginons que nous avons à notre disposition un magasin en ligne qui souhaite mettre en avant les produits qui sont les moins chers (dont le prix est entre 1 et 5 euros par exemple). Nous pourrions être tentés d&#8217;écrire ceci, par exemple :</p><pre class="brush: ruby;">
Articles.find :all, :conditions => { :prix => [ 0 .. 5 ] }
</pre><p>Notez la syntaxe <code>[ ... ]</code>, elle permet de spécifier un intervale de valeurs. L&#8217;inconvénient de cette façon de faire c&#8217;est que si par la suite nous voulons ajouter d&#8217;autres crtières de sélection (uniquement les articles de telle catégorie, uniquement ceux qui sont soldées, dont la date de péremption expire bientôt, etc.) nous allons devoir <strong>dupliquer ce critère</strong> &#8230; De plus, si notre client décide d&#8217;agrandir la gamme de prix, nous serions obligés de repasser sur tout notre code en risquant d&#8217;oublier quelque chose.</p><p>Nous pourrions bien sûr utiliser des constantes, ou des variables de classe, mais ça rendrait l&#8217;écriture de nos conditions encore plus longues et moins explicite &#8211; c&#8217;est là qu&#8217;interviennent les <code>named_scope</code>. Éditons la classe Article pour ajouter un filtre :</p><pre class="brush: ruby;">
class Article &lt; ActiveRecord::Base
  named_scode :pas_chers, :conditions => { :prix => [ 0 .. 5 ] }
end
</pre><p>Pour l&#8217;instant, nous n&#8217;avons fait que déplacer un peu de code du Contrôleur vers le Modèle, rien de transcendant &#8211; mais voilà ce que nous pouvons maintenant écrire :</p><pre class="brush: ruby;">
@articles = Article.pas_chers.find :all, :conditions => { :rubrique_id => 2 }
</pre><p><strong>Et voilà le travail : nous pouvons réutiliser et même chaîner des jeux de critères de sélection !</strong> Attention toutefois à ne pas en abuser, même si c&#8217;est assez tentant : cela a un impact sur les performances qui doit rester maîtrisé.</p><p>Pour en savoir plus, je vous renvoie à <a href="http://api.rubyonrails.org/classes/ActiveRecord/NamedScope/ClassMethods.html#M002177">la documentation de la méthode sur l&#8217;API</a> ainsi qu&#8217;à <a href="http://railscasts.com/episodes/108-named-scope">un épisode des Railscasts de Ryan Bates</a> (en Anglais).</p><h2>Les fichiers de garniture (<em>fixtures</em>)</h2><p>Les fichiers de garniture permettent d&#8217;ajouter facilement des données de test à votre application et vont donc en faciliter le développement : leur écriture peut se faire sous forme de fichier <code>yaml</code> ou <code>csv</code>. La première forme est la plus intéressante, car elle s&#8217;intègre mieux à Rails et permet même d&#8217;utiliser directement les mécanismes de relations entre les classes Modèles <a href="http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/">que nous voyions dans un précédent article</a>.</p><p>Concrètement, tout se passe dans les fichiers qui sont générés automatiquement quand vous créez des Modèles à l&#8217;aide de la commande <code>script/generate model ...</code>. Regardez dans le répertoire <code>test/fixtures</code> pour les retrouver. En reprenant l&#8217;exemple des classes que nous voyions dans l&#8217;article précédent, vous devriez ainsi trouver les fichiers <code>articles.yml</code>, <code>taggables.yml</code>, <code>tags.yml</code> et <code>utilisateurs.yml</code>.</p><p>Ouvrons dans un premier temps le premier fichier, <code>articles.yml</code> :</p><pre class="brush: yaml;">
one:
  login: MyString
  mot_de_passe: MyString

two:
  login: MyString
  mot_de_passe: MyString
</pre><p>Comme vous pouvez le voir, le fait d&#8217;utiliser le générateur pour créer la classe de modèle Utilisateur a également eu pour effet de générer deux entrées dans ce fichier en renseignant directement les noms des colonnes que nous avions spécifié.</p><p>Notez également que chaque enregistrement possède déjà un identifiant (<code>one</code> et <code>two</code>) qui nous permettra d&#8217;y faire référence dans un autre fichier pour créer une relation !</p><p>Modifions un peu ce fichier pour créer des Utilisateur plus intéressants :</p><pre class="brush: yaml;">
pierre:
  login: pierre
  mot_de_passe: mdp1

killian:
  login: killian
  mot_de_passe: mdp2

georges:
  login: georges
  mot_de_passe: mdp3
</pre><p>Nous pouvons maintenant utiliser chacun de ces enregistrements dans la garniture de la classe Article ! Ouvrons donc le fichier <code>articles.yml</code> :</p><pre class="brush: yaml;">
one:
  titre: MyString
  contenu: MyText
  utilisateur_id: 1

two:
  titre: MyString
  contenu: MyText
  utilisateur_id: 1
</pre><p>Rails nous propose de changer la valeur de la colonne <code>utilisateur_id</code>, ce qui est tout à fait possible &#8211; cependant, puisque nous avons <em>déjà</em> défini des relations entre ces deux classes, nous allons pouvoir nous passer de cette étape et réécrire le contenu de ce fichier de la façon suivante :</p><pre class="brush: yaml;">
article1:
  titre: "Mon premier article"
  contenu: "Contenu de mon premier article"
  utilisateur: pierre

article2:
  titre: "Mon deuxième article"
  contenu: "Contenu de mon deuxième article"
  utilisateur: killian

article3:
  titre: "Mon troisième article"
  contenu: "Contenu de mon troisième article"
  utilisateur: pierre
</pre><p>Rails comprendra tout seul que l&#8217;utilisateur <code>pierre</code> auquel nous faisons référence a été défini dans le fichier <code>utilisateurs.yml</code>.</p><p>Nous pouvons maintenant vider notre base de développement (<code>rake db:reset</code>)pour la remplir avec ces quelques enregistrements, à l&#8217;aide de la commande <code>rake db:fixtures:load</code>. Ouvrez maintenant une console Rails, (<code>script/console</code>) et constatez que les enregistrements ont bien été créés dans la base :</p><pre class="brush: plain;">
>> Utilisateur.find :all
=> [#&lt;Utilisateur id: 134130964, login: "killian", mot_de_passe: "mdp2",
created_at: "2009-12-23 13:26:49", updated_at: "2009-12-23 13:26:49">,
#&lt;Utilisateur id: 219077049, login: "georges", mot_de_passe: "mdp3",
created_at: "2009-12-23 13:26:49", updated_at: "2009-12-23 13:26:49">,
#&lt;Utilisateur id: 362712307, login: "pierre", mot_de_passe: "mdp1",
created_at: "2009-12-23 13:26:49", updated_at: "2009-12-23 13:26:49">]

>> Article.find :all
=> [#&lt;Article id: 151089403, titre: "Mon troisième article",
contenu: "Contenu de mon troisième article", utilisateur_id: 362712307,
created_at: "2009-12-23 13:26:50", updated_at: "2009-12-23 13:26:50">,
#&lt;Article id: 655299028, titre: "Mon premier article",
contenu: "Contenu de mon premier article", utilisateur_id: 362712307,
created_at: "2009-12-23 13:26:50", updated_at: "2009-12-23 13:26:50">,
.#&lt;Article id: 1040597104, titre: "Mon deuxième article",
contenu: "Contenu de mon deuxième article", utilisateur_id: 134130964,
created_at: "2009-12-23 13:26:50", updated_at: "2009-12-23 13:26:50">]
</pre><p>Vous remarquerez que les identifiants des enregistrements créés par ce biais ne commencent jamais par 1, ce qui permet de les distinguer facilement des enregistrements standards et ce qui évitent qu&#8217;ils perturbent vos tests manuels.</p><p>Si vous voulez tagger un article dans les garnitures, c&#8217;est un peu plus sophistiqué, car il faut indiquer à Rails que vous passez par une table intermédiaire à cause de l&#8217;utilisation du système <code>has_many :through</code> ; la démarche reste cependant la même : voyez directement dans <a href="http://api.rubyonrails.org/classes/Fixtures.html">la documentation</a> pour en apprendre plus.</p><p>En guise de conclusion, notez que les <em>fixtures</em> ne sont pas une réponse à toutes les situations. Par exemple, c&#8217;est généralement une mauvaise idée de s&#8217;en servir pour placer des données nécessaires au bon fonctionnement de l&#8217;application : les données que vous y placerez doivent être réservées à vos tests fonctionnels &amp; unitaires.</p><p>Si vous avez besoin de mettre en place des données de base pour votre application en production, utilisez le fichier <code>db/seeds.rb</code>. Vous pourrez ensuite exécuter les commandes qu&#8217;il contient avec la commande <code>rake db:setup</code>.</p><h2>La suite au prochain épisode !</h2><p>Cet article était le dernier de notre série consacrée à la couche Modèle de Rails : aussi dès la semaine prochaine (si tout va bien <img src='http://www.chosesafaire.fr/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> ) nous entamerons une toute nouvelle séquence qui concernera l&#8217;implémentation des ressources et des formulaires dans notre <em>framework</em> préféré.</p><div class="sexy-bookmarks sexy-bookmarks-center"><ul class="socials"><li class="sexy-blogmarks"> <a href="http://blogmarks.net/my/new.php?mini=1&amp;simple=1&amp;url=http://www.chosesafaire.fr/2010/01/la-couche-modele-de-rails-partie-3/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+3%29" rel="nofollow" title="Marquez-le sur BlogMarks">Marquez-le sur BlogMarks</a></li><li class="sexy-google"> <a href="http://www.google.com/bookmarks/mark?op=add&amp;bkmk=http://www.chosesafaire.fr/2010/01/la-couche-modele-de-rails-partie-3/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+3%29" rel="nofollow" title="Ajoutez-le à Google Bookmarks">Ajoutez-le à Google Bookmarks</a></li><li class="sexy-delicious"> <a href="http://del.icio.us/post?url=http://www.chosesafaire.fr/2010/01/la-couche-modele-de-rails-partie-3/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+3%29" rel="nofollow" title="Partagez-le sur del.icio.us">Partagez-le sur del.icio.us</a></li><li class="sexy-reddit"> <a href="http://reddit.com/submit?url=http://www.chosesafaire.fr/2010/01/la-couche-modele-de-rails-partie-3/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+3%29" rel="nofollow" title="Partagez-le sur Reddit">Partagez-le sur Reddit</a></li><li class="sexy-stumbleupon"> <a href="http://www.stumbleupon.com/submit?url=http://www.chosesafaire.fr/2010/01/la-couche-modele-de-rails-partie-3/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+3%29" rel="nofollow" title="Tomber sur un bon truc ? Partagez cet article sur StumbleUpon">Tomber sur un bon truc ? Partagez cet article sur StumbleUpon</a></li><li class="sexy-technorati"> <a href="http://technorati.com/faves?add=http://www.chosesafaire.fr/2010/01/la-couche-modele-de-rails-partie-3/" rel="nofollow" title="Partagez-le sur Technorati">Partagez-le sur Technorati</a></li><li class="sexy-twitter"> <a href="http://twitter.com/home?status=La+couche+Mod%C3%A8le+de+Rails+%28Partie+3%29+-+http://tinyurl.com/yggasbk+(via+@dandelionmood)" rel="nofollow" title="Tweetez-le !">Tweetez-le !</a></li><li class="sexy-comfeed"> <a href="http://www.chosesafaire.fr/2010/01/la-couche-modele-de-rails-partie-3/feed" rel="nofollow" title="S'abonner aux commentaires de cet article ?">S'abonner aux commentaires de cet article ?</a></li></ul><div style="clear:both;"></div></div><p>Articles liés :</p><ol><li><a href='http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/' rel='bookmark' title='Permanent Link: La couche Modèle de Rails (Partie 2)'>La couche Modèle de Rails (Partie 2)</a></li><li><a href='http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/' rel='bookmark' title='Permanent Link: La couche Modèle de Rails (Partie 1)'>La couche Modèle de Rails (Partie 1)</a></li></ol></p>]]></content:encoded> <wfw:commentRss>http://www.chosesafaire.fr/2010/01/la-couche-modele-de-rails-partie-3/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>Applications et bases de données, comment tester ?</title><link>http://www.chosesafaire.fr/2009/12/applications-et-bases-de-donnees-comment-tester/</link> <comments>http://www.chosesafaire.fr/2009/12/applications-et-bases-de-donnees-comment-tester/#comments</comments> <pubDate>Thu, 17 Dec 2009 08:50:09 +0000</pubDate> <dc:creator>Killian Ebel</dc:creator> <category><![CDATA[Développement]]></category> <category><![CDATA[Outils]]></category> <category><![CDATA[base]]></category> <category><![CDATA[données]]></category> <category><![CDATA[intégration]]></category> <category><![CDATA[java]]></category> <category><![CDATA[mock]]></category> <category><![CDATA[tests]]></category> <category><![CDATA[unitaire]]></category><guid isPermaLink="false">http://www.chosesafaire.fr/?p=165</guid> <description><![CDATA[Même si l'on aimerait l'éviter, les tests unitaires portent souvent sur des parties du programme accédant à la base de données. Bien qu'il existe plusieurs outils servant à simplifier la tâche, voici tout d'abord des bonnes pratiques que je conseillerais à tout développeur.Articles liés :<ol><li><a href='http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/' rel='bookmark' title='Permanent Link: Mocks et Multithreading'>Mocks et Multithreading</a></li><li><a href='http://www.chosesafaire.fr/2009/12/le-probleme-du-singleton/' rel='bookmark' title='Permanent Link: Le problème du Singleton'>Le problème du Singleton</a></li></ol>]]></description> <content:encoded><![CDATA[<p>Même si l&#8217;on préfèrerait l&#8217;éviter, les tests unitaires d&#8217;une application web par exemple, portent souvent sur des parties du programme accédant à une base de données. Bien qu&#8217;il existe plusieurs outils servant à simplifier la tâche, voici tout d&#8217;abord des bonnes pratiques que je conseillerais à tout développeur.</p><p><span id="more-165"></span></p><h2>Bien séparer les couches</h2><p>Même si cela peut paraître idiot de le répéter encore une fois, il est indubitable que moins votre code concernera les accès à la base, moins il y aura de tests à faire en ce sens. Ainsi, concentrez ces parties par exemple dans des <a href="http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html">DAO</a>, très loin des partie « IHM » et « Business » de votre application.</p><h2>Utilisez une base de données embarquée</h2><p>Je ne sais pas si c&#8217;est réellement une bonne pratique, mais il me semble idéal pour l&#8217;indépendance des tests qu&#8217;ils puissent être lancés n&#8217;importe où / n&#8217;importe quand, même quand le <a href="http://fr.wikipedia.org/wiki/Syst%C3%A8me_de_gestion_de_base_de_donn%C3%A9es">SGBD</a> réel n&#8217;est pas actif. Ainsi, des systèmes tels que <a href="http://www.sqlite.org/">sqlite</a>, <a href="http://hsqldb.org/">HSQLDB</a> ou d&#8217;autres de la même catégorie sont à mon goût plus adaptés pour les tests.</p><h2>Évitez au maximum les dépendances</h2><p>Analysez ces deux classes et imaginez la plus pratique à tester :</p><pre class="brush: java;">
public class UserDAOImpl implements UserDAO {

    protected Connection getConnection() throws SQLException {
        return DriverManager.getConnection("jdbc://...", "login", ...);
    }

    // ... createUser ...
}
</pre><pre class="brush: java;">
public class UserDAOImpl implements UserDAO {
    private Connection connection;

    public UserDAOImpl(Connection connection) {
        this.connection = connection;
    }

    // ... createUser ...
}
</pre><p>Voici la méthode <strong>createUser</strong> (<a href="http://java.sun.com/javase/technologies/database/">JDBC</a>) :</p><pre class="brush: java;">
public User createUser(String userId, String firstName, String lastName) throws DAOException {
    try {
        PreparedStatement ps = getConnection().prepareStatement("INSERT INTO ...");
        ps.setString(1, userId);
        ps.setString(2, firstName);
        ps.setString(3, lastName);
        ps.executeUpdate();
        ps.close();
        return new User(userId, firstName, lastName);
    } catch (SQLException e) {
        throw new DAOException(e.getMessage());
    }
}
</pre><p>On voit rapidement que le premier code est directement dépendant du DriverManager, en gérant lui-même la récupération de la connexion courante. Dans le deuxième cas, on injecte la connexion dans le DAO, c&#8217;est à dire plus de <strong>dépendance directe</strong>, et beaucoup plus facile à tester. Pour mieux découpler, un bon <a href="http://fr.wikipedia.org/wiki/Framework">Framework</a> d&#8217;<a href="http://fr.wikipedia.org/wiki/Inversion_de_contr%C3%B4le">injection de dépendance</a> vous aidera à rendre vos classes autonomes.</p><h2>Les différentes approches</h2><h3>L&#8217;approche par les Mocks / Fakes</h3><p>Bien que très pratiques, les <a href="http://www.chosesafaire.fr/2009/10/mocks-stubs-leurres-des-tests/">Mocks</a> peuvent ne pas être appropriés pour les tests d&#8217;accès à la base, certains les <a href="http://www.xaprb.com/blog/2008/08/19/how-to-unit-test-code-that-interacts-with-a-database/">déconseillent même vivement</a>.</p><p>Les avantages sont une <strong>vitesse d&#8217;exécution</strong> bien supérieure, en plus de pouvoir tester aisément les cas les plus extrêmes en forçant les exceptions. En contrepartie, cela représente beaucoup de code à écrire (tous les objets factices à implémenter), et de plus si la structure des tables venait à changer, les tests pourraient ne pas échouer alors qu&#8217;ils ne seraient plus adaptés sémantiquement.</p><p>En fonction de la testabilité de votre code, l&#8217;écriture des Mocks peut s&#8217;avérer très compliquée. Si votre code viole la <a href="http://fr.wikipedia.org/wiki/Loi_de_D%C3%A9m%C3%A9ter">loi de Déméter</a>, vous aurez à créer des Mocks / Fakes à la chaîne ; par exemple dans l&#8217;exemple précédent (qui est un mauvais exemple !), la méthode <strong>createUser</strong>, l&#8217;appel à <strong>getConnection()</strong> n&#8217;est utilisé que pour instancier un <strong>PreparedStatement</strong>&#8230;</p><pre class="brush: java;">
final MockConnection connection = new MockConnection();
expect(mock.prepareStatement("INSERT..."));
...

final UserDAO dao = new UserDAOImpl(connection);
dao.createUser(...);
...
</pre><h3>L&#8217;approche &laquo;&nbsp;Sandbox&nbsp;&raquo;</h3><p>Cette approche très différente consiste à premièrement, utiliser une base de test différente de la base de production. Les avantages sont évidents, mais les défauts aussi : <strong>très lourd à maintenir</strong>, il faut rétablir l&#8217;état initial de la base entre chaque test pour garder leur indépendance, ce qui rend l&#8217;exécution d&#8217;une suite de tests très lente (par expérience, il est pénible d&#8217;attendre 10 &#8211; 15 minutes pour avoir les résultats d&#8217;une suite de tests).</p><p>Une alternative à cette méthode est de travailler <a href="http://xunitpatterns.com/Transaction%20Rollback%20Teardown.html">en mode transactionnel</a> et de faire un <a href="http://fr.wikipedia.org/wiki/Rollback_%28base_de_donn%C3%A9es%29">Rollback</a> après chaque test, pour que les données n&#8217;y persistent pas ; je trouve l&#8217;idée intéressante, mais il faut faire attention car si le test lance une erreur imprévue et que le Rollback n&#8217;est pas exécuté, il faussera la suite des tests en semant la zizanie au sein des données&#8230;</p><p>Enfin, toujours dans l&#8217;idée de rendre les tests indépendants du moment et du lieu d&#8217;exécution, cette approche utilise fréquemment le concept de « Dataset » (ou <a href="http://en.wikipedia.org/wiki/Test_fixture">Fixtures</a>). Selon l&#8217;outil utilisé, la façon de charger les données de test sera différente mais l&#8217;idée reste la même.</p><p>Voici une liste d&#8217;outils dont le but est de faciliter ce type de tests :</p><h4><a href="http://dbunit.sourceforge.net/">DbUnit</a> (Java)</h4><p>Le Framework de référence (où en tout cas, celui sur lequel les autres se basent).</p><p>Il fonctionne sur un principe de Dataset XML. Deux types de Dataset existent, le <strong>XmlDataSet</strong> et le <strong>FlatXmlDataSet</strong>, respectivement :</p><pre class="brush: xml;">
&lt;!DOCTYPE dataset SYSTEM "dataset.dtd"&gt;
&lt;dataset&gt;
    &lt;table name="USERS"&gt;
        &lt;column&gt;USER_ID&lt;/column&gt;
        &lt;column&gt;FIRST_NAME&lt;/column&gt;
        &lt;column&gt;LAST_NAME&lt;/column&gt;
        &lt;row&gt;
            &lt;value&gt;1&lt;/value&gt;
            &lt;value&gt;Foo&lt;/value&gt;
            &lt;value&gt;Bar&lt;/value&gt;
        &lt;/row&gt;
        &lt;row&gt;
            ...
        &lt;/row&gt;
    &lt;/table&gt;
&lt;/dataset&gt;
</pre><p>et :</p><pre class="brush: xml;">
&lt;!DOCTYPE dataset SYSTEM "users-dataset.dtd"&gt;
&lt;dataset&gt;
    &lt;USERS USER_ID="1" FIRST_NAME="Foo" LAST_NAME="Bar" /&gt;
    &lt;USERS ... /&gt;
&lt;/dataset&gt;
</pre><p>Le XmlDataSet est ainsi bien plus verbeux mais plus généraliste, alors que le FlatXmlDataSet est plus simple à écrire mais peut être plus fastidieux à mettre en place (il faut créer le <a href="http://fr.wikipedia.org/wiki/Document_Type_Definition">DTD</a> de son Dataset).</p><p>Voici un exemple basique qui montre une des opérations disponibles :</p><pre class="brush: java;">
public class SampleTest extends DBTestCase {

    public SampleTest(String name) {
        super(name);
        // ... configuration de la connection
    }

    protected DatabaseOperation getSetUpOperation() throws Exception {
        // On rafraîchit la base avant chaque test
        return DatabaseOperation.REFRESH;
    }

    protected DatabaseOperation getTearDownOperation() throws Exception {
        // On ne fait rien après un test
        return DatabaseOperation.NONE;
    }

    public void testAjoutMembre() {
        UserDAO dao = ...;
        dao.createUser(...)

        // On récupère l'état courant de la table sous forme de dataset
        IDataSet databaseDataSet = getConnection().createDataSet();
        ITable actualTable = databaseDataSet.getTable("USERS");

        // On le compare à un dataset XML
        IDataSet expectedDataSet = new FlatXmlDataSet(new File("expectedDataSet.xml"));
        ITable expectedTable = expectedDataSet.getTable("USERS");

        Assertion.assertEquals(expectedTable, actualTable);
    }
}
</pre><h4>DbUnit et Spring (Java)</h4><p><a href="http://www.springsource.org/about">Spring</a>, l&#8217;ingénieux Framework d&#8217;Inversion de Contrôle (et de plein d&#8217;autres choses !), permet de simplifier l&#8217;utilisation de DbUnit. Spring et les annotations rendent conjointement le test vraiment agréable à écrire, jugez-en par vous même.</p><p>Voici le fichier <strong>applicationContext.xml</strong> avec la configuration de la <a href="http://java.sun.com/j2se/1.4.2/docs/api/javax/sql/DataSource.html">DataSource</a> :</p><pre class="brush: xml;">
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd"&gt;

&lt;beans&gt;
    &lt;bean id="dataSource" destroy-method="close"&gt;
        &lt;property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" /&gt;
        &lt;property name="url" value="jdbc:oracle:thin:@10.0.0.2:1521:orcl" /&gt;
        &lt;property name="username" value="your_user_name" /&gt;
        &lt;property name="password" value="your_password" /&gt;
    &lt;/bean&gt;
&lt;/beans&gt;
</pre><p>Et le test unitaire :</p><pre class="brush: java;">
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:applicationContext.xml"})
public class SampleTestWithSpring {

    @Autowired
    private DataSource dataSource;

    @Before
    public void init() throws Exception {
        // Avant le test on insère le dataSet
        DatabaseOperation.CLEAN_INSERT.execute(dataSource.getConnection(), getDataSet());
    }

    @After
    public void after() throws Exception {
        // Après le test on supprime le dataSet
        DatabaseOperation.DELETE_ALL.execute(dataSource.getConnection(), getDataSet());
    }

    private IDataSet getDataSet() throws Exception {
        return new FlatXmlDataSet(new File("src/test/resources/dataset.xml"));
    }

    @Test
    public void testXXX() {
        ...
    }
}
</pre><p>On pourra ajouter que Spring contient à lui seul <a href="http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ch09s03.html">un module pour les tests d&#8217;intégration</a>.</p><h4><a href="http://unitils.sourceforge.net/tutorial.html">Unitils</a> (Java)</h4><p>Déjà évoqué concernant l&#8217;utilisation des Mocks, Unitils permet de simplifier les tests d&#8217;accès à la base de données.</p><pre class="brush: java;">
public class UserDAOTest extends UnitilsJUnit4 {

    @Test
    @DataSet("UserDAOTest.testFindByMinimalAge.xml")
    public void testFindByMinimalAge() {
        List&lt;User&gt; result = userDao.findByMinimalAge(18);
        assertPropertyLenientEquals("firstName", Arrays.asList("jack"), result);
    }
}
</pre><p>Le fichier dataset est un fichier XML similaire à ceux utilisés par DbUnit. J&#8217;avoue préférer la syntaxe d&#8217;Unitils à celle de DbUnit, car l&#8217;utilisation des annotations (par exemple <strong>@ExpectedDataSet</strong> ou <strong>@Transactional(TransactionMode.ROLLBACK)</strong>) combinée à Spring rendent ce framework extrêment flexible.</p><h4><a href="http://www.digitalsandwich.com/archives/63-PHPUnit-Database-Extension-DBUnit-Port.html">PHPUnit Database Extension</a> (PHP)</h4><p>Cette extension de PHPUnit est en fait un portage de DbUnit en PHP. Il utilise ainsi le même principe des dataset.xml :</p><pre class="brush: php;">
require_once 'PHPUnit/Extensions/Database/TestCase.php';

class BankAccountDBTest extends PHPUnit_Extensions_Database_TestCase {

    protected $pdo;

    public function __construct() {
        $this-&gt;pdo = new PDO('sqlite::memory:');
    }

    protected function getConnection() {
        return $this-&gt;createDefaultDBConnection($this-&gt;pdo, 'sqlite');
    }

    protected function getSetUpOperation() {
        return $this-&gt;getOperations()-&gt;CLEAN_INSERT();
    }

    protected function getTearDownOperation() {
        return $this-&gt;getOperations()-&gt;DELETE_ALL();
    }

    protected function getDataSet() {
        return $this-&gt;createFlatXMLDataSet(dirname(__FILE__).'/_files/users-dataset.xml');
    }

    public function testNewUserCreation() {
        $user = new User(...);
        // ... enregistrement ...

        $expected_dataset = $this-&gt;createFlatXMLDataSet(dirname(__FILE__).'/_files/expected-users-dataset.xml');
        $this-&gt;assertDataSetsEqual($xml_dataset, $this-&gt;getConnection()-&gt;createDataSet());
    }
}
</pre><h4><a href="http://www.doctrine-project.org/documentation/manual/1_2/en/unit-testing">Doctrine ORM</a> (PHP)</h4><p>Ayant pas mal utilisé cet outil dans divers projets PHP, j&#8217;ai cherché par curiosité s&#8217;il proposait une façon de gérer les tests unitaires. Effectivement, il propose quelques raccourcis via la classe <strong>Doctrine_UnitTestCase</strong>, mais sans entrer dans le détail j&#8217;ai l&#8217;impression que les possibilités sont malheureusement moins grandes qu&#8217;avec PHPUnit.</p><pre class="brush: php;">
class Doctrine_Sample_TestCase extends Doctrine_UnitTestCase {

    public function prepareTables() {
        $this-&gt;tables[] = "Users";
        parent::prepareTables();
    }

    public function prepareData() {
        // Peut-être des fixtures peuvent être chargées ici ?
    }

    public function testNewUserCreation() {
        $this-&gt;assertTrue(...);
    }
}
</pre><p>Un point intéressant proposé par Doctrine est la présence des <strong>Mock Drivers</strong>, qui permettent de simuler le comportement des tests sur un SGBD virtuel (si vous voulez par exemple tester la génération des requêtes pour Oracle) :</p><pre class="brush: php;">
class Doctrine_Sample_TestCase extends Doctrine_UnitTestCase {

    public function testInit() {
        $this-&gt;dbh = new Doctrine_Adapter_Mock('oracle');
        $this-&gt;conn = Doctrine_Manager::getInstance()-&gt;openConnection($this-&gt;dbh);
    }

    public function testMockDriver() {
        $user = new User();
        $user-&gt;username = 'jwage';
        $user-&gt;password = 'changeme';
        $user-&gt;save();

        $sql = $this-&gt;dbh-&gt;getAll();

        $this-&gt;assertEqual($sql[0], 'INSERT INTO user (username, password) VALUES (?, ?)');
    }
}
</pre><h4><a href="http://code.google.com/p/ndbunit/">NDbUnit</a> (.Net)</h4><p>Un projet pour <a href="http://www.microsoft.com/NET/">.Net</a> sous licence <a href="http://www.gnu.org/licenses/lgpl.html">LGPL</a> écrit en C#. Le fonctionnement est assez similaire à DbUnit ; d&#8217;après la documentation, on peut facilement le combiner à <a href="http://code.google.com/p/proteusproject/">Proteus</a> afin de simplifier l&#8217;écriture du code et d&#8217;éviter les redondances.</p><p>Voici un exemple de Dataset (UsersDataSet.xml) :</p><pre class="brush: xml;">
&lt;?xml version="1.0" encoding="utf-8" ?&gt;
&lt;MyDataset xmlns="http://tempuri.org/UsersDataSet.xsd"&gt;
    &lt;Customer&gt;
        &lt;CustomerId&gt;1&lt;/CustomerId&gt;
        &lt;Firstname&gt;John&lt;/Firstname&gt;
        &lt;Lastname&gt;Doe&lt;/Lastname&gt;
    &lt;/Customer&gt;
    &lt;Customer&gt;
        &lt;CustomerId&gt;2&lt;/CustomerId&gt;
        &lt;Firstname&gt;Sam&lt;/Firstname&gt;
        &lt;Lastname&gt;Smith&lt;/Lastname&gt;
    &lt;/Customer&gt;
&lt;/MyDataset&gt;
</pre><p>Et un exemple de test :</p><pre class="brush: csharp;">
[TestFixture]
public class Tests
{
    private string _connectionString;
    private NDbUnit.Core.INDbUnitTest _mySqlDatabase;

    [SetUp]
    public void _Setup()
    {
        _mySqlDatabase.PerformDbOperation(NDbUnit.Core.DbOperationFlag.CleanInsertIdentity);
    }

    [FixtureSetUp]
    public void _TestFixtureSetup()
    {
        _connectionString = "server=localhost;user=dbuser;password=dbpassword;initial catalog=MyDatabase;";
        _mySqlDatabase = new NDbUnit.Core.SqlClient.SqlDbUnitTest(_connectionString);

        _mySqlDatabase.ReadXmlSchema(@"..\..\UsersDataSet.xsd");
        _mySqlDatabase.ReadXml(@"..\..\UsersDataSet.xml");
    }

    [FixtureTearDown]
    public void _TestFixtureTearDown()
    {
        _mySqlDatabase.PerformDbOperation(NDbUnit.Core.DbOperationFlag.DeleteAll);
    }

    [Test]
    public void Test()
    {
        CustomerRepository repository = new CustomerRepository();
        Assert.AreEqual(2, repository.GetAllCustomers().Count);
    }
}
</pre><h4><a href="http://guides.rubyonrails.org/testing.html">Ruby On Rails</a> (Ruby)</h4><p>Si vous êtes développeur Ruby et utilisez le framework Rails, sachez que ce type de tests est prévu par l&#8217;outil : un mécanisme de <a href="http://ar.rubyonrails.org/classes/Fixtures.html">fixtures YAML ou CSV</a> est inclus dans le projet, qui combiné au moteur de tests unitaires par défaut (<strong>ActiveSupport::TestCase</strong>) se révèle très complet.</p><p>De plus, un ensemble d&#8217;assertions vous permettra de tester rapidement vos modèles (ainsi que d&#8217;effectuer rapidement des tests d&#8217;intégration et fonctionnels, mais c&#8217;est un autre sujet !).</p><p>Le Framework peut travailler avec d&#8217;autres outils comme <a href="http://avdi.org/projects/nulldb/">NullDB</a>, qui permet d&#8217;éviter l&#8217;utilisation d&#8217;une base de données, ou d&#8217;autres méchanismes en remplacement des fixtures (Factory Girl, Machinist).</p><p>Voici un exemple de dataset (users.yml) :</p><pre class="brush: text;">
david:
    name: David Heinemeier Hansson
    birthday: 1979-10-15
    profession: Systems development

steve:
    name: Steve Ross Kellock
    birthday: 1974-09-27
    profession: guy with keyboard
</pre><p>Ces fixtures peuvent être rendus dynamiques à l&#8217;aide d&#8217;ERb.</p><pre class="brush: ruby">
require 'test_helper'

class PostTest &lt; ActiveSupport::TestCase
    def test_user
        david = users(:david).find
        assert_equals("Systems development", david.profession)
    end
end
</pre><p>C&#8217;est un test très basique mais je pense qu&#8217;on peut pousser bien plus loin à l&#8217;aide du Framework.</p><h4><a href="http://docs.djangoproject.com/en/dev/topics/testing/#topics-testing">Django</a> (Python)</h4><p>Le puissant Framework Python propose aussi tout l&#8217;outillage nécessaire pour tester votre application. En plus des <a href="http://djangoapi.quamquam.org/trunk/django.test.testcases.TestCase-class.html">TestCase</a> habituels, vous pourrez créer des <strong>TransactionTestCase</strong>, qui réinitialisera l&#8217;état de la base (de test) avant le lancement des tests en chargeant les données initiales.</p><p>De plus, des fixtures peuvent aussi être utilisées, au format JSON ou YAML (ici users.json) :</p><pre class="brush: js">
[
    {
        "model": "myapp.models.User",
        "pk": 1,
        "fields": {
            "first_name": "John",
            "last_name": "Lennon"
        }
    },
    {
        "model": "myapp.models.User",
        "pk": 2,
        "fields": {
            "first_name": "Paul",
            "last_name": "McCartney"
        }
    }
]
</pre><p>Le dataset peut de même être un fichier <a href="http://fr.wikipedia.org/wiki/Structured_Query_Language">SQL</a>, même si cela rend vos tests un peu moins indépendants du SGBD utilisé :</p><pre class="brush: sql">
INSERT INTO myapp_user (first_name, last_name) VALUES ('John', 'Lennon');
INSERT INTO myapp_user (first_name, last_name) VALUES ('Paul', 'McCartney');
</pre><p>Et voici un exemple de classe de test utilisant ces fixtures :</p><pre class="brush: python;">
from django.test import TestCase
from myapp.models import User

class UsersTestCase(TestCase):
    fixtures = ['users.json']

    def testUsers(self):
        john = User.objects.get(pk=1)
        self.assertEquals("John", john.first_name)
</pre><h4>Et les <a href="http://xunitpatterns.com/Stored%20Procedure%20Test.html">procédures stockées</a> ?</h4><p>Dans beaucoup d&#8217;applications, procédures stockées et triggers sont utilisés pour automatiser certaines tâches. Il existe deux méthodes pour les tester : localement, via une autre procédure de test, ou à distance, en appelant la procédure avec votre langage par défaut (Java, Ruby&#8230;).</p><p>Voici un exemple de procédure stockée à tester :</p><pre class="brush: sql;">
CREATE OR REPLACE PROCEDURE add (
    a IN NUMBER,
    b IN NUMBER,
    result OUT NUMBER
)
IS
BEGIN
    result := a + b;
END;
/
</pre><p>Pour écrire un test sous forme de procédure stockée, il existe l&#8217;outil <a href="http://utplsql.sourceforge.net/">utPLQSL</a> pour Oracle, mais il n&#8217;a plus l&#8217;air maintenu depuis quelques années. Je n&#8217;ai pas trouvé d&#8217;outil similaire pour les autres SGBD, si ce n&#8217;est <a href="http://sqlunit.sourceforge.net/">SQLUnit</a>, mais je ne l&#8217;ai pas étudié en détail.</p><pre class="brush: sql;">
CREATE OR REPLACE PACKAGE BODY ut_myapp
IS
    PROCEDURE ut_setup
    IS
    BEGIN
        NULL;
    END;

    PROCEDURE ut_teardown
    IS
    BEGIN
        NULL;
    END;

    PROCEDURE ut_ADD
    IS
        result NUMBER;
    BEGIN
        ADD ( A =&gt; 3,  B =&gt; 2, RESULT =&gt; result );
        utAssert.eq ( 5, result );
    END ut_ADD;

END ut_myapp;
/
</pre><p><strong>Les tests unitaires rattachés aux bases de données permettent d&#8217;assurer un peu plus la stabilité de l&#8217;application et surtout l&#8217;intégrité de ses données. Utilisez-vous un de ces outils ou un outil similaire, et avez-vous rencontré des problèmes lors de sa mise en place ? Quels seraient les conseils à donner à tous les développeurs pour améliorer l&#8217;écriture de leur code / tests ?</strong></p><h3>Quelques références</h3><ul><li><a href="http://www.dallaway.com/acad/dbunit.html">Les notes de Richard Dallaway sur le &laquo;&nbsp;Database unit testing&nbsp;&raquo;</a></li><li><a href="http://xunitpatterns.com/Database%20Sandbox.html">Database sandbox</a></li><li><a href="http://www.javaranch.com/journal/2003/12/UnitTestingDatabaseCode.html">Comment tester du code accédant à une base de données ?</a></li><li><a href="http://onjava.com/pub/a/onjava/2004/01/21/dbunit.html">Tester efficacement avec DbUnit</a></li><li><a href="http://www.theserverside.com/tt/articles/article.tss?l=ManageTestDataSpringandDBunit">Utiliser Spring et DbUnit</a></li><li><a href="http://www.theserverlabs.com/blog/2009/05/18/continuous-integration-with-oracle-plsql-utplsql-and-hudson/">Intégration continue avec Hudson, Oracle et utPLQSL</a></li></ul><div class="sexy-bookmarks sexy-bookmarks-center"><ul class="socials"><li class="sexy-blogmarks"> <a href="http://blogmarks.net/my/new.php?mini=1&amp;simple=1&amp;url=http://www.chosesafaire.fr/2009/12/applications-et-bases-de-donnees-comment-tester/&amp;title=Applications+et+bases+de+donn%C3%A9es%2C+comment+tester+%3F" rel="nofollow" title="Marquez-le sur BlogMarks">Marquez-le sur BlogMarks</a></li><li class="sexy-google"> <a href="http://www.google.com/bookmarks/mark?op=add&amp;bkmk=http://www.chosesafaire.fr/2009/12/applications-et-bases-de-donnees-comment-tester/&amp;title=Applications+et+bases+de+donn%C3%A9es%2C+comment+tester+%3F" rel="nofollow" title="Ajoutez-le à Google Bookmarks">Ajoutez-le à Google Bookmarks</a></li><li class="sexy-delicious"> <a href="http://del.icio.us/post?url=http://www.chosesafaire.fr/2009/12/applications-et-bases-de-donnees-comment-tester/&amp;title=Applications+et+bases+de+donn%C3%A9es%2C+comment+tester+%3F" rel="nofollow" title="Partagez-le sur del.icio.us">Partagez-le sur del.icio.us</a></li><li class="sexy-reddit"> <a href="http://reddit.com/submit?url=http://www.chosesafaire.fr/2009/12/applications-et-bases-de-donnees-comment-tester/&amp;title=Applications+et+bases+de+donn%C3%A9es%2C+comment+tester+%3F" rel="nofollow" title="Partagez-le sur Reddit">Partagez-le sur Reddit</a></li><li class="sexy-stumbleupon"> <a href="http://www.stumbleupon.com/submit?url=http://www.chosesafaire.fr/2009/12/applications-et-bases-de-donnees-comment-tester/&amp;title=Applications+et+bases+de+donn%C3%A9es%2C+comment+tester+%3F" rel="nofollow" title="Tomber sur un bon truc ? Partagez cet article sur StumbleUpon">Tomber sur un bon truc ? Partagez cet article sur StumbleUpon</a></li><li class="sexy-technorati"> <a href="http://technorati.com/faves?add=http://www.chosesafaire.fr/2009/12/applications-et-bases-de-donnees-comment-tester/" rel="nofollow" title="Partagez-le sur Technorati">Partagez-le sur Technorati</a></li><li class="sexy-twitter"> <a href="http://twitter.com/home?status=Applications+et+bases+de+donn%C3%A9es%2C+comment+tester+%3F+-+http://tinyurl.com/ybposst+(via+@dandelionmood)" rel="nofollow" title="Tweetez-le !">Tweetez-le !</a></li><li class="sexy-comfeed"> <a href="http://www.chosesafaire.fr/2009/12/applications-et-bases-de-donnees-comment-tester/feed" rel="nofollow" title="S'abonner aux commentaires de cet article ?">S'abonner aux commentaires de cet article ?</a></li></ul><div style="clear:both;"></div></div><p>Articles liés :</p><ol><li><a href='http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/' rel='bookmark' title='Permanent Link: Mocks et Multithreading'>Mocks et Multithreading</a></li><li><a href='http://www.chosesafaire.fr/2009/12/le-probleme-du-singleton/' rel='bookmark' title='Permanent Link: Le problème du Singleton'>Le problème du Singleton</a></li></ol></p>]]></content:encoded> <wfw:commentRss>http://www.chosesafaire.fr/2009/12/applications-et-bases-de-donnees-comment-tester/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>La couche Modèle de Rails (Partie 2)</title><link>http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/</link> <comments>http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/#comments</comments> <pubDate>Sat, 12 Dec 2009 21:38:02 +0000</pubDate> <dc:creator>Pierre Quillery</dc:creator> <category><![CDATA[Développement]]></category> <category><![CDATA[activerecord]]></category> <category><![CDATA[modèle]]></category> <category><![CDATA[rails]]></category><guid isPermaLink="false">http://www.chosesafaire.fr/?p=235</guid> <description><![CDATA[Aujourd'hui, nous expérimentons l'implémentation de la couche Modèle de Rails, à l'aide la console : au programme, la configuration d'une base de données MySQL, la découverte des opérations de CRUD basiques, ainsi que la mise en place de relations entre les objets métier.Articles liés :<ol><li><a href='http://www.chosesafaire.fr/2010/01/la-couche-modele-de-rails-partie-3/' rel='bookmark' title='Permanent Link: La couche Modèle de Rails (Partie 3)'>La couche Modèle de Rails (Partie 3)</a></li><li><a href='http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/' rel='bookmark' title='Permanent Link: La couche Modèle de Rails (Partie 1)'>La couche Modèle de Rails (Partie 1)</a></li></ol>]]></description> <content:encoded><![CDATA[<p>Voici la suite du premier article qui parlait de la couche Modèle dans Rails, n&#8217;hésitez pas à <a href="http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/">aller y jeter un œil</a> si vous ne l&#8217;avez pas encore lu ! Ce post fait partie d&#8217;une série de tutoriels dont vous pouvez trouver <a href="http://dandelionmood.com/Ruby-on-Rails-lancez-vous.html">la liste sur mon ancien site</a>.</p><p><span id="more-235"></span></p><h2>Configuration des base de données dans Rails</h2><p>Comme vous l&#8217;avez sans doute remarqué dans le précédent article, nous avons directement commencé à travailler avec la base de données sans pour autant avoir eu besoin de configurer quoi que ce soit au niveau de l&#8217;application. En effet, <a href="http://dandelionmood.com/Installation-de-Ruby-on-Rails-sous.html">ainsi que je le décrivais déjà ici</a>, Rails se repose par défaut sur une base Sqlite, ce qui permet de s&#8217;affranchir de configuration et rentrer directement dans le vif du sujet.</p><p>La configuration concernant les bases de données est placée dans le fichier <code>config/database.yml</code>. Comme vous pouvez le voir, on y trouve 3 zones qui correspondent à la configuration de chaque environnement (<code>dev</code>, <code>preprod</code> et <code>prod</code>). <strong>L&#8217;intérêt de ce fonctionnement est qu&#8217;il est très souple à l&#8217;usage : pour développer vous aurez rarement besoin d&#8217;une base MySql, alors qu&#8217;en production, c&#8217;est une nécessité.</strong></p><pre class="brush: yaml;">
# SQLite version 3.x
#   gem install sqlite3-ruby (not necessary on OS X Leopard)
development:
  adapter: sqlite3
  # Emplacement du fichier où se trouve la base
  database: db/development.sqlite3
  # Nombre de connexion dans le pool
  pool: 5
  timeout: 5000

# ...
</pre><p>Notons que MySQL n&#8217;est pas le seul <a href="http://wiki.rubyonrails.org/start">SGBD supporté par Rails</a> : il en est de même pour PostgreSQL, DB2, SQL Server, Oracle, Sybase, Firebird, etc.</p><p>Prenons l&#8217;exemple de MySQL, et changeons la configuration pour nous connecter à une base MySQL correspondant à notre application en production :</p><pre class="brush: yaml;">
# ...

production:
  adapter: mysql
  # Encodage du client
  encoding: utf8
  # Nom de la base à laquelle se connecter
  database: monapplication_prod
  pool: 5
  username: monnomdutilisateur
  password: monmotdepasse
  # Si la base se trouve sur la machine locale ...
  socket: /var/run/mysqld/mysql.sock
  # ... Ou si la base se trouve ailleurs
  host: serveurbdd.monapplication.com
</pre><h3>Interaction avec les bases de données avec la ligne de commande</h3><p>Nous avons vu dans le précédent article comment utiliser les migrations pour interagir avec les bases de données via la ligne de commande Rails. Voici quelques autres commandes qui vous permettront de tester tout ça à loisir :</p><ul><li><code>rake db:create</code> → Créer la base de donnée</li><li><code>rake db:destroy</code> → Détruire la base de données</li></ul><p>Notez que ces commandes, comme toutes les opérations en ligne de commande de Rails, sont sensibles au contexte pour le choix de l&#8217;environnement à affecter : par défaut, vous travaillez avec l&#8217;environnement de développement. Pour changer ce comportement, il vous faut changer la valeur de la variable d&#8217;environnement <code>RAILS_ENV</code> en lui donnant le nom de l&#8217;environnement de votre choix.</p><p>Ainsi vous écrirez par exemple la commande suivante pour supprimer le contenu de la base de production :</p><pre class="brush: bash;">
RAILS_ENV=production rake db:destroy
</pre><h2>Sélection, Modification et Suppression d&#8217;élément</h2><h3>Utilisation de la console Rails</h3><p>Ruby on Rails intègre une console interactive qui est un lieu d&#8217;expérimentation privilégié : vous pouvez directement créer des objets persistants en base, tester un bout de code, une fonction, etc. Nous allons l&#8217;utiliser pour la suite du tutorial, car cela nous évitera d&#8217;avoir à nous soucier des autres couches. Pour la démarrer, rien de plus simple :</p><pre class="brush: bash;">
script/console
</pre><p>Vous entrez maintenant dans la console interactive, tapez <code>quit</code> pour en sortir à tout moment ! Notez enfin que l&#8217;invite de commande commence toujours par les caractères <code>>></code> alors que les retours de ruby sont préfixés d&#8217;une flèche <code>=></code>.</p><h3>Mise en place de notre projet de travail</h3><p>Voici quelques commandes qui vous nous permettre de mettre en place un projet pour tester un peu les possibilités offertes en ce qui concerne les opérations simples de CRUD sur lesquelles nous allons nous attarder aujourd&#8217;hui.</p><pre class="brush: bash;">
rails appli_de_test
cd appli_de_test
script/generate model Utilisateur login:string mot_de_passe:string
script/generate model Article titre:string contenu:text utilisateur_id:integer
script/generate model Tag libelle:string --skip-timestamps
script/generate model Taggable tag_id:integer article_id:integer --skip-timestamps
</pre><p><strong>Nous allons créer, comme vous l&#8217;avez compris un blog minimaliste : un Utilisateur possède des Articles. Les Articles partagent un ou plusieurs Tags via la relation Taggable.</strong></p><p>Il ne nous reste plus qu&#8217;à éditer les fichiers de classes correspondants que vous trouverez dans le répertoire <code>app/models</code> pour exprimer explicitement les relations qui lient ces éléments entre eux :</p><pre class="brush: ruby;">
# app/models/utilisateur.rb
class Utilisateur &lt; ActiveRecord::Base
  has_many :articles
end

# app/models/article.rb
class Article &lt; ActiveRecord::Base
  belongs_to :utilisateur
  has_many :taggables
  has_many :tags, :through => :taggables
end

# app/models/taggable.rb
class Taggable &lt; ActiveRecord::Base
  belongs_to :article
  belongs_to :tag
end

# app/models/tag.rb
class Tag &lt; ActiveRecord::Base
  has_many :taggables
  has_many :articles, :through => :taggables

  # Ce champ est obligatoire, on force donc sa présence
  validates_presence_of :libelle
end
</pre><p>Maintenant, il ne nous reste plus qu&#8217;à mettre en place la structure de la base de données, à l&#8217;aide de la commande <code>rake db:migrate</code> puis à nous connecter à la console de Rails pour commencer nos essais : <code>script/console</code></p><h3>Création d&#8217;élément</h3><p>Précisons en guise de préambule que cet aperçu des possibilités qui vous sont offertes par Rails et Active Record (classe qui s&#8217;occupe spécifiquement de la persistence des données en base) est vraiment très superficiel ; je ne saurais trop vous conseiller de vous référer à <a href="http://rails.rubyonrails.org/classes/ActiveRecord/Base.html">la documentation d&#8217;Active Record</a> pour apprendre réellement à vous en servir <img src='http://www.chosesafaire.fr/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> .</p><p>Nous allons tout d&#8217;abord créer des Tags à l&#8217;aide de la méthode <code><a href="http://rails.rubyonrails.org/classes/ActiveRecord/Base.html#M002269">create()</a></code>. Cette méthode va instancier un objet Tag et créer directement l&#8217;enregistrement en base (si la validation s&#8217;est bien déroulée).</p><pre class="brush: ruby;">
>> t = Tag.create
=> #&lt;Tag id: nil, libelle: nil>
</pre><p>Ah, nous remarquons le Tag n&#8217;a pas d&#8217;id (<code>nil</code> signifie <code>null</code> en PHP) ; cela signifie qu&#8217;il n&#8217;est pas présent en base &#8211; or il devrait l&#8217;être, puisque c&#8217;est ce que nous attendons de la commande <code>create()</code> &#8230;</p><p>Une erreur s&#8217;est produite à cause de la validation que nous avons ajouté dans la classe Tag : j&#8217;ai volontairement omis de préciser le champ <code>libelle</code> et l&#8217;enregistrement n&#8217;a pu être effectué. C&#8217;est ce mécanisme qui nous permettra de faciliter la validation des formulaires, ainsi que nous le verrons dans un prochain article.</p><p>Rails nous indique en fait dans la variable <code>errors</code> de l&#8217;objet <code>t</code> la liste des erreurs qui se sont produites et qui empêchent la validation et donc l&#8217;enregistrement de l&#8217;élément :</p><pre class="brush: ruby;">
>> t = Tag.create
=> #&lt;Tag id: nil, libelle: nil>
>> t.errors
=> #&lt;ActiveRecord::Errors:0x7fd366f44020 @errors=#&lt;OrderedHash
  {"libelle"=>[#&lt;ActiveRecord::Error:0x7fd366f42f90 @attribute=:libelle,
  @type=:blank, @options={}, @message=:blank, @base=#&lt;Tag id: nil,
  libelle: nil>>]}>, @base=#&lt;Tag id: nil, libelle: nil>>
</pre><p>Le message n&#8217;est pas forcément très lisible présenté comme ça, mais nous apprenons ainsi qu&#8217;il s&#8217;agit d&#8217;une erreur qui concerne le champ <code>libelle</code> et qu&#8217;elle a le type <code>:blank</code>. Le mécanisme de validation de formulaire de Rails rendra ce tableau très facile à exploiter pour nous par la suite, même s&#8217;il n&#8217;a pas l&#8217;air très avenant vu sous cet angle <img src='http://www.chosesafaire.fr/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> !</p><p>Essayons maintenant de reprendre la saisie des Tags en évitant de provoquer des erreurs :</p><pre class="brush: ruby;">
>> Tag.create :libelle => "Rock"
=> #&lt;Tag id: 1, libelle: "Rock">
>> Tag.create :libelle => "Folk"
=> #&lt;Tag id: 2, libelle: "Folk">
>> Tag.create :libelle => "Soul"
=> #&lt;Tag id: 3, libelle: "Soul">
>> Tag.create :libelle => "Funk"
=> #&lt;Tag id: 4, libelle: "Funk">
</pre><p><strong>Ce coup-ci, il est clair que tout se passe bien : les enregistrements se voient directement attribuer un id numérique, ce qui signifie qu&#8217;ils sont maintenant présents en base.</strong></p><h3>Sélection d&#8217;éléments</h3><p>Maintenant que la base est peuplée de quelques éléments, nous pouvons nous intéresser aux méthodes qui nous permettent de retrouver ces mêmes éléments. Tout passe à vrai dire par la méthode de classe <code>find</code> qui est disponible pour chaque classe de modèle.</p><p>Ainsi, pour sélectionner une collection d&#8217;éléments, nous utilisons la variante <code>find :all</code>, en voici un exemple :</p><pre class="brush: ruby;">
>> Tag.find :all
=> [#&lt;Tag id: 1, libelle: "Rock">, #&lt;Tag id: 2, libelle: "Folk">, #&lt;Tag id: 3, libelle: "Soul">, #&lt;Tag id: 4, libelle: "Funk">]
</pre><p>Notez que dans le retour qui est fait, Rails nous renvoie bien <strong>une collection d&#8217;éléments</strong> qu&#8217;il a placé dans un tableau  (<code>[ ... ]</code>) ; comme en Javascript, on emploie en Ruby les crochets pour écrire un tableau.</p><p>Essayons maintenant d&#8217;ajouter une condition à notre sélection &#8211; plusieurs syntaxes sont disponibles, voici celle qui permet de créer une sorte de requête préparée qui prend la forme d&#8217;un tableau. Les <code>?</code> dans la requête SQL sont ainsi remplacés par les valeurs entrées dans les index suivant. Ici nous recherchons par exemple les Tags qui commencent par la lettre F.</p><pre class="brush: ruby;">
>> Tag.find :all, :conditions => [ 'libelle like ?', 'F%' ]
=> [#&lt;Tag id: 2, libelle: "Folk">, #&lt;Tag id: 4, libelle: "Funk">]
</pre><p>Voyons maintenant comment sélectionner un élément unique dans la base ; pour cela, on emploie la version <code>find :first</code> de la méthode :</p><pre class="brush: ruby;">
>> Tag.find :first, 1
=> #&lt;Tag id: 1, libelle: "Rock">
</pre><p>Dans le cas présent, Rails a bien renvoyé un élément unique, et non un tableau, ainsi que c&#8217;était le cas précédemment. On aurait éventuellement pu omettre le <code>:first</code>, c&#8217;est en fait la méthode utilisée par Rails si on ne précise rien.</p><p>Nous allons maintenant voir une autre façon d&#8217;écrire les conditions : plutôt que de passer un tableau, il est également possible de passer un simple hash (<code>{ ... }</code>) &#8211; il s&#8217;agit d&#8217;une sorte de tableau associatif :</p><pre class="brush: ruby;">
>> Tag.find :first, :conditions => { :libelle => 'Rock' }
=> #&lt;Tag id: 1, libelle: "Rock">
</pre><p>Voilà les bases de ce qu&#8217;il est possible de faire pour sélectionner des éléments ; vous trouverez <a href="http://apidock.com/rails/ActiveRecord/Base/find/class ">ici le détail des différentes options</a> supportées par la méthode <code>find</code>, elles sont nombreuses. Vous pouvez également <a href="http://railscasts.com/episodes/15-fun-with-find-conditions">regarder ce screencast</a> qui fait une démonstration des possibilités offertes par cette méthode.</p><h3>Modification d&#8217;un élément</h3><p>Maintenant que vous savez retrouver un élément, il serait utile de savoir le modifier puis le sauvegarder ; voici comment faire en employant assez logiquement la méthode <code>save</code> :</p><pre class="brush: ruby;">
>> t = Tag.find :first, 1
=> #&lt;Tag id: 1, libelle: "Rock">
>> t.libelle = "Rock'n'Roll"
=> "Rock'n'Roll"
>> t.save
=> true
</pre><p>Je sélectionne ainsi l&#8217;élément &laquo;&nbsp;Rock&nbsp;&raquo;, que je désire modifier en &laquo;&nbsp;Rock&#8217;n'Roll&nbsp;&raquo;. Il me suffit de changer la valeur du champ &laquo;&nbsp;libelle&nbsp;&raquo;, puis de sauvegarder. Notez que la méthode <code>save</code> retourne une variable qui va nous permettre de savoir que l&#8217;enregistrement a bien été effectué, s&#8217;il a correctement passé la validation. <a href="http://apidock.com/rails/v2.3.4/ActiveRecord/Base/save">Retrouvez ici la documentation concernant cette méthode</a>.</p><p>Voici un exemple de modification qui va échouer :</p><pre class="brush: ruby;">
>> t = Tag.find 2
=> #&lt;Tag id: 2, libelle: "Folk">
>> t.libelle = ""
=> ""
>> t.save
=> false
>> t.errors
=> #&lt;ActiveRecord::Errors:0x7f9f6d187d60 @errors=#&lt;OrderedHash
  {"libelle"=>[#&lt;ActiveRecord::Error:0x7f9f6d185510 @attribute=:libelle,
  @type=:blank, @options={}, @message=:blank, @base=#&lt;Tag id: 2,
  libelle: "">>]}>, @base=#&lt;Tag id: 2, libelle: "">>
</pre><p>Comme avec la méthode <code>create</code> que nous voyions tout à l&#8217;heure, il est toujours possible d&#8217;accéder aux messages de validation. Il nous est toutefois possible de court-circuiter la validation si nous sommes dans un environnement contrôlé, ce qui permet d&#8217;accélérer le traitement, grâce à la méthode <code>save!</code>.</p><h3>Suppression d&#8217;un élément</h3><p>Dernière partie, et non la moins importante de notre passage en revue du CRUD le plus basique en Rails, la destruction d&#8217;un élément. Voilà comment s&#8217;y prendre, grâce à la méthode <code>destroy</code> :</p><pre class="brush: ruby;">
>> t = Tag.find :first, :conditions => {:id => 1}
=> #&lt;Tag id: 1, libelle: "Rock'n'Roll">
>> t.destroy
=> #&lt;Tag id: 1, libelle: "Rock'n'Roll">
>> Tag.find :first, :conditions => {:id => 1}
=> nil
</pre><p><strong>Il est important de noter que la fonction <code>destroy</code> retourne l&#8217;élément supprimé en cas de succès, ce qui s&#8217;avère souvent très utile.</strong></p><h2>Créons des relations entre les objets</h2><p>Maintenant que nous savons interagir avec nos Modèles, il nous reste à voir comment créer des relations entre eux ; nous allons ainsi créer un Utilisateur et lui ajouter plusieurs Articles, ce qui nous permettra de voir le fonctionnement de la relation 1 à plusieurs. Dans un second temps, nous verrons comment assigner des Tags à un Article donnée en passant par la relation plusieurs à plusieurs.</p><h3>Utiliser la relation Un à Plusieurs</h3><p>Voici quelques commandes maintenant connues qui vont nous permettre de créer quelques enregistrements que nous allons utiliser par la suite.</p><pre class="brush: ruby;">
>> util1 = Utilisateur.create :login => "utilisateur1", :mot_de_passe => "mdp1"
=> #&lt;Utilisateur id: 1, login: "utilisateur1", mot_de_passe: "mdp1",
  created_at: "2009-12-11 17:07:08", updated_at: "2009-12-11 17:07:08">
>> util2 = Utilisateur.create :login => "utilisateur2", :mot_de_passe => "mdp2"
=> #&lt;Utilisateur id: 2, login: "utilisateur2", mot_de_passe: "mdp2",
  created_at: "2009-12-11 17:07:21", updated_at: "2009-12-11 17:07:21">

>> arti1 = Article.create :titre => "Titre de mon premier article",
  :contenu => "Pas de contenu pour le moment"
=> #&lt;Article id: 1, titre: "Titre de mon premier article",
  contenu: "Pas de contenu pour le moment", utilisateur_id: nil,
  created_at: "2009-12-11 17:08:11", updated_at: "2009-12-11 17:08:11">
>> arti2 = Article.create :titre => "Titre de mon second article",
  :contenu => "Pas de contenu pour le moment"
=> #&lt;Article id: 2, titre: "Titre de mon second article",
  contenu: "Pas de contenu pour le moment", utilisateur_id: nil,
  created_at: "2009-12-11 17:08:28", updated_at: "2009-12-11 17:08:28">
>> arti3 = Article.create :titre => "Titre de mon troisième article",
  :contenu => "Pas de contenu pour le moment"
=> #&lt;Article id: 3, titre: "Titre de mon troisième article",
  contenu: "Pas de contenu pour le moment", utilisateur_id: nil,
  created_at: "2009-12-11 17:08:42", updated_at: "2009-12-11 17:08:42">
</pre><p>Comment changer le rédacteur du premier Article pour dire qu&#8217;il est en fait le premier Utilisateur ? Rien de plus simple :</p><pre class="brush: ruby;">
>> arti1.utilisateur = util1
=> #&lt;Utilisateur id: 1, login: "utilisateur1", mot_de_passe: "mdp1",
  created_at: "2009-12-11 17:07:08", updated_at: "2009-12-11 17:07:08">
>> arti1.save
=> true
>> arti1
=> #&lt;Article id: 1, titre: "Titre de mon premier article",
  contenu: "Pas de contenu pour le moment", utilisateur_id: 1,
  created_at: "2009-12-11 17:08:11", updated_at: "2009-12-11 17:10:41">
</pre><p>Si nous avions voulu exploiter la relation dans le sens inverse, il nous aurait fallu énoncer les choses différemment : en effet un Article n&#8217;a qu&#8217;un seul et unique Utilisateur dans notre exemple &#8211; <em>alors qu&#8217;un Utilisateur peut avoir écrit un nombre indéfini d&#8217;Articles</em>.</p><p>Rails nous donne donc accès à un tableau dans le lequel il nous suffit d&#8217;ajouter les Articles de notre choix à l&#8217;Utilisateur qui est leur auteur. Disons que le deuxième Utilisateur a écrit les deux derniers articles :</p><pre class="brush: ruby;">
>> util2.articles = [ arti2, arti3 ]
=> [#&lt;Article id: 2, titre: "Titre de mon second article",
  contenu: "Pas de contenu pour le moment", utilisateur_id: 2,
  created_at: "2009-12-11 17:08:28", updated_at: "2009-12-11 17:14:41">,
  #&lt;Article id: 3, titre: "Titre de mon troisième article",
  contenu: "Pas de contenu pour le moment", utilisateur_id: 2,
  created_at: "2009-12-11 17:08:42", updated_at: "2009-12-11 17:14:41">]
>> util2.save
=> true
>> util2
=> #&lt;Utilisateur id: 2, login: "utilisateur2", mot_de_passe: "mdp2",
  created_at: "2009-12-11 17:07:21", updated_at: "2009-12-11 17:07:21">
</pre><p><strong>Et voilà pour la relation Un à Plusieurs ; comme vous le voyez, le fonctionnement de Rails à ce sujet est assez intuitif et logique.</strong></p><h3>Utiliser la relation Plusieurs à Plusieurs</h3><p>Nous allons maintenant ajouter les tags que nous avons déjà employé précédemment pour tagguer nos Articles. Rails fait la plus grosse partie de notre travail grâce à la relation <code>has_many :through</code> que nous avons défini dans les classes Article et Tag : nous pouvons faire abstraction de la classe Taggable avec laquelle notre framework se chargera d&#8217;interagir.</p><p>Commençons par ajouter deux Tags à notre premier article :</p><pre class="brush: ruby;">
>> arti1.tags << Tag.find(:first, :conditions => { :libelle => "Folk" })
=> [#&lt;Tag id: 2, libelle: "Folk">]
>> arti1.tags << Tag.find(:first, :conditions => { :libelle => "Funk" })
=> [#&lt;Tag id: 2, libelle: "Folk">, #&lt;Tag id: 4, libelle: "Funk">]
>> arti1.tags
=> [#&lt;Tag id: 2, libelle: "Folk">, #&lt;Tag id: 4, libelle: "Funk">]
</pre><p>Notez que j&#8217;utilise une syntaxe différente pour interagir avec le tableau des tags de l&#8217;Article ; l&#8217;opérateur <code>&lt;&lt;</code> peut se lire comme : &laquo;&nbsp;ajoute l&#8217;élément suivant à ce tableau&nbsp;&raquo; ; il s&#8217;agit d&#8217;un équivalent du <code>array_push()</code> de PHP ou du <code>push()</code> de Javascript.</p><p>Ajoutons encore quelques Tags à un autre Article et regardons le contenu de la table contenant les Taggables, avec lesquels nous ne sommes pas directement intervenus, pour constater que <em>Rails a fait lui-même les associations nécessaires</em> pour nous permettre de nous abstraire de cette table de jointure.</p><pre class="brush: ruby;">
>> arti2.tags = Tag.find(:all)
=> [#&lt;Tag id: 2, libelle: "Folk">, #&lt;Tag id: 3, libelle: "Soul">,
  #&lt;Tag id: 4, libelle: "Funk">]
>> Taggable.find :all
=> [#&lt;Taggable id: 1, tag_id: 2, article_id: 1>,
  #&lt;Taggable id: 2, tag_id: 4, article_id: 1>,
  #&lt;Taggable id: 3, tag_id: 2, article_id: 2>,
  #&lt;Taggable id: 4, tag_id: 3, article_id: 2>,
  #&lt;Taggable id: 5, tag_id: 4, article_id: 2>]
</pre><p><strong>Voilà, c&#8217;est tout pour aujourd&#8217;hui !</strong> Dans quelques temps, nous verrons la suite (et <em>a priori</em> la fin) de notre programme initial sur la couche Modèle de Rails, avec les garnitures (<em>fixtures</em>) &amp; les filtres. En attendant, n&#8217;hésitez pas à essayer de refaire ce tutoriel chez vous, c&#8217;est tout son intérêt <img src='http://www.chosesafaire.fr/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> , et laissez un commentaire si vous rencontrez un problème.</p><div class="sexy-bookmarks sexy-bookmarks-center"><ul class="socials"><li class="sexy-blogmarks"> <a href="http://blogmarks.net/my/new.php?mini=1&amp;simple=1&amp;url=http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+2%29" rel="nofollow" title="Marquez-le sur BlogMarks">Marquez-le sur BlogMarks</a></li><li class="sexy-google"> <a href="http://www.google.com/bookmarks/mark?op=add&amp;bkmk=http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+2%29" rel="nofollow" title="Ajoutez-le à Google Bookmarks">Ajoutez-le à Google Bookmarks</a></li><li class="sexy-delicious"> <a href="http://del.icio.us/post?url=http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+2%29" rel="nofollow" title="Partagez-le sur del.icio.us">Partagez-le sur del.icio.us</a></li><li class="sexy-reddit"> <a href="http://reddit.com/submit?url=http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+2%29" rel="nofollow" title="Partagez-le sur Reddit">Partagez-le sur Reddit</a></li><li class="sexy-stumbleupon"> <a href="http://www.stumbleupon.com/submit?url=http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+2%29" rel="nofollow" title="Tomber sur un bon truc ? Partagez cet article sur StumbleUpon">Tomber sur un bon truc ? Partagez cet article sur StumbleUpon</a></li><li class="sexy-technorati"> <a href="http://technorati.com/faves?add=http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/" rel="nofollow" title="Partagez-le sur Technorati">Partagez-le sur Technorati</a></li><li class="sexy-twitter"> <a href="http://twitter.com/home?status=La+couche+Mod%C3%A8le+de+Rails+%28Partie+2%29+-+http://tinyurl.com/ye4x9h5+(via+@dandelionmood)" rel="nofollow" title="Tweetez-le !">Tweetez-le !</a></li><li class="sexy-comfeed"> <a href="http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/feed" rel="nofollow" title="S'abonner aux commentaires de cet article ?">S'abonner aux commentaires de cet article ?</a></li></ul><div style="clear:both;"></div></div><p>Articles liés :</p><ol><li><a href='http://www.chosesafaire.fr/2010/01/la-couche-modele-de-rails-partie-3/' rel='bookmark' title='Permanent Link: La couche Modèle de Rails (Partie 3)'>La couche Modèle de Rails (Partie 3)</a></li><li><a href='http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/' rel='bookmark' title='Permanent Link: La couche Modèle de Rails (Partie 1)'>La couche Modèle de Rails (Partie 1)</a></li></ol></p>]]></content:encoded> <wfw:commentRss>http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/feed/</wfw:commentRss> <slash:comments>2</slash:comments> </item> <item><title>La couche Modèle de Rails (Partie 1)</title><link>http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/</link> <comments>http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/#comments</comments> <pubDate>Sat, 05 Dec 2009 14:51:54 +0000</pubDate> <dc:creator>Pierre Quillery</dc:creator> <category><![CDATA[Développement]]></category> <category><![CDATA[rails]]></category> <category><![CDATA[tutorial]]></category><guid isPermaLink="false">http://www.chosesafaire.fr/?p=229</guid> <description><![CDATA[Rails, en bon framework qu'il est, propose bien sûr une couche Modèle assez complète à laquelle les deux prochains articles se proposent de vous introduire. Nous couvrirons dans cet article la définition du concept de Modèle, la création d'objets à l'aide de Migrations, validerons les données et créerons des Relations entre ces même Modèles ... Copieux programme en perspective !Articles liés :<ol><li><a href='http://www.chosesafaire.fr/2010/01/la-couche-modele-de-rails-partie-3/' rel='bookmark' title='Permanent Link: La couche Modèle de Rails (Partie 3)'>La couche Modèle de Rails (Partie 3)</a></li><li><a href='http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/' rel='bookmark' title='Permanent Link: La couche Modèle de Rails (Partie 2)'>La couche Modèle de Rails (Partie 2)</a></li></ol>]]></description> <content:encoded><![CDATA[<p>Rails, en bon <em>framework</em> qu&#8217;il est, propose bien sûr une couche Modèle assez complète à laquelle les deux prochains articles se proposent de vous introduire. Nous couvrirons dans cet article <strong>la définition du concept de Modèle</strong>, la création d&#8217;objets à l&#8217;aide de <strong>Migrations</strong>, <strong>validerons les données</strong> et créerons des <strong>Relations</strong> entre ces même Modèles &#8230; Copieux programme en perspective !</p><p><span id="more-229"></span></p><p>Tout d&#8217;abord, un petit <em>disclaimer</em> s&#8217;impose par rapport au contenu de cet article : il s&#8217;agit de la suite d&#8217;une série d&#8217;articles que j&#8217;avais commencé sur mon précédent blog sans aller jusqu&#8217;au bout. Je ne pense pas reprendre les anciens articles sur ce blog-ci, ce qui n&#8217;aurait pas beaucoup de sens dans un premier temps &#8211; je vous invite donc à aller <a href="http://dandelionmood.com/Ruby-on-Rails-lancez-vous.html">consulter la page de présentation de mon projet</a> ainsi que les premiers articles sur l&#8217;ancien site.</p><h2>Un Modèle ?</h2><p>Le Modèle est, comme nous l&#8217;avons précédemment, une des trois couches du modèles MVC. Il s&#8217;agit de celle qui est en charge de permettre l&#8217;enregistrement et la recherche de données. Typiquement, on utilise pour cela une classe dite d&#8217;ORM, de mapping objet-relationnel, qui va nous permettre de nous abstraire d&#8217;un SGBD particulier tout en s&#8217;intégrant mieux dans un ensemble de classes.</p><p>L&#8217;idée est de réduire au maximum la distance qui sépare la base de données (l&#8217;implémentation du stockage de données) des objets métiers de notre application (un Client, un Article, un Blog, un Commentaire, etc.). C&#8217;est également dans la couche Modèle que nous allons définir facilement <strong>les relations</strong> entre ces derniers (un Article a plusieurs Commentaires, etc.) ainsi que la <strong>validation des données</strong> (la date de naissance d&#8217;un utilisateur donné doit toujours être sous la forme jj/mm/aaaa, etc.).</p><h2>Créons une nouvelle classe de Modèle à l&#8217;aide des Migrations</h2><p>Tout d&#8217;abord, qu&#8217;est ce qu&#8217;une migration ? Il s&#8217;agit d&#8217;un fichier qui va contenir la définition d&#8217;une ou plusieurs tables ainsi que des champs qui les composent. Ils peuvent être créés soit automatiquement, (lorsque vous ajoutez une classe métier) soit par un plugin &#8230; Vous pouvez également en générer à tout moment.</p><p>L&#8217;idée est de placer dans un fichier texte facile à lire et à éditer la structure de votre base de données. Rails s&#8217;occupera ensuite de générer les commandes SQL qui correspondent dans la base correspondant à votre environnement courant (nous verrons cela plus loin). <strong>Vous pouvez ainsi facilement versionner votre base.</strong></p><p>Créons donc un classe Modèle de test à l&#8217;aide de la console Rails. Comme vous le voyez, je précise immédiatement les champs que je connais de ce Modèle : ils seront ajoutés automatiquement par Rails dans le fichier de migration correspondant que nous complèterons manuellement tout à l&#8217;heure.</p><pre class="brush: bash;">
script/generate model Article titre:string date_publication:datetime contenu:text timestamps
</pre><p>Ça y est : quelques fichiers ont été créés, notamment <code>app/models/article.rb</code> qui est la classe de notre Modèle à propremment parler, et qui ne contient rien pour l&#8217;instant et <code>db/migrate/xxxx_create_article.rb</code>, notre Migration.</p><p>Voyons maintenant le contenu de ce deuxième fichier :</p><pre class="brush: ruby;">
class CreateArticles &lt; ActiveRecord::Migration

  # Méthode appelée pour mettre en place la migration
  def self.up
    create_table :articles do |t|
      t.string :titre
      t.text :contenu
      t.datetime :date_publication

      # Va créer un champ auteur_id que nous utiliserons un peu plus loin
      t.references :auteur

      # Ajout automatique de champs tels que created_at, modified_at, etc.
      # Ces champs seront ensuite gérés par Rails automatiquement.
      t.timestamps
    end
  end

  # Méthode permettant d'enlever la migration au besoin
  def self.down
    drop_table :articles
  end
end
</pre><p>Nous pouvons éditer le fichier pour ajouter éventuellement des champs supplémentaires puis déclencher la migration en utilisant la ligne de commande Rails. Nous pourrons alors utiliser le fichier de Modèle et poursuivre notre application.</p><pre class="brush: bash;">
rake db:migrate
</pre><h2>Validation dans le Modèle</h2><p>Nous allons maintenant travailler avec la classe Modèle qui se trouve dans le répertoire <code>app/models</code> et ajouter du code pour faciliter la validation des champs qui s&#8217;y trouvent. Cela signifie que nous pourrons ainsi nous assurer par exemple du bon formattage d&#8217;un email, d&#8217;une date de naissance, de la présence d&#8217;un titre etc.</p><p>Cette validation empêchera par défaut toute création d&#8217;enregistrement ou mise à jour. Elle ajoutera le cas échéant des messages au modèle (personnalisables et même disponibles en plusieurs langues via l&#8217;internationalisation du framework) que vous pourrez simplement montrer à l&#8217;utilisateur pour lui permettre de corriger facilement sa saisie.</p><pre class="brush: ruby;">
class Article &lt; ActiveRecord::Base
  # Le titre de l'article est obligatoire (il ne doit pas être vide)
  validate_presence_of :titre

  # Le titre ne devrait pas être trop court ...
  validates_length_of :titre, :minimum => 30

  # etc .
end
</pre><p>Voici une page où vous trouverez <a href="http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html">toutes les méthodes de validation à votre disposition</a>, les méthodes par défaut couvrent une bonne partie des besoins les plus évidents.</p><p>Nous verrons dans le prochain article comment exploiter efficacement ces messages de validation pour les afficher à l&#8217;internaute lors de sa saisie.</p><h2>Créons des Relations</h2><p>La création de relations est une partie intéressante des possibilités que Rails vous offre pour la couche Modèle : vous pouvez ainsi exprimer directement les principaux types de relations dont vous avez besoin pour reproduire tout schéma de base dans votre application.</p><p>Précisons d&#8217;abord que pour fonctionner, Rails a tout de même besoin que vous respectiez ses propres convention de nommage des champs pour lui permettre de retrouver ses petits et inférer ce que vous voulez faire. Ainsi, les champs de clés étrangères seront toujours nommées <em>champ_id</em>.</p><p>Je vais maintenant présenter les principaux types de relations disponibles, mais toutes ces méthodes sont très personnalisables, <a href="http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html">voyez dans la documentation pour en apprendre plus à leur sujet</a>.</p><h3>Une à Plusieurs</h3><p>Un des cas le plus fréquent est la relation Une à Plusieurs : un Auteur a plusieurs Articles, un Article a plusieurs Commentaires, etc. Voici comment l&#8217;écrire dans les classes e Modèle (je pars du principe que les modèles correspondants ont été créés et migrés vers la base) :</p><pre class="brush: ruby;">
# Fichier app/models/commentaire.rb
class Commentaire &lt; ActiveRecord::Base
  # C'est cette table qui contient la clé étrangère article_id,
  # on écrit donc belongs_to puis le nom du Modèle à lier au singulier
  belongs_to :article
end

# Fichier app/models/article.rb
class Article &lt; ActiveRecord::Base
  # Deuxième côté de la relation, on écrit has_many puis le
  # nom du Modèle au pluriel.
  has_many :commentaires
end
</pre><h3>Une à Une</h3><p>Moins fréquent, vous pouvez restreindre cette relation pour créer une relation Une à Une en changeant le <code>has_many</code> par un <code>has_one</code>, comme dans cet exemple :</p><pre class="brush: ruby;">
# Fichier app/models/utilisateur.rb
class Utilisateur &lt; ActiveRecord::Base
  # C'est cette table qui contient la clé étrangère profil_id
  belongs_to :profil
end

# Fichier app/models/profil.rb
class Profil &lt; ActiveRecord::Base
  # Deuxième côté de la relation, on écrit has_many puis le
  # nom du Modèle au singulier.
  has_many :utilisateur
end
</pre><h3>Plusieurs à Plusieurs et Relations Composites</h3><p>Vous trouverez sur Internet de nombreux tutoriaux qui vous présenteront l&#8217;emploi de la technique dite du <em>habtm</em> ou <code>has_and_belongs_to</code> ; je ferais pour ma part l&#8217;impasse sur cette dernière qui est maintenant globalement dépréciée et qui ne devrait plus être utilisée dans la plupart des cas.</p><p>Voici donc la bonne façon de voir les choses pour créer une relation Plusieurs à Plusieurs viable, c&#8217;est à dire grâce au <code>has_many :through</code> :</p><pre class="brush: ruby;">
# Fichier app/models/utilisateur.rb
class Utilisateur &lt; ActiveRecord::Base
  has_many :abonnements
  has_many :magazines, :through => :abonnements
end

# Fichier app/models/abonnement.rb
class Abonnement &lt; ActiveRecord::Base
  # Notez qu'on emploie dans cet objet deux clés étrangères
  belongs_to :utilisateur
  belongs_to :magazine
end

class Magazine &lt; ActiveRecord::Base
  has_many :abonnements
  has_many :utilisateurs, :through => :abonnements
end
</pre><p>Rien ne vous empêche d&#8217;ajouter des champs supplémentaires à votre objet Abonnement si vous souhaitez enrichir la relation ; on pourrait imaginer enregistrer la durée de l&#8217;abonnement par exemple. Ce n&#8217;était pas possible avec l&#8217;ancienne technique (habtm), et c&#8217;est une des raisons pour laquelle elle tombe petit à petit en désuétude.</p><p>Vous pouvez, sur le même principe, créer des relations composites (ou polymorphiques) qui pourront vous servir à factoriser pas mal de code en créeant une interface commune auxquels des Modèles de différents types pourront s&#8217;accrocher. C&#8217;est un peu complexe à expliquer, mais un exemple vaut mieux qu&#8217;un long discours :</p><pre class="brush: ruby;">
class Coordonnee &lt; ActiveRecord::Base
  # Il faut deux champs supplémentaires dans la table coordonnees :
  # - localisable_id, un entier
  # - localisable_type, une chaîne
  # => Utilisez la syntaxe suivante dans la Migration :
  #    t.references :localisable, :polymorphic => true
  belongs_to :localisable, :polymorphic => true
end

class Utilisateur &lt; ActiveRecord::Base
  has_many :localisations, :as => :localisable
end

class Societe &lt; ActiveRecord::Base
  has_many :localisations, :as => :localisable
end
</pre><p>Grâce à cette syntaxe, les Utilisateurs et les Societes partagent la possibilité d&#8217;avoir un objet Coordonnees qui leur est lié, ce qui serait plus complexe à mettre en place sinon. Nous pourrions facilement faire évoluer ce modèle au besoin, c&#8217;est ce qui fait sa force.</p><p>Dans quelques jours, nous verrons la suite de notre programme sur les Modèles dans Rails en parlant des différents types de base de données disponibles et leur configuration, des requêtes préparées, etc. <a href="http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/">Voir l&#8217;article suivant !</a></p><div class="sexy-bookmarks sexy-bookmarks-center"><ul class="socials"><li class="sexy-blogmarks"> <a href="http://blogmarks.net/my/new.php?mini=1&amp;simple=1&amp;url=http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+1%29" rel="nofollow" title="Marquez-le sur BlogMarks">Marquez-le sur BlogMarks</a></li><li class="sexy-google"> <a href="http://www.google.com/bookmarks/mark?op=add&amp;bkmk=http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+1%29" rel="nofollow" title="Ajoutez-le à Google Bookmarks">Ajoutez-le à Google Bookmarks</a></li><li class="sexy-delicious"> <a href="http://del.icio.us/post?url=http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+1%29" rel="nofollow" title="Partagez-le sur del.icio.us">Partagez-le sur del.icio.us</a></li><li class="sexy-reddit"> <a href="http://reddit.com/submit?url=http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+1%29" rel="nofollow" title="Partagez-le sur Reddit">Partagez-le sur Reddit</a></li><li class="sexy-stumbleupon"> <a href="http://www.stumbleupon.com/submit?url=http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/&amp;title=La+couche+Mod%C3%A8le+de+Rails+%28Partie+1%29" rel="nofollow" title="Tomber sur un bon truc ? Partagez cet article sur StumbleUpon">Tomber sur un bon truc ? Partagez cet article sur StumbleUpon</a></li><li class="sexy-technorati"> <a href="http://technorati.com/faves?add=http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/" rel="nofollow" title="Partagez-le sur Technorati">Partagez-le sur Technorati</a></li><li class="sexy-twitter"> <a href="http://twitter.com/home?status=La+couche+Mod%C3%A8le+de+Rails+%28Partie+1%29+-+http://tinyurl.com/y8cnaqz+(via+@dandelionmood)" rel="nofollow" title="Tweetez-le !">Tweetez-le !</a></li><li class="sexy-comfeed"> <a href="http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/feed" rel="nofollow" title="S'abonner aux commentaires de cet article ?">S'abonner aux commentaires de cet article ?</a></li></ul><div style="clear:both;"></div></div><p>Articles liés :</p><ol><li><a href='http://www.chosesafaire.fr/2010/01/la-couche-modele-de-rails-partie-3/' rel='bookmark' title='Permanent Link: La couche Modèle de Rails (Partie 3)'>La couche Modèle de Rails (Partie 3)</a></li><li><a href='http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-2/' rel='bookmark' title='Permanent Link: La couche Modèle de Rails (Partie 2)'>La couche Modèle de Rails (Partie 2)</a></li></ol></p>]]></content:encoded> <wfw:commentRss>http://www.chosesafaire.fr/2009/12/la-couche-modele-de-rails-partie-1/feed/</wfw:commentRss> <slash:comments>1</slash:comments> </item> <item><title>Le problème du Singleton</title><link>http://www.chosesafaire.fr/2009/12/le-probleme-du-singleton/</link> <comments>http://www.chosesafaire.fr/2009/12/le-probleme-du-singleton/#comments</comments> <pubDate>Wed, 02 Dec 2009 06:00:08 +0000</pubDate> <dc:creator>Killian Ebel</dc:creator> <category><![CDATA[Développement]]></category> <category><![CDATA[dependency injection]]></category> <category><![CDATA[ioc]]></category> <category><![CDATA[mock]]></category> <category><![CDATA[tests]]></category><guid isPermaLink="false">http://www.chosesafaire.fr/?p=214</guid> <description><![CDATA[Le concept du Singleton en lui-même n&#8217;est pas un souci, avoir une instance unique d&#8217;une classe est même souvent très pratique. Quand bien même, qu&#8217;en est-il de la testabilité d&#8217;un tel composant ?L&#8217;implémentation reconnue, le design pattern Singleton du fameux GoF, est malheureusement assez compliquée à tester : une classe ne devrait pas forcer elle-même [...]Articles liés :<ol><li><a href='http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/' rel='bookmark' title='Permanent Link: Mocks et Multithreading'>Mocks et Multithreading</a></li><li><a href='http://www.chosesafaire.fr/2010/03/easyb-behavior-driven-development/' rel='bookmark' title='Permanent Link: Easyb, Behavior Driven Development'>Easyb, Behavior Driven Development</a></li></ol>]]></description> <content:encoded><![CDATA[<p>Le concept du Singleton en lui-même n&#8217;est pas un souci, avoir une instance unique d&#8217;une classe est même souvent très pratique. Quand bien même, qu&#8217;en est-il de la testabilité d&#8217;un tel composant ?<br /> <span id="more-214"></span><br /> L&#8217;implémentation reconnue, le <a href="http://fr.wikipedia.org/wiki/Singleton_%28patron_de_conception%29">design pattern Singleton</a> du fameux <a href="http://fr.wikipedia.org/wiki/Patron_de_conception#Liste_des_patrons_de_conception_du_GoF">GoF</a>, est malheureusement assez compliquée à tester : une classe ne devrait pas forcer <strong>elle-même</strong> son statut de Singleton, mais l&#8217;instance unique devrait être gérée au niveau de l&#8217;application.</p><h2>Le problème</h2><p>Imaginons un composant <strong>Service</strong>, une quelconque classe métier. Dans une de ses méthodes, la classe Service fait appel à un autre composant, nommé par exemple <strong>Counter</strong> ; ce dernier est un simple compteur qui incrémente sa valeur à chaque appel d&#8217;un service. Pour avoir un compteur commun à tous les services, il doit en exister une instance unique, partagée par tous les composants de type Service.</p><p>Ainsi, voici un exemple d&#8217;implémentation :</p><pre class="brush: java;">class Counter {
    private static final Counter instance = new Counter();
    private int count = 0;

    private Counter() {}

    public static Counter getInstance() {
        return instance;
    }

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}</pre><pre class="brush: java;">class Service {
    public void execute() {
        // ...
        Counter.getInstance().increment();
    }
}</pre><p>L&#8217;implémentation semble correcte, un test unitaire appelant le service peut être écrit afin d&#8217;en vérifier le comportement. Imaginons maintenant que :</p><ul><li>Un grand nombre d&#8217;autres services utilise ce compteur ;</li><li>L&#8217;appel à l&#8217;incrémentation du compteur soit très complexe, faisant appel à des webservices / se connectant à une base de données / lisant un fichier du système ;</li><li>Plusieurs tests unitaires, pouvant même être lancés simultanément, ont besoin d&#8217;un compteur &laquo;&nbsp;remis à zéro&nbsp;&raquo;.</li></ul><h2>Que faire ?</h2><p>Votre service est complètement dépendant de la classe Counter. Ecrire une méthode qui réinitialise le Singleton ? Rajouter des conditions dans la méthode du Singleton pour éviter les opérations trop importantes en cas de test ? Les tests unitaires n&#8217;ont pas à interférer votre code métier, qui réciproquement ne doit pas changer pour s&#8217;adapter aux tests. Ce qui importe dans ce test, c&#8217;est de vérifier le fonctionnement du service, et non l&#8217;appel au compteur ; comment <a href="http://www.chosesafaire.fr/2009/10/mocks-stubs-leurres-des-tests/">mocker</a> le compteur si celui-ci est un Singleton et crée lui-même son instance ?</p><p>Une solution au problème est l&#8217;injection de dépendances. Votre application (ou plutôt le <a href="http://fr.wikipedia.org/wiki/Inversion_de_contr%C3%B4le">container IoC</a>) va se charger d&#8217;instancier l&#8217;unique compteur et de l&#8217;injecter dans chacun des services. Le processus peut-être fait manuellement, mais sera bien plus simple à mettre en place avec un framework spécialisé comme <a href="http://www.springsource.org/">Spring</a> ou <a href="http://code.google.com/p/google-guice/">Guice</a>. Voici un exemple avec Spring :</p><pre class="brush: java;">interface Counter() {
    void increment();
    int getCount();
}</pre><pre class="brush: java;">class ComplexCounter implements Counter {
    private int count = 0;

    public Counter() {}

    public void increment() {
        // Beaucoup de choses compliquées et très lourdes, assez longues à exécuter (le temps d'aller boire un café)
        count++;
    }

    public int getCount() {
        return count;
    }
}</pre><pre class="brush: java;">class Service {
    private Counter counter;

    public Service(Counter counter) {
        this.counter = counter;
    }

    public void execute() {
        // ...
        counter.increment();
    }
}
</pre><pre class="brush: xml;">
&lt;bean id="counter" class="fr.chosesafaire.singleton.ComplexCounter" /&gt;
&lt;bean id="service"&gt;
    &lt;constructor-arg ref="counter" /&gt;
&lt;/bean&gt;
&lt;bean id="otherService"&gt;
     &lt;constructor-arg ref="counter" /&gt;
&lt;/bean&gt;
</pre><p>Ainsi votre instance de compteur reste un Singleton au sens conceptuel du terme (instance unique partagée par les autres composants), mais la classe Counter ne gère plus elle-même son instance. L&#8217;avantage est de pouvoir tester désormais le service en mockant le compteur, et éviter la liste d&#8217;ennuis énoncée précédemment (avec <a href="http://www.jmock.org/">jMock</a>) :</p><pre class="brush: java;">Mockery context = new Mockery();

public void testService() {
    final Counter counter = context.mock(Counter.class);
    context.checking(new Expectations() {{
        oneOf(counter).increment();
    }});

    Service service = new Service(counter);
    service.execute();

    // Assertions ...
    context.assertIsSatisfied();
}</pre><p>On s&#8217;attend seulement (<strong>Expectations</strong>) à ce que la méthode <strong>increment</strong> du compteur soit appelée, et peu importe son réel fonctionnement, vu que seul le Service doit être testé !</p><p><strong>Que pensez-vous de cette solution à propos du Singleton ? Comment faîtes-vous pour tester vos classes Singleton ?</strong></p><p>Voici quelques références :</p><ul><li><a href="http://misko.hevery.com/attachments/Guide-Writing%20Testable%20Code.pdf">Le guide de l&#8217;écriture d&#8217;un code testable</a>, par des développeurs de Google</li><li><a href="http://structuremap.sourceforge.net/SingletonInjection.htm">Injected Singleton</a></li></ul><div class="sexy-bookmarks sexy-bookmarks-center"><ul class="socials"><li class="sexy-blogmarks"> <a href="http://blogmarks.net/my/new.php?mini=1&amp;simple=1&amp;url=http://www.chosesafaire.fr/2009/12/le-probleme-du-singleton/&amp;title=Le+probl%C3%A8me+du+Singleton" rel="nofollow" title="Marquez-le sur BlogMarks">Marquez-le sur BlogMarks</a></li><li class="sexy-google"> <a href="http://www.google.com/bookmarks/mark?op=add&amp;bkmk=http://www.chosesafaire.fr/2009/12/le-probleme-du-singleton/&amp;title=Le+probl%C3%A8me+du+Singleton" rel="nofollow" title="Ajoutez-le à Google Bookmarks">Ajoutez-le à Google Bookmarks</a></li><li class="sexy-delicious"> <a href="http://del.icio.us/post?url=http://www.chosesafaire.fr/2009/12/le-probleme-du-singleton/&amp;title=Le+probl%C3%A8me+du+Singleton" rel="nofollow" title="Partagez-le sur del.icio.us">Partagez-le sur del.icio.us</a></li><li class="sexy-reddit"> <a href="http://reddit.com/submit?url=http://www.chosesafaire.fr/2009/12/le-probleme-du-singleton/&amp;title=Le+probl%C3%A8me+du+Singleton" rel="nofollow" title="Partagez-le sur Reddit">Partagez-le sur Reddit</a></li><li class="sexy-stumbleupon"> <a href="http://www.stumbleupon.com/submit?url=http://www.chosesafaire.fr/2009/12/le-probleme-du-singleton/&amp;title=Le+probl%C3%A8me+du+Singleton" rel="nofollow" title="Tomber sur un bon truc ? Partagez cet article sur StumbleUpon">Tomber sur un bon truc ? Partagez cet article sur StumbleUpon</a></li><li class="sexy-technorati"> <a href="http://technorati.com/faves?add=http://www.chosesafaire.fr/2009/12/le-probleme-du-singleton/" rel="nofollow" title="Partagez-le sur Technorati">Partagez-le sur Technorati</a></li><li class="sexy-twitter"> <a href="http://twitter.com/home?status=Le+probl%C3%A8me+du+Singleton+-+http://tinyurl.com/yzbqcny+(via+@dandelionmood)" rel="nofollow" title="Tweetez-le !">Tweetez-le !</a></li><li class="sexy-comfeed"> <a href="http://www.chosesafaire.fr/2009/12/le-probleme-du-singleton/feed" rel="nofollow" title="S'abonner aux commentaires de cet article ?">S'abonner aux commentaires de cet article ?</a></li></ul><div style="clear:both;"></div></div><p>Articles liés :</p><ol><li><a href='http://www.chosesafaire.fr/2010/01/mocks-et-multithreading/' rel='bookmark' title='Permanent Link: Mocks et Multithreading'>Mocks et Multithreading</a></li><li><a href='http://www.chosesafaire.fr/2010/03/easyb-behavior-driven-development/' rel='bookmark' title='Permanent Link: Easyb, Behavior Driven Development'>Easyb, Behavior Driven Development</a></li></ol></p>]]></content:encoded> <wfw:commentRss>http://www.chosesafaire.fr/2009/12/le-probleme-du-singleton/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>VRaptor, le framework qui a les crocs</title><link>http://www.chosesafaire.fr/2009/10/vraptor-le-framework-qui-a-les-crocs/</link> <comments>http://www.chosesafaire.fr/2009/10/vraptor-le-framework-qui-a-les-crocs/#comments</comments> <pubDate>Fri, 30 Oct 2009 06:00:50 +0000</pubDate> <dc:creator>Killian Ebel</dc:creator> <category><![CDATA[Développement]]></category> <category><![CDATA[gradle]]></category> <category><![CDATA[ioc]]></category> <category><![CDATA[java]]></category> <category><![CDATA[maven]]></category> <category><![CDATA[mvc]]></category><guid isPermaLink="false">http://www.chosesafaire.fr/?p=155</guid> <description><![CDATA[A l'occasion de la sortir de VRaptor 3, un framework MVC dernière génération pour Java, je me suis permis de faire un petit article de présentation.Articles liés :<ol><li><a href='http://www.chosesafaire.fr/2010/03/easyb-behavior-driven-development/' rel='bookmark' title='Permanent Link: Easyb, Behavior Driven Development'>Easyb, Behavior Driven Development</a></li><li><a href='http://www.chosesafaire.fr/2009/11/lucene-scala-et-spring/' rel='bookmark' title='Permanent Link: Lucene, Scala et Spring'>Lucene, Scala et Spring</a></li></ol>]]></description> <content:encoded><![CDATA[<p>A l&#8217;occasion de la sortie de VRaptor 3, un framework MVC dernière génération pour Java, je me suis permis de faire un petit article de présentation.</p><p><span id="more-155"></span></p><p><a href="http://vraptor.caelum.com.br/en/">VRaptor</a> s&#8217;annonce très prometteur, mais n&#8217;est malheureusement pas encore disponible sur un dépôt Maven. Pour rappel, Maven est un outil de build qui fonctionne en indiquant les dépendances de son projet, appuyé par des conventions d&#8217;architecture. Ainsi, en respectant les standards proposés par Maven au niveau de l&#8217;arborescence d&#8217;un projet, il n&#8217;est plus nécessaire d&#8217;indiquer impérativement une opération de compilation, vu que Maven se charge de vous fournir le bon <strong>classpath</strong> et de télécharger les librairies manquantes si votre projet en dépend.</p><p>Si Maven vous fait peur, que le XML vous rebute, je ne peux que vous conseiller d&#8217;utiliser <a href="http://www.gradle.org/0.8/docs/userguide/userguide.html">Gradle</a>. Cet outil de build basé sur <a href="http://groovy.codehaus.org/">Groovy</a> me paraît très flexible et simple à mettre en place ; nous le verrons rapidement à la fin de l&#8217;article.</p><h3>Mise en place de Maven</h3><p>Créez votre fichier de projet <strong>pom.xml</strong> :</p><pre class="brush: xml;">
&lt;project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;

&lt;properties&gt;
    &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
&lt;/properties&gt;

&lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
&lt;groupId&gt;fr.chosesafaire&lt;/groupId&gt;
&lt;artifactId&gt;blog&lt;/artifactId&gt;
&lt;packaging&gt;war&lt;/packaging&gt;
&lt;name&gt;blog&lt;/name&gt;
&lt;version&gt;0.0.1&lt;/version&gt;
&lt;description&gt;Test de VRaptor&lt;/description&gt;
&lt;url&gt;http://www.chosesafaire.fr&lt;/url&gt;

&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;source&gt;1.5&lt;/source&gt;
                &lt;target&gt;1.5&lt;/target&gt;
                &lt;encoding&gt;UTF-8&lt;/encoding&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
        &lt;plugin&gt;
            &lt;artifactId&gt;maven-war-plugin&lt;/artifactId&gt;
            &lt;configuration&gt;
                &lt;warSourceDirectory&gt;${basedir}/src/main/webapp&lt;/warSourceDirectory&gt;
                &lt;webappDirectory&gt;${env.TOMCAT_HOME}/webapps/${project.artifactId}&lt;/webappDirectory&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;

&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.aspectj&lt;/groupId&gt;
        &lt;artifactId&gt;aspectjrt&lt;/artifactId&gt;
        &lt;version&gt;1.6.4&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;cglib&lt;/groupId&gt;
        &lt;artifactId&gt;cglib-nodep&lt;/artifactId&gt;
        &lt;version&gt;2.2&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;commons-fileupload&lt;/groupId&gt;
        &lt;artifactId&gt;commons-fileupload&lt;/artifactId&gt;
        &lt;version&gt;1.2.1&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.apache.commons&lt;/groupId&gt;
        &lt;artifactId&gt;commons-io&lt;/artifactId&gt;
        &lt;version&gt;1.3.2&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;commons-logging&lt;/groupId&gt;
        &lt;artifactId&gt;commons-logging&lt;/artifactId&gt;
        &lt;version&gt;1.1.1&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.google.collections&lt;/groupId&gt;
        &lt;artifactId&gt;google-collections&lt;/artifactId&gt;
        &lt;version&gt;1.0-rc2&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.hamcrest&lt;/groupId&gt;
        &lt;artifactId&gt;hamcrest-all&lt;/artifactId&gt;
        &lt;version&gt;1.1&lt;/version&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javassist&lt;/groupId&gt;
        &lt;artifactId&gt;javassist&lt;/artifactId&gt;
        &lt;version&gt;3.8.0.GA&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;javax.servlet&lt;/groupId&gt;
        &lt;artifactId&gt;jstl&lt;/artifactId&gt;
        &lt;version&gt;1.2&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;junit&lt;/groupId&gt;
        &lt;artifactId&gt;junit&lt;/artifactId&gt;
        &lt;version&gt;4.7&lt;/version&gt;
        &lt;scope&gt;test&lt;/scope&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;log4j&lt;/groupId&gt;
        &lt;artifactId&gt;log4j&lt;/artifactId&gt;
        &lt;version&gt;1.2.12&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;net.vidageek&lt;/groupId&gt;
        &lt;artifactId&gt;mirror&lt;/artifactId&gt;
        &lt;version&gt;1.5.1&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.objenesis&lt;/groupId&gt;
        &lt;artifactId&gt;objenesis&lt;/artifactId&gt;
        &lt;version&gt;1.1&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;ognl&lt;/groupId&gt;
        &lt;artifactId&gt;ognl&lt;/artifactId&gt;
        &lt;version&gt;2.7.3&lt;/version&gt;
    &lt;/dependency&gt;
        &lt;dependency&gt;
        &lt;groupId&gt;com.thoughtworks.paranamer&lt;/groupId&gt;
        &lt;artifactId&gt;paranamer&lt;/artifactId&gt;
        &lt;version&gt;2.1&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.reflections&lt;/groupId&gt;
        &lt;artifactId&gt;reflections&lt;/artifactId&gt;
        &lt;version&gt;0.9.4&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
        &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
        &lt;version&gt;1.5.6&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
        &lt;artifactId&gt;slf4j-log4j12&lt;/artifactId&gt;
        &lt;version&gt;1.5.6&lt;/version&gt;
        &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework&lt;/groupId&gt;
        &lt;artifactId&gt;spring&lt;/artifactId&gt;
        &lt;version&gt;2.5.6&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;br.com.caelum&lt;/groupId&gt;
        &lt;artifactId&gt;vraptor&lt;/artifactId&gt;
        &lt;version&gt;3.0.0&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;
&lt;/project&gt;
</pre><p>Pour la liste des dépendances, j&#8217;ai pris toutes les librairies qui étaient incluses dans l&#8217;exemple de projet vide avec VRaptor (plus d&#8217;autres, comme JUnit et Hamcrest), et les ai recherchées sur <a href="http://www.mvnrepository.com/">http://www.mvnrepository.com/</a>. On remarque certaines dépendances intéressantes et encore expérimentales comme <a href="http://code.google.com/p/google-collections/">Google Collections</a>. J&#8217;ai pris soin à chaque fois de récupérer la version la plus récente (en espérant qu&#8217;il n&#8217;y aura pas de problèmes de compatibilité !).<br /> Comme VRaptor (version 3) n&#8217;est encore disponible sur aucun dépôt Maven, il va falloir <a href="http://vraptor.caelum.com.br/en/download.jsp">télécharger le JAR</a>, puis l&#8217;installer dans votre dépôt local ( <a href="http://maven.apache.org/plugins/maven-install-plugin/usage.html">plus de détails</a> ) :</p><pre class="brush: shell;">
mvn install:install-file -Dfile=/path/to/vraptor-3.0.0.jar -DgroupId=br.com.caelum -DartifactId=vraptor -Dversion=3.0.0 -Dpackaging=jar
</pre><p>De même, je n&#8217;ai pas trouvé l&#8217;archive reflections-0.9.4.jar, mais le projet est <a href="http://code.google.com/p/reflections/">herbergé sur Google Code</a> alors comme pour VRaptor, récupérez l&#8217;archive et installez le dans votre dépôt local :</p><pre class="brush: shell;">
mvn install:install-file -Dfile=/path/to/reflections-0.9.4.jar -DgroupId=org.reflections -DartifactId=reflections -Dversion=0.9.4 -Dpackaging=jar
</pre><p>Mises à part ces deux outils, Maven va se charger de télécharger automatiquement les versions demandées des autres et les incluera dans votre projet (que ce soit au niveau de la compilation ou de l&#8217;exécution, c&#8217;est à vous de paramétrer). Attention cependant si vous êtes <a href="http://maven.apache.org/guides/mini/guide-proxies.html">derrière un proxy</a>.</p><h3>src/main/webapp/META-INF/MANIFEST.MF</h3><p>Souvent passé à la trappe, ce fichier peut se révéler bien utile dans certains cas. Par défaut, il contient seulement une indication sur sa version :</p><pre class="brush: text;">
Manifest-Version: 1.0
</pre><p>Mais comme indiqué sur<a href="http://java.sun.com/docs/books/tutorial/deployment/jar/manifestindex.html"> le site de Sun</a>, il peut être utilisé par exemple à des fins de signature du JAR, ou encore pour indiquer les dépendances de votre JAR. Cette dernière fonctionnalité est la base du fonctionnement des <a href="http://en.wikipedia.org/wiki/OSGi">Bundles OSGi</a>, le framework qui transforme votre application en modules indépendants, pouvant être rechargés individuellement et dynamiquement.</p><h3>src/main/resources/log4j.properties</h3><p>Pour cet exemple on ne va pas trop complexifier le debugger, qui affichera simplement dans la console :</p><pre class="brush: text;">
log4j.rootLogger=debug, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
</pre><h3>src/main/webapp/web.xml</h3><p>VRaptor s&#8217;installe sous la forme d&#8217;un filtre, dont le seul paramètre obligatoire est le package de base à partir duquel il va scanner vos classes :</p><pre class="brush: xml;">
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="Blog" version="2.5"&gt;

    &lt;display-name&gt;chosesafaire-blog&lt;/display-name&gt;

    &lt;context-param&gt;
        &lt;param-name&gt;br.com.caelum.vraptor.packages&lt;/param-name&gt;
        &lt;param-value&gt;fr.chosesafaire.blog&lt;/param-value&gt;
    &lt;/context-param&gt;

    &lt;filter&gt;
        &lt;filter-name&gt;vraptor&lt;/filter-name&gt;
        &lt;filter-class&gt;br.com.caelum.vraptor.VRaptor&lt;/filter-class&gt;
    &lt;/filter&gt;

    &lt;filter-mapping&gt;
        &lt;filter-name&gt;vraptor&lt;/filter-name&gt;
        &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
        &lt;dispatcher&gt;FORWARD&lt;/dispatcher&gt;
        &lt;dispatcher&gt;REQUEST&lt;/dispatcher&gt;
    &lt;/filter-mapping&gt;

&lt;/web-app&gt;
</pre><p>Ce fichier de configuration est le seul dont vous aurez besoin dans votre projet, il est malheureusement obligatoire car c&#8217;est le fonctionnement même de Java EE qui l&#8217;impose. Cependant, la sortie de<a href="http://today.java.net/pub/a/today/2008/10/14/introduction-to-servlet-3.html"> Java EE 6 et des Servlets 3.0</a> va sûrement changer la donne, en privilégiant les annotations par rapport au XML !</p><h3>src/main/java/fr/chosesafaire/blog/controllers/IndexController.java</h3><p>Déclarons un contrôleur assez simple qui interceptera les requêtes :</p><pre class="brush: java;">
package fr.chosesafaire.blog.controllers;

import br.com.caelum.vraptor.Path;
import br.com.caelum.vraptor.Resource;
import br.com.caelum.vraptor.Result;

@Resource
public class IndexController {

    private final Result result;

    public IndexController(Result result) {
        this.result = result;
    }

    @Path("/{name}")
    public void index(String name) {
        result.include("name", name.toUpperCase());
    }

    @Path("/")
    public void index() {
        index("Anonymous");
    }
}
</pre><h3>src/main/webapp/jsp/index/index.jsp</h3><p>Lors de l&#8217;appel de votre méthode <strong>index </strong>de l&#8217;<strong>Index</strong>Controller, VRaptor va automatiquement associer le fichier <strong>index</strong>/<strong>index</strong>.jsp pour l&#8217;affichage :</p><pre class="brush: xml;">
&lt;%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %&gt;
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;meta http-equiv="Content-Type" content="text/html; charset=UTF-8"&gt;
        &lt;title&gt;Le blog des choses à faire&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;c:out value="${name}" /&gt; has been kicked by the Raptor (Aoutch !)
    &lt;/body&gt;
&lt;/html&gt;
</pre><h3>src/test/java/fr/chosesafaire/blog/controllers/IndexControllerTest.java</h3><p>Une application de qualité ne le serait pas vraiment si elle n&#8217;était pas accompagnée de sa batterie de tests unitaires <img src='http://www.chosesafaire.fr/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /></p><p>Dans le projet d&#8217;exemple, les tests d&#8217;intégration sont réalisés avec SeleniumDSL et HtmlUnit, et les tests unitaires avec JUnit, JMock et Hamcrest (pour les Matchers). Nous nous contenterons de simples tests unitaires pour compléter l&#8217;article, mais surtout regardez le projet d&#8217;exemple, qui exploite bien les possibilités de ces librairies de tests !</p><pre class="brush: java;">
package fr.chosesafaire.blog.controllers;

import static org.junit.Assert.assertThat;
import static org.hamcrest.Matchers.is;

import org.junit.Before;
import org.junit.Test;

import br.com.caelum.vraptor.util.test.MockResult;

public class IndexControllerTest {

    private IndexController controller;
    private MockResult result;

    @Before
    public void setUp() throws Exception {
        result = new MockResult();
        controller = new IndexController(result);
    }

    @Test
    public void indexWithParameter() throws Exception {
        controller.index("test");
        String name = result.included("name");
        assertThat(name, is("TEST"));
    }

    @Test
    public void indexWithoutParameter() throws Exception {
        controller.index();
        String name = result.included("name");
        assertThat(name, is("ANONYMOUS"));
    }
}
</pre><h3>Déploiement de l&#8217;application</h3><p>Un appel à maven-war-plugin vous permettra de compiler votre application dans un WAR :</p><pre class="brush: shell;">
mvn compile war:war
</pre><p>Si vous souhaitez faire une installation complète (lancer les tests unitaires etc&#8230;), vous pouvez utiliser la commande <strong>mvn clean install</strong>.</p><p>Enfin, déployez votre application dans votre serveur d&#8217;applications préféré. Si vous utilisez Tomcat par exemple, copiez juste votre WAR dans le répertoire <strong>$TOMCAT_HOME/webapps/</strong>, puis démarrez-le si ce n&#8217;est pas déjà fait (n&#8217;oubliez pas de déclarer TOMCAT_HOME !).</p><p>Pour éviter de s&#8217;embêter à redéplacer l&#8217;archive après chaque compilation, il est possible de spécifier une option au plugin (cela est déjà fait dans le pom.xml de l&#8217;article) :</p><pre class="brush: xml;">
...
&lt;configuration&gt;
...
    &lt;webappDirectory&gt;${env.TOMCAT_HOME}/webapps/${project.artifactId}&lt;/webappDirectory&gt;
...
&lt;/configuration&gt;
...
</pre><p>Vous pouvez aussi vous simplifier la tâche en utilisant directement le <a href="http://mojo.codehaus.org/tomcat-maven-plugin/deployment.html">plugin Tomcat</a>. Rendez-vous à l&#8217;adresse <a href="http://mojo.codehaus.org/tomcat-maven-plugin/deployment.html">http://localhost:8080/blog/</a> (ou là où vous l&#8217;avez déployé), puis sur <a href="http://localhost:8080/blog/chuck">http://localhost:8080/blog/chuck</a>.</p><h3>Encore une astuce Maven</h3><p>Si vous disposez de plusieurs processeurs, vous pouvez essayer de rendre les builds plus rapides avec une option de Maven :</p><pre class="brush: shell;">
# Lors de la compilation
mvn -Dmaven.artifact.threads=3 clean install

# Ou pour le rendre définitif
export MAVEN_OPTS=-Dmaven.artifact.threads=3
</pre><h3>Bonus : Gradle</h3><p>Si Maven vous rebute un peu, vous pouvez toujours tenter <a href="http://www.gradle.org/">Gradle</a>, un nouvel outil de build dans l&#8217;ère du temps. L&#8217;avantage d&#8217;écrire votre build en Groovy par rapport au XML est assez évident : plus léger, c&#8217;est un langage de script qui peut accéder aux classes Java, tout en apportant une syntaxe dynamique et simplifiée. La documentation de Gradle vous montrera mieux que moi toutes les possibilités offertes (d&#8217;ailleurs, Gradle sera étendu pour permettre d&#8217;écrire ses scripts en Ruby ou en Python&#8230; vraiment génial !).</p><p>A la racine de votre projet, créez un fichier <strong>build.gradle</strong> (l&#8217;équivalent du pom.xml, en plus concis !) :</p><pre class="brush: groovy;">
group = 'fr.chosesafaire'
version = '0.0.1'
sourceCompatibility = 1.5

usePlugin('war')

repositories {
    mavenCentral()
    flatDir name: 'localRepository', dirs: 'lib'
}

dependencies {
    compile 'org.aspectj:aspectjrt:1.6.4'
    compile 'cglib:cglib-nodep:2.2'
    compile 'commons-fileupload:commons-fileupload:1.2.1'
    compile 'commons-io:commons-io:1.3.2'
    compile 'commons-logging:commons-logging:1.1.1'
    compile 'com.google.collections:google-collections:1.0-rc2'
    compile 'javassist:javassist:3.8.0.GA'
    compile 'javax.servlet:jstl:1.2'
    compile 'log4j:log4j:1.2.12'
    compile 'net.vidageek:mirror:1.5.1'
    compile 'org.objenesis:objenesis:1.1'
    compile 'ognl:ognl:2.7.3'
    compile 'com.thoughtworks.paranamer:paranamer:2.1'
    compile 'org.reflections:reflections:0.9.4'
    compile 'org.slf4j:slf4j-api:1.5.6'
    compile 'org.slf4j:slf4j-log4j12:1.5.6'
    compile 'org.springframework:spring:2.5.6'
    compile 'br.com.caelum:vraptor:3.0.0'

    testCompile 'org.hamcrest:hamcrest-all:1.1'
    testCompile 'junit:junit:4.7'
}
</pre><p>Faîtes attention si vous êtes <a href="http://www.gradle.org/0.8/docs/userguide/tutorial_this_and_that.html#N1092A">derrière un proxy</a> sans l&#8217;indiquer, il ne pourra pas télécharger les dépendances. Gradle peut utiliser les dépôts Maven et Ivy ; pour nos deux archives introuvables, on peut simplement déclarer un repértoire (ici <strong>lib</strong>) qui servira de dépôt (pas besoin d&#8217;installer quoi que ce soit). Ainsi, si vous souhaitez récupérer vos librairies manuellement, il suffit d&#8217;indiquer à Gradle le répertoire, qu&#8217;il scannera automatiquement.</p><p>Pour compiler, lancez la commande :</p><pre class="brush: shell;">
# pour seulement compiler le war
gradle war

# pour la construction totale (tests etc)
gradle build
</pre><p>L&#8217;archive WAR se retrouvera dans <strong>build/libs/</strong> ; je n&#8217;ai pas encore repéré les options comme le nom du war généré et son emplacement. Dans la version actuelle de Gradle, certaines fonctionnalités ne sont pas encore bien implémentées (génération de rapports&#8230;), mais je ne doute pas (et j&#8217;espère !) que cet outil évoluera assez vite vers sa version finale.</p><h3>Conclusion</h3><p>Voilà, votre projet est opérationnel, et fonctionne avec Maven (ou Gradle), ce qui simplifiera tous vos builds futurs. Je n&#8217;ai pas vraiment montré les possibilités du framework, <a href="http://vraptor.caelum.com.br/documentation/vraptor3-one-minute-guide/">la documentation officielle</a> le fera bien mieux que moi, mais en tout cas, il simplifie énormément le travail d&#8217;un développeur Java : quasiment rien à configurer, les injections sont automatiques, les intercepteurs aussi&#8230; A surveiller de près !</p><p><strong>Avez-vous déjà utilisé ce framework dans une précédente version ? Que vaut-il face à Spring MVC ou Struts 2 ?</strong></p><div class="sexy-bookmarks sexy-bookmarks-center"><ul class="socials"><li class="sexy-blogmarks"> <a href="http://blogmarks.net/my/new.php?mini=1&amp;simple=1&amp;url=http://www.chosesafaire.fr/2009/10/vraptor-le-framework-qui-a-les-crocs/&amp;title=VRaptor%2C+le+framework+qui+a+les+crocs" rel="nofollow" title="Marquez-le sur BlogMarks">Marquez-le sur BlogMarks</a></li><li class="sexy-google"> <a href="http://www.google.com/bookmarks/mark?op=add&amp;bkmk=http://www.chosesafaire.fr/2009/10/vraptor-le-framework-qui-a-les-crocs/&amp;title=VRaptor%2C+le+framework+qui+a+les+crocs" rel="nofollow" title="Ajoutez-le à Google Bookmarks">Ajoutez-le à Google Bookmarks</a></li><li class="sexy-delicious"> <a href="http://del.icio.us/post?url=http://www.chosesafaire.fr/2009/10/vraptor-le-framework-qui-a-les-crocs/&amp;title=VRaptor%2C+le+framework+qui+a+les+crocs" rel="nofollow" title="Partagez-le sur del.icio.us">Partagez-le sur del.icio.us</a></li><li class="sexy-reddit"> <a href="http://reddit.com/submit?url=http://www.chosesafaire.fr/2009/10/vraptor-le-framework-qui-a-les-crocs/&amp;title=VRaptor%2C+le+framework+qui+a+les+crocs" rel="nofollow" title="Partagez-le sur Reddit">Partagez-le sur Reddit</a></li><li class="sexy-stumbleupon"> <a href="http://www.stumbleupon.com/submit?url=http://www.chosesafaire.fr/2009/10/vraptor-le-framework-qui-a-les-crocs/&amp;title=VRaptor%2C+le+framework+qui+a+les+crocs" rel="nofollow" title="Tomber sur un bon truc ? Partagez cet article sur StumbleUpon">Tomber sur un bon truc ? Partagez cet article sur StumbleUpon</a></li><li class="sexy-technorati"> <a href="http://technorati.com/faves?add=http://www.chosesafaire.fr/2009/10/vraptor-le-framework-qui-a-les-crocs/" rel="nofollow" title="Partagez-le sur Technorati">Partagez-le sur Technorati</a></li><li class="sexy-twitter"> <a href="http://twitter.com/home?status=VRaptor%2C+le+framework+qui+a+les+crocs+-+http://tinyurl.com/yf5k2g9+(via+@dandelionmood)" rel="nofollow" title="Tweetez-le !">Tweetez-le !</a></li><li class="sexy-comfeed"> <a href="http://www.chosesafaire.fr/2009/10/vraptor-le-framework-qui-a-les-crocs/feed" rel="nofollow" title="S'abonner aux commentaires de cet article ?">S'abonner aux commentaires de cet article ?</a></li></ul><div style="clear:both;"></div></div><p>Articles liés :</p><ol><li><a href='http://www.chosesafaire.fr/2010/03/easyb-behavior-driven-development/' rel='bookmark' title='Permanent Link: Easyb, Behavior Driven Development'>Easyb, Behavior Driven Development</a></li><li><a href='http://www.chosesafaire.fr/2009/11/lucene-scala-et-spring/' rel='bookmark' title='Permanent Link: Lucene, Scala et Spring'>Lucene, Scala et Spring</a></li></ol></p>]]></content:encoded> <wfw:commentRss>http://www.chosesafaire.fr/2009/10/vraptor-le-framework-qui-a-les-crocs/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>Awk : Premiers pas</title><link>http://www.chosesafaire.fr/2009/10/awk-premiers-pas/</link> <comments>http://www.chosesafaire.fr/2009/10/awk-premiers-pas/#comments</comments> <pubDate>Mon, 26 Oct 2009 06:00:06 +0000</pubDate> <dc:creator>Pierre Quillery</dc:creator> <category><![CDATA[Développement]]></category> <category><![CDATA[awk]]></category> <category><![CDATA[csv]]></category> <category><![CDATA[script]]></category> <category><![CDATA[unix]]></category> <category><![CDATA[xml]]></category><guid isPermaLink="false">http://www.chosesafaire.fr/?p=181</guid> <description><![CDATA[Awk est un vieil outil *nix qui peut, aujourd'hui encore, nous rendre bien des services pour ce qui est de manipuler des fichiers de données tabulaires, ce qu'il fait à la perfection. Voyons dans cet article comment approcher la programmation de cette commande.Aucun article lié]]></description> <content:encoded><![CDATA[<p>Awk est un vieil outil *nix qui peut, aujourd&#8217;hui encore, nous rendre bien des services pour ce qui est de manipuler des fichiers de données tabulaires, ce qu&#8217;il fait à la perfection. Voyons dans cet article comment approcher la programmation de cette commande.</p><p><span id="more-181"></span></p><p>Il y a quelques jours, j&#8217;ai pu constater dans le cadre de mon travail l&#8217;efficacité et la rapidité de cet outil : nous disposons d&#8217;un flux qui nous est mis à disposition par un partenaire ; à nous de le récupérer, et de le rendre &laquo;&nbsp;intégrable&nbsp;&raquo; dans la base MySQL.</p><p>Au départ la solution développée ne reposait pas du tout sur ces commandes, mais quasi-exclusivement sur des feuilles de traitement XSLT. Le traitement durait alors près de 36 minutes pour chaque flux, ce qui rendait les tests et les modifications difficiles.</p><p>Il a donc été décidé de refondre ces traitements en employant Awk. Après quelques optimisations, le script, écrit en très peu de temps, a permis de descendre autour de <strong>quelques secondes de traitement</strong>.</p><h3>Un peu d&#8217;histoire</h3><p>Awk est un très vieil outil *nix, il est apparu pour la première fois en &#8230; 1987 sur System V. Il a été révisé plusieurs fois et existe en plusieurs moutures sur toutes les distributions : awk, gawk, nawk &#8230; C&#8217;est à peu près le même combat, à quelques différences près (plus de fonctions sont disponibles dans gawk, par exemple). <strong>En un mot, il permet de faciliter le traitement de fichiers textes contenant des données tabulaires.</strong></p><h3>Awk ?</h3><p>Il s&#8217;agit d&#8217;un véritable langage de programmation miniature qui contient tout ce dont vous avez besoin pour manipuler des chaînes et, à l&#8217;intérieur, des champs. <strong>Le principe de base est que vous ciblez une ligne (ou un groupe de ligne, ou toutes les lignes) à l&#8217;aide d&#8217;une expression régulière et que vous appliquez sur chacune des lignes un traitement particulier. Vous avez également pour compléter ce modèle de traitement la possibilité de définir un traitement à effectuer une seule fois au début (<em>BEGIN</em>) et à la fin du script (<em>END</em>).</strong></p><p>L&#8217;avantage d&#8217;utiliser Awk plutôt qu&#8217;un autre langage de script n&#8217;est pas qu&#8217;une question de performances : grâce à lui et son intégration à *nix, vous bénéficiez également de la possibilité d&#8217;utiliser des <em>pipes</em> et autres <em>redirections de flux</em>.</p><h3>Exemple d&#8217;utilisation : un CSV vers un tableau HTML</h3><p>Plutôt que de transformer un fichier XML en données CSV, je me propose plutôt de montrer ici comment convertir de la façon la plus générique possible un fichier CSV vers un tableau HTML.</p><p>Voici tout d&#8217;abord la ligne qui me permet d&#8217;invoquer la commande Awk en précisant le fichier de script à utiliser (<em>csvtohtml.awk</em>) ainsi le fichier sur lequel il doit travailler (<em>donnees.csv</em>). Enfin, je redirige la sortie standard vers le fichier <em>donnees.html</em>.</p><pre class="brush: bash;">gawk -f csvtohtml.awk donnees.csv &gt; donnees.html</pre><p>Rien de bien sorcier jusqu&#8217;ici, reste maintenant à définir le contenu de notre fichier de script, <em>csvtohtml.awk</em> qui va effectuer le fameux traitement.</p><pre class="brush: bash;">############################################################
# Définition du pré-traitement
############################################################
BEGIN {
  # Field Separator : dans notre cas, un point-virgule
  FS=&quot;;&quot;
  # On démarre l'affichage du tableau HTML
  print &quot;&lt;table&gt;&quot;
}

############################################################
# Fonction pour convertir une ligne du fichier en une ligne
# de tableau HTML.
# @param type (td/th) Suivant le type de ligne à générer
############################################################
function ligne_html(type) {
  retour = &quot;&lt;tr&gt;&quot;
  # Pour chaque champ de la ligne courante ...
  for ( i = 1 ; i &lt; NF ; i++ ) {
    # On récupère ce qui se trouve entre les guillemets
    # qui encapsulent éventuellement le champ
    # On place le résultat dans le tableau r.
    match($i,/^[\&quot;]?([^\&quot;]*)/,r)
    # On concatène tout ça à la variable de retour
    # Le champ 1 de r contient ce qu'on a trouvé 
    # précédemment.
    retour = retour &quot;&lt;&quot; type &quot;&gt;&quot; r[1] &quot;&lt;/&quot; type &quot;&gt;&quot;
  }
  return retour &quot;&lt;/tr&gt;&quot;
}

###########################################################
# Définition du traitement global 
# (appliqué à toutes les lignes)
###########################################################
{
  # Si le Number of Record est à un, il nous faut traiter
  # le case de la première ligne, celle qui contient les 
  # entêtes.
  if ( NR == 1 ) {
    # On génère une ligne de table headers et on ouvre
    # le corps du tableau.
    print &quot;&lt;thead&gt;&quot; ligne_html(&quot;th&quot;) &quot;&lt;/thead&gt;&lt;tbody&gt;&quot;
  } else {
    # Pour toutes les autres lignes, on génère des td.
    print ligne_html(&quot;td&quot;)
  }
}

############################################################
# Définition du post-traitement
############################################################
END {
  # On termine le tableau ... C'est fini :) !
  print &quot;&lt;/tbody&gt;&lt;/table&gt;&quot;
}
</pre><p>Si vous souhaitez approfondir le sujet, je ne peux que vous diriger sur le petit opuscule <a href="http://www.amazon.fr/Sed-Awk-Reference-Arnold-Robbins/dp/0596003528/ref=sr_1_1?ie=UTF8&#038;s=english-books&#038;qid=1256375484&#038;sr=8-1">&laquo;&nbsp;Sed &amp; Awk (2nd Edition)&nbsp;&raquo; de Arnold Robbis</a>, publié chez O&#8217;Reilly (vous pouvez l&#8217;avoir sur Amazon pour 5€ avec les frais de port, c&#8217;est une affaire).</p><div class="sexy-bookmarks sexy-bookmarks-center"><ul class="socials"><li class="sexy-blogmarks"> <a href="http://blogmarks.net/my/new.php?mini=1&amp;simple=1&amp;url=http://www.chosesafaire.fr/2009/10/awk-premiers-pas/&amp;title=Awk+%3A+Premiers+pas" rel="nofollow" title="Marquez-le sur BlogMarks">Marquez-le sur BlogMarks</a></li><li class="sexy-google"> <a href="http://www.google.com/bookmarks/mark?op=add&amp;bkmk=http://www.chosesafaire.fr/2009/10/awk-premiers-pas/&amp;title=Awk+%3A+Premiers+pas" rel="nofollow" title="Ajoutez-le à Google Bookmarks">Ajoutez-le à Google Bookmarks</a></li><li class="sexy-delicious"> <a href="http://del.icio.us/post?url=http://www.chosesafaire.fr/2009/10/awk-premiers-pas/&amp;title=Awk+%3A+Premiers+pas" rel="nofollow" title="Partagez-le sur del.icio.us">Partagez-le sur del.icio.us</a></li><li class="sexy-reddit"> <a href="http://reddit.com/submit?url=http://www.chosesafaire.fr/2009/10/awk-premiers-pas/&amp;title=Awk+%3A+Premiers+pas" rel="nofollow" title="Partagez-le sur Reddit">Partagez-le sur Reddit</a></li><li class="sexy-stumbleupon"> <a href="http://www.stumbleupon.com/submit?url=http://www.chosesafaire.fr/2009/10/awk-premiers-pas/&amp;title=Awk+%3A+Premiers+pas" rel="nofollow" title="Tomber sur un bon truc ? Partagez cet article sur StumbleUpon">Tomber sur un bon truc ? Partagez cet article sur StumbleUpon</a></li><li class="sexy-technorati"> <a href="http://technorati.com/faves?add=http://www.chosesafaire.fr/2009/10/awk-premiers-pas/" rel="nofollow" title="Partagez-le sur Technorati">Partagez-le sur Technorati</a></li><li class="sexy-twitter"> <a href="http://twitter.com/home?status=Awk+%3A+Premiers+pas+-+http://tinyurl.com/yfyjmbv+(via+@dandelionmood)" rel="nofollow" title="Tweetez-le !">Tweetez-le !</a></li><li class="sexy-comfeed"> <a href="http://www.chosesafaire.fr/2009/10/awk-premiers-pas/feed" rel="nofollow" title="S'abonner aux commentaires de cet article ?">S'abonner aux commentaires de cet article ?</a></li></ul><div style="clear:both;"></div></div><p>Aucun article lié</p>]]></content:encoded> <wfw:commentRss>http://www.chosesafaire.fr/2009/10/awk-premiers-pas/feed/</wfw:commentRss> <slash:comments>0</slash:comments> </item> </channel> </rss>