Ember.js ,Think different ! (Etape 2)

L’article précédent nous a permis de mettre en place une application simple qui ne tirait cependant pas partie de la particularité qui fait toute la puissance du framework Ember : Ember Data.

C’est ce point qui sera développé dans ce post, tout en insistant sur la façon de mocker les données.

Cet article fait partie d’une série composée des posts suivants :

  • Introduction à Ember – Rappel rapide sur les mécanismes mis en place par ce framework,
  • Etape 1  – Application simple avec données ‘rack’ retournées directement par le modèle sous forme d’un tableau JSON. Cette étape sera complété par l’utilisation de addons Ember pour rendre l’application plus esthétique et ergonomique.
  • Etape 2 (ce post) –  Passage sur un modèle géré par Ember Data. Etant donné qu’aucun back-office ne sera déployé, nous introduirons le framework de mock Mirage pour intercepter les appels HTTP.
  • Etape 3 – Introduction d’une relation rack —> bottle, et enrichissement des données mockées.
  • Etape 4 –  Construction d’un composant graphique permettant une représentation d’un rack et simplifiant les templates d’affichage
  • Etape 5 – Mise en place d’un backoffice construit à l’aide de JHipster. Nous profiterons des capacités de cet outil pour obtenir rapidement un modèle persisté en base, les services Rest permettant de l’administrer ainsi qu’une interface d’administration. Nous verrons au passage quelques problématiques liées à la sécurité.
  • Etape 6 – Mise en place d’un backoffice sur services Liferay et packaging de l’application Ember au sein d’une portlet. Nous avons choisi un backoffice Liferay pour sa facilité à construire un modèle de persistance mais surtout pour bien démontrer l’utilisation potentielle des Adapters et des Serializers, même si un backoffice plus conforme aux standards Rest n’en sera que plus aisé à utiliser.

Le code de cet article est disponible sur mon gitlab dans les branches step_2_x.

Step 2.1 – Mise en place d’un premier modèle

Le précédent article travaillait sur des données mockées directement dans le contrôleur Javascript. Nous allons maintenant mettre en place une véritable stratégie de récupération de ces données à partir d’un backoffice Pour cela, nous allons utiliser la pièce maîtresse d’Ember, Ember Data.

Passer à Ember Data signifie créer un modèle JavaScript et en quelque sorte déléguer ses manipulations au ‘store’ agissant avec le back-end par l’intermédiaire d’un adaptateur. Dans la philosophie Ember, tous ces objets sont créés par défaut, sauf bien entendu le modèle. On le crée via le générateur :

ember generate model rack

On ajoute les champs constitutifs de notre modèle dans le fichier model/rack.js créé :

// app/models/rack.js

import Ember from 'ember';

import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr('string'),
  nbColumns: DS.attr('number'),
  nbRows: DS.attr('number'),
  image: DS.attr('string'),
  createdAt: DS.attr('date', {defaultValue() { return new Date(); }}),

  capacity: Ember.computed('nbColumns', 'nbRows', function() {

     return this.get('nbColumns')*this.get('nbRows'); }),


});

On remarque au passage le champ ‘capacity’ obtenu dynamiquement à partir des champs ‘nbColumns’ et ‘nbRows’.

On va également créer explicitement un adaptateur (de niveau applicatif car concernant tous les objets du modèle) :

ember generate adapter application

Cela permet d’introduire un ‘namespace’ pour les services Rest qui font des appels au back-office

// app/adapters/application.js

import DS from 'ember-data';

export default DS.JSONAPIAdapter.extend({
  namespace: 'api'
});```

<div class="code-embed-infos"><span class="code-embed-name"></span></div></div>On édite enfin la route pour ‘racks’ en utilisant le store pour récupérer les objets du modèle (et supprimer au passage les données ‘en dur’) :

<div class="code-embed-wrapper">```
<code class="language-javascript code-embed-code">// app/routes/racks.js

import Ember from 'ember';

export default Ember.Route.extend({
  model() {
     return this.store.findAll('rack');
}

});

Et là, BADABOUM ! 😯 plus rien de fonctionne !!!

Mais c’est normal… En ouvrant les outils développeurs d’un Chrome et en se plaçant dans la vue ‘Network’, on constate que conformément au fonctionnement d’Ember Data et à ce que l’on a défini dans l’adaptateur, un appel à l’URL http://localhost:4200/api/racks est effectué au travers de la méthode findAll. Pour le moment, cette URL ne renvoie rien…

Deux solutions s’offrent alors à nous :

  • Implémenter un véritable service back
  • Mettre en place un mock

C’est cette seconde piste que nous allons explorer par l’intermédiaire de l’add-on Mirage.

Step 2.2 : Introduction de Mirage pour mocker les données

Pour démarrer avec Mirage, on commence comme d’habitude par installer l’add-on correspondant :

ember install ember-cli-mirage

L’installation de cet add-on crée un répertoire mirage au sein du projet :

Il existe plusieurs façons d’utiliser Mirage. Pour mocker des données fournies au travers d’Ember Data, il est cependant préférable de passer par la base intégrée de Mirage (le ‘schema’) et de recréer un objet ‘rack’ au sein de cette extension en exécutant la commande :

ember g mirage-model rack

Reste à indiquer à Mirage :

  1. Comment fabriquer nos objets ‘rack’ mockés ;
  2. Quel scénario de bouchonnage on souhaite mettre en place ;
  3. Quelles sont les URL qui doivent être interceptées par le framework pour être bouchonnées.

Pour le premier point, on va passer par la définition d’une factory, en créant un répertoire ‘factories’ sous le répertoire mirage, contenant un fichier rack.js du type :

// mirage/factories/rack.js

import { Factory, faker } from 'ember-cli-mirage';

export default Factory.extend({
  name(i) { return 'rack number '+i; },
  nbColumns() { return faker.random.number({min:4, max:12}); },
  nbRows() { return faker.random.number({min:3, max:20}); },
  image() { return './img/rack_0'+faker.random.number({min:1, max:5})+'.jpg';}

});

(à noter qu’on utilise directement la bibliothèque faker.js intégrée à Mirage pour fournir des données dynamiques et aléatoires visuellement convaincantes).

Pour le second point, on édite le fichier default.js localisé dans ‘scenarios’ pour lui indiquer que l’on souhaite créer 32 objets de type ‘rack’ :

// mirage/scenarios/default.js

export default function(server) {

 /*
   Seed your development database using your factories.
   This data will not be loaded in your tests.

   Make sure to define a factory for each model you want to create.
 */
 server.createList('rack', 32);
}

Enfin, pour le dernier point, on travaille dans le fichier config.js pour indiquer qu’on souhaite intercepter l’URL ‘/racks’ en GET sous le namespace ‘api’ afin de renvoyer l’ensemble des objets ‘rack’ contenus dans le schéma de Mirage :

// mirage/config.js

export default function() {
 // this.urlPrefix = '';    // make this `http://localhost:8080`, for example, if your API is on a different server
 // this.namespace = '';    // make this `api`, for example, if your API is namespaced
 // this.timing = 400;      // delay for each request, automatically set to 0 during testing
 /*
   Shorthand cheatsheet:

   this.get('/posts');
   this.post('/posts');
   this.get('/posts/:id');
   this.put('/posts/:id'); // or this.patch
   this.del('/posts/:id');

   http://www.ember-cli-mirage.com/docs/v0.2.x/shorthands/
 */
 this.namespace = 'api';

this.get('/racks',  function(schema) {
  return schema.racks.all();
  });
}

Et voilà !