Grails : un générateur de CRUD simple et performant ?

Grails est un framework Open-Source basé sur le langage Groovy et permettant d’effectuer du scaffolding, c’est à dire de générer automatiquement le code source du CRUD (Create Read Update Delete) des données en base pour l’application.

Cet article à été écrit en même temps que la réalisation d’une application Grails. Le but de la conception de cette application était de démontrer que Grails permet de générer un prototype fonctionnel et performant avec un minimum de développement.

L’application finale est disponible à l’adresse suivante : https://github.com/ippontech/grails-demo
A chaque étape de ce post, vous pourrez récupérer la version en cours de construction, et comparer ses changements par rapport à l’étape précédente. La base de données utilisée est Postgres SQL, mais vous pouvez bien entendu en utiliser une autre.

Grails possède de nombreux atouts qui font que nous avons choisi de travailler avec :

  • Applications réalisés en très peu de temps,
  • Simplicité du développement et de la réalisation d’une application,
  • La génération automatique du code source permettent d’améliorer la productivité du développeur,
  • Il respecte le principe Conventions over Configurations, puisqu’il propose des comportement par défaut pour ses fonctionnalités,
  • Il respecte aussi le principe Don’t Repeat Yourself avec le code automatiquement généré mais aussi la mise en place du MVC,
  • Bonne gestion des dépendances et plugins de l’application avec Maven.

Ce framework possède aussi quelques points négatifs :

  • Debug : les stacktrace de certaines erreurs ne sont pas toujours compréhensibles,
  • Tests unitaires : pour effectuer des tests, la syntaxe est très particulière, et pas évidente à appréhender au premier abord.

1 – Prérequis

Pour utiliser Grails, vous devez :

Si vous souhaitez utiliser un IDE pour développer, vous pouvez le configurer ou ajouter un plugin en suivant les indications suivantes : http://grails.org/doc/latest/guide/gettingStarted.html#ide

2 – Création d’une nouvelle application Grails

Pour créer une nouvelle application Grails, vous pouvez lancer la commande suivante dans un terminal :

grails create-app grails-demo

Cette commande va créer automatiquement toute la structure de votre projet, avec notamment un fichier .project et .classpath qui vous permettra de l’importer dans votre IDE.

Dans la structure de base d’un projet Grails, nous avons :

Structure d'un projet Grails

  • Les domaines dans grails-app/domain qui sont des classes qui permettent de configurer les tables et les colonnes de la base de données de l’application mais aussi l’organisation de l’interface. Les contrôleurs et les vues sont générés à partir de ces classes.
  • Les contrôleurs dans grails-app/controller qui font le lien entre les domaines et les vues

  • Les vues dans grails-app/views qui représentent les éléments de l’interface

  • Le dossier i18n contient des fichiers messages.properties qui nous permettront de modifier les libellés des champs de l’interface

  • Le dossier grails-app/conf contient tous les éléments pour configurer son application (DataSource, BuildConfig,…)

  • test/unit contient toutes les classes des tests JUnit.

A l’issue de la création du projet, voici la structure que vous obtiendrez de votre projethttps://github.com/ippontech/grails-demo/tree/0822c467cfb8ff62ee56e843ebd392dd0c220554

3 – Configuration de la DataSource

Pour commencer, nous avons modifié le fichier grails-app/conf/BuildConfig.groovy, pour ajouter dans le bloc dependencies le driver nécessaire pour la connection à PostgreSQL. Les dépendances et plugins dans Grails sont gérés avec Maven, il télécharge les sources nécessaires lorsque Grails recompile ses sources.

dependencies {
 
  /**
    * Ajout du driver JDBC PostgreSQL
    */
     runtime ‘org.postgresql:postgresql:9.3-1100-jdbc3’
}

Ensuite, pour configurer une nouvelle connexion à une base de données, vous pouvez modifier le fichier de DataSource dans grails-app/conf/DataSource.groovy.

Dans ce fichier, il faut déclarer le driver correspondant à votre base de donnée dans le bloc dataSource. Dans ce même bloc, nous pouvons déclarer, s’ils sont équivalents sur tous les environnements, les username et password de connection à la base. Dans le cas où ils seraient différents, il faut les déclarer dans le bloc d’environnement correspondant.

dataSource {
    pooled = true
 
/**
* Ajout du driver PostgreSQL pour la connection à la base
* Il ne faut pas oublier de rajouter le fichier .jar du driver PostgreSQL
* dans le dossier lib/ à la racine du projet Grails.
*/
    driverClassName = “org.postgresql.Driver”
    dialect = org.hibernate.dialect.PostgreSQLDialect
/**
* L’ajout du username et du password dans cette section permet de les spécifier qu’une
* seule fois dans le fichier pour tous les environnemments.
*/
    username = “postgres”
    password = “test”
}
 
environments {
    development {
        dataSource {
 
        /**
* Lors de la génération du fichier, dbCreate = “create-drop” est automatiquement
* ajoutée et efface toutes les données de la base lors de chaque démarrage
* de l’application. L’ajout de dbCreate = “update” permet de ne pas les effacer.
*/
            dbCreate = “update”
 
            // Ajout de l’URL de la base PostgreSQL grails-demo.
            url = “jdbc:postgresql://localhost:5432/grails-demo”
        }
    }
}

Dans le bloc environments, l’URL de connection à la base doit être spécifié par environnement dans le dataSource.

Vous pouvez dès à présent lancer le serveur de l’application Grails avec la commande suivante :

grails run-app

Ensuite, pour se connecter à l’interface, il faut aller sur l’URL http://localhost:8080/grails-demo/ (où grails-demo est le nom de votre application).

Différences avec l’étape précédentehttps://github.com/ippontech/grails-demo/commit/ab1151bf771e284b09686a2e8b8be3b4960af330

Code du projet à la fin de cette étapehttps://github.com/ippontech/grails-demo/tree/ab1151bf771e284b09686a2e8b8be3b4960af330

4 – Création d’un nouveau “scaffold”

Un scaffold (“échaffaudage” en Français) est une technique permettant de générer tout le code source d’un CRUD à partir de quelques classes métiers. Sur Grails, la classe métier qui permet de générer tout le code source s’appelle un domain.

Il peut y avoir deux types de scaffolding :

  • Statique : le CRUD est généré dynamiquement à partir de la classe domain en lançant des commandes comme “grails generate-controller” ou “grails generate-views”
  • Dynamique : le CRUD est généré automatiquement lors du lancement de l’application. Il suffit pour cela de rajouter le code “static scaffold = true” dans la classe domaine.

Dans le projet de démonstration et dans cet article, nous partirons sur du scaffolding statique, pour bien analyser étape par étape la structure et les codes sources générés.

Pour le moment, l’interface du projet que nous avons crée dans la première partie comporte que la structure de base d’une application Grails, sans les fonctionnalités permettant de faire du CRUD. C’est pour cela que nous allons créer notre premier scaffold, dont le code source généré va créer une nouvelle table en base de données ainsi que les fonctions du CRUD qui va nous permettre d’interagir avec elle. Une précision toutefois, lorsque l’application sera de nouveau lancée, la table dans la base de donnée ne sera crée que si la base de donnée à déjà été crée auparavant.

Pour la création de ce scaffold, nous devons dans un premier temps ajouter un domaine, qui en est la base. Pour faire ceci, il faut lancer, en prenant l’exemple de notre projet de démonstration, la commande grails create-domain-class fr.ippon.demo.grails.Contact. La classe /grails-app/domain/fr/ippon/demo/grails/Contact.groovy est automatiquement crée.

Dans ce domaine, vous pouvez ajouter des attributs, qui seront les colonnes de la nouvelle table qui portera le nom de la classe de domaine (dans notre cas contact). Grails va créer, lors de la génération des vues, des champs pour chaque attribut.

class Contact {
        /**
* Les propriétés ci-dessous référencent les colonnes
* qui vont être créées dans la base de données lors
* du lancement de l’application. Elle permettent
* aussi de déclarer les champs qui seront affichés
* sur l’interface de l’application.
*/
    String last_name
    String first_name
    String email_address
}

La déclaration constraints permet de définir plusieurs éléments de configurations liés aux champs de l’interface :

  • elle permet de définir l’ordre d’apparition des champs :

static constraints = {

/**
* Je spécifie l’ordre d’affichage des champs dans
* l’interface qui sera automatiquement générée par Grails.
*/
last_name()
first_name()
email_address()

}

Dans cette exemple, le champ last_name apparaîtra en premier sur la fiche d’un Contact alors que email_address apparaîtra en dernier.

  • de configurer des champs comme non obligatoire, puisque par défaut, chaque champ est obligatoire :

static constraints = {

/**
* first_name pourra être null lors de la création d’une nouvelle
* company. Le champ n’est donc pas obligatoire.
*/
last_name()
first_name(nullable: true)

}

Le champ first_name pourra, en spécifiant nullable:true être non obligatoire.

  • de définir différents formatages concernant la saisie d’un champ :

static constraints = {

/**
* Le last_name ne peut pas être vide, et doit commencer
* par un majuscule
*/
last_name(blank: false, matches: /[A-Z].*/)
first_name(nullable: true)

}

Ensuite, vous pouvez générer le contrôleur pour le domaine Contact avec la commande grails generate-controller fr.ippon.demo.grails.Contact. La nouvelle classe du controller crée sera /grails-app/controller/fr/ippon/demo/grails/ContactController.groovy. Vous pourrez remarquer les méthodes du CRUD dont le code source est automatiquement généré par Grails dans la classe.

Enfin, pour générer les vues qui se basent sur le domaine et le controlleur, vous pouvez lancer la commande :

grails generate-views fr.ippon.demo.grails.Contact

Il générera 5 fichiers avec l’extension gsp dans le dossier /grails-app/views/ :

  • _form.gsp qui contient l’affichage des champs utilisés dans les vues create.gsp et edit.gsp,
  • create.gsp correspond au formulaire de création du domaine,
  • edit.gsp affiche le formulaire d’édition du domaine,
  • index.gsp est l’accueil du domaine, elle permet d’avoir une liste des enregistrements de la base de données,
  • show.gsp correspond à la fiche d’un enregistrement du domaine.

Vous pouvez à nouveau lancer votre application avec la commande grails run-app et constater sur l’application que les interfaces CRUD d’un contact ont été crée. Vous pouvez maintenant effectuer tout type d’action sur un contact qui sera automatiquement enregistré en base de données.

Affichage de la liste des contacts

Affichage de la liste des contacts

Interface pour la création d'un nouveau contact

Interface pour la création d’un nouveau contact

Exemple de fiche pour un contact

Exemple de fiche pour un contact

Différences avec l’étape précédentehttps://github.com/ippontech/grails-demo/commit/4733a2a74ca39ab7610e1387ac0ffb9513b58c95

Code du projet à la fin de cette étapehttps://github.com/ippontech/grails-demo/tree/4733a2a74ca39ab7610e1387ac0ffb9513b58c95

5 – Création d’un scaffold en relation avec “Contact”

Nous allons créer un deuxième scaffold Company, qui sera en relation avec notre premier scaffold Contact. En effet, nous allons faire en sorte de pouvoir spécifier, pour une entreprise, une multitude de contacts.

Dans un premier temps, il faut créer un domaine Company (grails create-domain-class fr.ippon.demo.grails.Company). Pour spécifier une relation entre Company et Contact de type one-to-many, il faut ajouter dans le domaine une déclaration static hasMany = [contacts: Contact], dans laquelle vous spécifiez une liste contacts de type Contact. A partir de cette déclaration, quand le controlleur sera généré, il prendra en compte automatiquement la relation entre les deux tables.

Nous avons crée, dans le projet de démonstration, une énumération (à créer avec le menu contexctuel de votre IDE), ActivityType, pour visualiser l’intégration d’une classe de ce type dans un domaine. Cette énumération recense des domaines dans lesquelles des entreprises peuvent effectuer leur activité tel que la restauration ou l’informatique par exemple. Pour l’intégrer à l’interface et pouvoir affecter ses valeurs à des entreprises, il faut déclarer une propriété de type ActivityType.

class Company {
    /**
* Les propriétés ci-dessous référencent les colonnes
* qui vont être créées dans la base de données lors
* du lancement de l’application. Elle permettent
* aussi de déclarer les champs qui seront affichés
* sur l’interface de l’application.
*/
    String company_name
    ActivityType activityType
    /**
* Permet de déclarer une relation one-to-many avec
* le domaine Contact.
*/
    static hasMany = [contacts: Contact]
    /**
* Cette section permet de spécifier des contraintes
* liées à l’ajustement des champs sur l’interface.
*/
    static constraints = {
        /**
* Je spécifie l’ordre d’affichage des champs dans
* l’interface qui sera automatiquement générée par Grails.
*/
        company_name()
        activityType()
    }
    /**
* Cette méthode permet d’indiquer que sur l’interface,
* nous voulons que les company soient affiché avec son nom, et non
* avec le package du domaine et l’id de la company.
*/
    def String toString(){
        return “${company_name}”
    }
}

Enfin, une méthode def String toString() à été définie dans le domaine Company. Par défaut, les domaines qui sont liés sont affichés sur l’interface par le package du domaine ainsi que l’id de l’enregistrement. En redéfinissant la méthode toString, on peut modifier le contenu de cet affichage. Avec le domaine Company, nous avons décidé, sur le projet de démonstration, d’afficher le nom de l’entreprise.

Pour finir, le domaine Contact à aussi été modifié. La propriété Company company à été ajoutée pour qu’une entreprise soit affichée (et modifiable) sur la fiche d’un contact et sur la liste des contacts. La méthode toString à été redéfinie, comme pour le domaine Company pour afficher le nom et le prénom du contact sur la fiche des entreprises.

 class Contact {

    /**
* Les propriétés ci-dessous référencent les colonnes
* qui vont être créées dans la base de données lors
* du lancement de l’application. Elle permettent
* aussi de déclarer les champs qui seront affichés
* sur l’interface de l’application.
*/
    String last_name
    String first_name
    String email_address
    /**
* En ajoutant la section belongsTo, les ajouts, modifications
* et suppressions de données se feront en cascade.
*/
    Company company
    /**
* Permet de mettre en place des actions en cascade pour
* les deux domaines. De ce fait, si une company est supprimé, ajoutée,
* ou modifié, cela impactera les contacts associés.
*/
    static belongsTo = Company
    /**
* Cette section permet de spécifier des contraintes
* liées à l’ajustement des champs sur l’interface.
*/
    static constraints = {
        /**
* Je spécifie l’ordre d’affichage des champs dans
* l’interface qui sera automatiquement générée par Grails.
*/
        last_name()
        first_name()
        email_address()
    }
    /**
* Cette méthode permet d’indiquer que sur l’interface,
* nous voulons que les contacts dans l’interface concernant
* la company soient affiché avec le prénom et le nom, et non
* avec le package du domaine et l’id du contact.
*/
    def String toString(){
        return “${first_name}, ${last_name}”
    }
}

Il ne vous reste plus qu’a générer les controlleurs et les vues de Company et Contact, pour ajouter toutes les modifications qui ont été apportés et à lancer la commande “run-app” pour lancer le serveur de l’application.

Affichage de la liste des company

Affichage de la liste des company

Affectation d'un contact à une entreprise

Affectation d’un contact à une entreprise

Affichage de la liste des contacts avec les entreprises auxquels ils sont affectés.

Affichage de la liste des contacts avec les entreprises auxquels ils sont affectés.

Différences avec l’étape précédentehttps://github.com/ippontech/grails-demo/commit/ca70738eb2b35df2997300aadf41cd4a684b470c

Code du projet à la fin de cette étapehttps://github.com/ippontech/grails-demo/tree/ca70738eb2b35df2997300aadf41cd4a684b470c

6 – Ajout de données de test avec la classe Bootstrap

Pour ajouter automatiquement des enregistrements dans la base de données au démarrage du serveur de l’application, il est possible de définir un jeu de test dans la classe grails-app/conf/Bootstrap.groovy.

Pour commencer, vous pouvez importer, tout en haut de la classe, les différents domaines dans lesquels vous souhaitez insérer des données (par exemple : import fr.ippon.demo.grails.Contact). Vous pourrez ensuite utiliser les méthodes et les constructeurs de chacun de ces domaines pour pouvoir interagir avec la base de données.

Les données à insérer doivent être définis dans la méthode init de la classe, vous pouvez faire appel à des méthodes que vous avez définis vous-même en dessous.

Vous pouvez définir pour quel environnement les données doivent être ajoutés. Pour faire ceci, il vous faut ajouter un bloc environments dans lequel nous pouvons définir avec plusieurs bloc à quel environnement nous faisons référence et quel code doit être exécuté.

Vous pouvez, comme sur le projet de démonstration, utiliser une condition afin que les données ne soient pas ajoutés lorsqu’elles sont déjà insérées dans la base. Il vous suffit pour cela d’effectuer le test suivant : if(!Company.count() && !Contact.count()).

class BootStrap {
    /**
* C’est dans cette méthode qu’on initialise les données. Dans cet exemple,
* les données sont ajoutés que si le projet est lancé en environnement
* de développement.
*/
    def init = { servletContext ->
        environments {
            development {
                /**
* Les modifications en base de données se feront si les
* tables Company et Contact sont vides.
*/
                if(!Company.count() && !Contact.count()){
                    /**
* Appel à chaque méthode qui ajoute des données
* par entreprise
*/
                    createIpponData()
                    createCulturaData()
                    createCompanyWithoutContact()
                    createContactWithoutCompany()
                }
            }
        }
    }

Pour insérer des données, vous pouvez utiliser le constructeur du domaine dans lequel vous souhaitez ajouter des données. Ensuite, à chaque propriété, vous pouvez insérer des données comme dans l’exemple ci-dessous. Prenez garde à ne pas oublier de définir des valeurs pour les attributs qui sont notés obligatoires dans la déclaration “constraint”. Si vous oubliez de définir des attributs obligatoires, aucune donnée ne sera insérée pour ce constructeur.

/**
* Cette méthode permet de créer la société Ippon avec ses contacts associés.
*/
    void createIpponData(){
        Company ippon = new Company(company_name: “Ippon Technologies”, activityType: ActivityType.Informatique)
        ippon.save()
        new Contact(last_name: “Morgan”, first_name: “Uriah”, email_address: “morgan@ippon.fr”, company: ippon).save()
        new Contact(last_name: “Willis”, first_name: “Carson”, email_address: “willis@ippon.fr”, company: ippon).save()
        new Contact(last_name: “Bentley”, first_name: “Imani”, email_address: “bentley@ippon.fr”, company: ippon).save()
    }

Comme vous pouvez le voir ci-dessus, il est aussi possible, en affectant une instance de type Company à une variable, de lier l’entreprise à un contact.

Lorsque vous avez fini de définir les données que vous souhaitez ajouter, il vous suffit de lancer la commande run-app pour insérer les nouveaux enregistrements et par la suite démarrer le serveur de l’application.

Différences avec l’étape précédentehttps://github.com/ippontech/grails-demo/commit/8e5bdff36535d498acafd39c6a2a45d22b3acd12

Code du projet à la fin de cette étapehttps://github.com/ippontech/grails-demo/tree/8e5bdff36535d498acafd39c6a2a45d22b3acd12

7 – Installation du plugin Filterpane

Filterpane est un plugin permettant d’insérer, dans l’interface d’une application Grails, un système de filtre de recherche dans les données.

Pour l’installer, il vous faut ajouter dans la liste des plugins, dans le fichier grails-app/conf/BuildConfig.groovy, la commande compile “:filterpane:2.2.5”. Lorsque Grails recompilera ses sources, le plugin filterpane sera ajouté aux plugins de l’application.

Pour la mise en place, vous pouvez directement modifier les templates des controlleurs et des vues. Pour installer les templates, vous devez effectuer la commande suivante : grails install-templates. De ce fait, tous les controlleurs et vues que vous allez générer comporteront déjà l’installation de filterpane. Ces deux fichiers se trouvent dans /src/templates/scaffolding :

  • Controller.groovy
  • index.gsp

Sinon, si vous souhaitez que le filterpane ne soit que sur un seul domaine, vous devez modifier le controlleur et la vue de ce domaine. L’inconvénient est que lorsque vous aller générer de nouveau les controlleur et vues de ce domaine, vos modifications seront supprimées.

Dans le code du controlleur, vous devez ajouter def filterPaneService entre la déclaration de la classe et la déclaration static allowedMethods pour injecter le service filter. Ensuite, vous devez ajouter la méthode suivante, qui permet d’envoyer les paramètres dont à besoin la vue pour afficher la recherche filterpane (dans l’exemple suivant, cette méthode à été faite pour le domaine Contact) :

 /**
* On injecte le service pour filter les
* données.
*/
def filterPaneService

def filter() {

if(!params.max) params.max = 10

render( view:’index’, model:[ contactInstanceList: filterPaneService.filter( params, Contact ),
contactInstanceTotal: filterPaneService.count( params, Contact ),
filterParams: org.grails.plugin.filterpane.FilterPaneUtils.extractFilterParams(params),
params:params ] )

}

Vous devez maintenant regénérer vos vues (grails generate-views fr.ippon.demo.grails.Contact). Ensuite, vous devez modifier le fichier index.gsp du domaine Contact :

  • <r:require module=”filterpane” /> doit être inclus dans la balise du fichier pour ajouter le code Javascript et le CSS de filterpane,
  • params=”${filterParams}” doit être ajouté comme attribut dans la balise dans la div si vous souhaitez que la pagination soit supporté
  • Toujours dans la même div, après la balise , il faut ajouter la balise <filterpane:filterButton /> pour ajouter le bouton qui permettra d’afficher la zone de recherche,
  • Ajouter <filterpane:filterPane domain=”fr.ippon.demo.grails.Contact” /> où vous le souhaitez dans la structure de votre page pour afficher le panel de filtre de recherche.
<%@ page import=”fr.ippon.demo.grails.Company” %>
<!DOCTYPE html>
<html>
        <head>
                <meta name=”layout” content=”main”>
                <g:set var=”entityName” value=”${message(code: ‘company.label’, default: ‘Company’)}” />
                <title><g:message code=”default.list.label” args=”[entityName]” /></title>
                <!– Cette balise permet d’inclure le Javascript et les css concernant filterpane. –>
                <r:require module=”filterpane” />
        </head>
        <body>
                <a href=”#list-company” class=”skip” tabindex=”-1″><g:message code=”default.link.skip.label” default=”Skip to content&hellip;”/></a>
                <div class=”nav” role=”navigation”>
                        <ul>
                                <li><a class=”home” href=”${createLink(uri: ‘/’)}”><g:message code=”default.home.label”/></a></li>
                                <li><g:link class=”create” action=”create”><g:message code=”default.new.label” args=”[entityName]” /></g:link></li>
                        </ul>
                </div>
                <div id=”list-company” class=”content scaffold-list” role=”main”>
                        <h1><g:message code=”default.list.label” args=”[entityName]” /></h1>
                        <g:if test=”${flash.message}”>
                                <div class=”message” role=”status”>${flash.message}</div>
                        </g:if>
                        <table>
                        <thead>
                                        <tr>
                                                <g:sortableColumn property=”company_name” title=”${message(code: ‘company.company_name.label’, default: ‘Companyname’)}” />
                                                <g:sortableColumn property=”activityType” title=”${message(code: ‘company.activityType.label’, default: ‘Activity Type’)}” />
                                        </tr>
                                </thead>
                                <tbody>
                                <g:each in=”${companyInstanceList}” status=”i” var=”companyInstance”>
                                        <tr class=”${(i % 2) == 0 ? ‘even’ : ‘odd’}”>
                                                <td><g:link action=”show” id=”${companyInstance.id}”>${fieldValue(bean: companyInstance, field: “company_name”)}</g:link></td>
                                                <td>${fieldValue(bean: companyInstance, field: “activityType”)}</td>
                                        </tr>
                                </g:each>
                                </tbody>
                        </table>
                        <div class=”pagination”>
                                <!– On ajoute le paramètre “filterParams” pour supporter la pagination –>
                                <g:paginate total=”${companyInstanceCount ?: 0}” params=”${filterParams}”/>
                                <!– Ajout d’un bouton pour afficher la zone de recherche –>
                                <filterpane:filterButton />
                        </div>
                </div>
                <!– Cette balise permet d’ajouter la zone de recherche sur l’interface –>
                <filterpane:filterPane domain=”fr.ippon.demo.grails.Company”/>
        </body>
</html>

Pour finir, vous pouvez ajouter dans le fichier messages.properties des entrées concernant les libellés qui s’afficheront dans la zone de recherche de Filterpane. Vous pouvez par exemple ajouter Contact.last_name=Nom pour que Nom s’affiche sur la page au lieu de lastname.

# Libelles de la zone de recherche Filterpane sur l’interface
# du domaine de Contact
Contact.last_name=Nom du contact
Contact.first_name=Prenom du contact
Contact.email_address=Email du contact

Losque vous relancerez le serveur de votre application, sur l’interface, vous pourrez remarquer qu’en dessous de la liste de vos enregistrements d’un domaine, vous avez le bouton filtrer. Si vous cliquez sur ce bouton, un panel se déroulera et vous permettra, pour chaque champ, d’effectuer des filtres au niveau de la table des enregistrements.

Le plugin Filterpane est assez complet au niveau de l’interface puisqu’il permet de filtrer les élément selon de multiples critères. De plus, le panel de recherche est configurable, tant au niveau des champs qui sont affichés que sur les recherches en elles-mêmes.

Différences avec l’étape précédentehttps://github.com/ippontech/grails-demo/commit/92f564878bd53cd5f7b7d51fee67f752cead9355

Code du projet à la fin de cette étapehttps://github.com/ippontech/grails-demo/tree/92f564878bd53cd5f7b7d51fee67f752cead9355

8. Tests JUnit

Grails permet aux développeurs d’effectuer des tests unitaires sur l’ensemble des éléments d’un projet, comme les contrôleurs, les domaines ou les pages .gsp.

Tout d’abord, un bug à été détecté (avec le message d’erreur Error Forked Grails VM exited with error) sur les version 2.3.3 de Grails, qui peuvent se manifester lors du démarrage du serveur de l’application ou lorsqu’on lance les tests unitaires. Au démarrage du lancement des tests unitaires, une JVM séparée est exécutée. Hors, l’usage de multiples JVM en même temps peuvent provoquer des problèmes de performances mais aussi soulève cette erreur de Grails. Pour le corriger, il faut, dans le bloc grails.project.fork dans le fichier grails-app/conf/BuildConfig.groovy, modifier la propriété test qu’il faut mettre à false (référence : http://grails.org/doc/2.3.x/guide/commandLine.html).

Ensuite, Grails génère automatiquement les classes de test unitaires des controlleurs et des domaines lorsqu’on lance la commande de génération automatique. Elles sont créées dans le dossier test/unit/(package du domaine). Pour les domaines, les fichiers sont nommés (Nom du domaine)Spec.groovy, et pour les controlleurs (Nom du controlleur)Spec.groovy.

Dans la classe des tests unitaires des controlleurs, de multiples tests sont déjà générés automatiquement pour chacune des méthodes. Ils vont donc tester si les méthodes save, update, show, edit et delete fonctionnent correctement. Vous devez cependant modifier un élément pour que certains des tests n’échouent pas. Dans la méthode populateValidParams, tout en haut de la classe, un attribut params est spécifié. Il sert à tester une instance du domaine, pour savoir si elle est valide. Vous devez donc spécifier, dans l’attribut params, des valeurs de test pour construire une instance valide du domaine. en voici un exemple de code :

def populateValidParams(params) {

assert params != null
// TODO: Populate valid properties like…
//params[“name”] = ‘someValidName’

/**
* Si vous ne spécifiez pas ces paramètres,
* les tests qui utilisent la variable “params”
* échouent.
*/
params[“company_name”] = ‘Ippon Technologies’
params[“activityType”] = ActivityType.Informatique

}

Pour les test concernant les domaines, ils peuvent être effectués sur les contraintes du domaine (propriété peut être null, vide, doit comporter n caractères,…). Ils peuvent être effectués de la manière suivante :

/**
* Test que le nom de l’entreprise doit obligatoirement commencer
* par une majuscule.
*/
void “Test that company_name must begin with an upper case”() {

when: ‘the name begins with a lower letter’
def company = new Company(company_name: ‘ippon’)

then: ‘validation should fail’
!company.validate()

when: ‘the name begins with an upper case letter’
company = new Company(company_name: ‘Ippon’)

then: ‘validation should pass’
company.validate()

}

Avec Grails, le nom de la méthode de test correspond à une phrase sur ce que l’on souhaite tester. Ensuite, on utilise when pour déclarer avec une phrase l’objet du test qui suivra la déclaration. En dessous de when, on ajoute le code qui permettra d’instancier les éléments nécessaires au test. then est utilisé pour tester les éléments que l’on souhaite tester dans ce test. Les déclarations when et then sont obligatoires, mais pas les phrases qui les suivent.

Dans l’exemple ci-dessus, je teste si la première lettre du nom de la compagnie est une majuscule. Voici le code du domaine permettant d’ajouter la contrainte :

/**
* Cette section permet de spécifier des contraintes
* liées à l’ajustement des champs sur l’interface.
*/
static constraints = {

/**
* Je spécifie l’ordre d’affichage des champs dans
* l’interface qui sera automatiquement générée par Grails.
* company_name ne peut pas être null ni vide, et la première
* doit être une majuscule.
*/
company_name(nullable: false, blank: false, matches: /[A-Z].*/)

/**
* activityType peut-être null.
*/
activityType(nullable: true)

}

Il faut par contre faire attention à un élément important. Dans la classe de test, on ajoute “c.validate()” pour savoir si le test fonctionne. La méthode validate test la validité de l’ensemble des propriétés du domaine. Donc si vous spécifiez un attribut avec une valeur null alors que celui-ci à pour contrainte de ne pas être null, alors la méthode validate vous enverra que l’instance n’est pas valide, même si les autres valeurs sont bien enregistrés.

Pour finir, vous pouvez lancer la commande “grails test-app Company (nom de votre domaine à tester) –unit (pour exécuter seulement les tests unitaires)”. Vous n’avez pas besoin cette fois-ci de spécifier le package de la classe à tester. Un rapport est automatiquement généré dans le dossier “target\test-reports”, le fichier “all.html” dans le dossier “html”.

Tests unitaires Grails

Résultat des tests unitaires dans le fichier all.html

Différences avec l’étape précédentehttps://github.com/ippontech/grails-demo/commit/8d3cbf046a2617a9e21e690952ed1bbf3499f5db

Code du projet à la fin de cette étapehttps://github.com/ippontech/grails-demo/tree/8d3cbf046a2617a9e21e690952ed1bbf3499f5db

Conclusion

Pour commencer, cet article et ce projet nous ont permit de visualiser les apports de Grails pour des projets de petite envergure, pour effectuer un simple CRUD. C’est un framework qui n’est pas compliqué à prendre en main, mais qui bien sûr demande un certain temps d’apprentissage.

Cependant, le gain de productivité pour développer une application CRUD est saisissante. Un développeur, ayant déjà travaillé sur Grails, peut effectuer un CRUD fonctionnel assez simple, avec une/deux tables qui peuvent être jointes, en 15/30 min.

L’application que nous avons réalisé reste bien entendu basique au niveau de ses fonctionnalités. Faire un projet plus complet et complexe permettrait de bien se rendre compte de toute la puissance de ce framework mais aussi de ses limites.

Grails propose, sur son site, un annuaire des plugins que nous pouvons insérer dans nos projets. Et la encore, avec le nombre et la diversité de ces plugins, nous pouvons constater qu’une application Grails peut être très enrichie et complète.

Au final, l’application nous à donné entière satisfaction pour ce nous voulions démontrer. Grails est un framework performant qui permet de générer un CRUD rapidement, qui ne nécessite pas beaucoup de code et permet une bonne maintenabilité du code.

Tweet about this on TwitterShare on FacebookGoogle+Share on LinkedIn

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *


*