Ruby on Rails par Grafikart – quelques astuces

En regardant en diagonale quelques vidéos de Grafikart sur Ruby on Rails, j’ai noté quelques astuces intéressantes :

rails s -b IP

Pour binder l’IP sur laquelle l’application écoute.

Par exemple, rails s -b 0.0.0.0 permet d’écouter sur toutes les interfaces (utile pour les machines virtuelles)

Callback de modèle

Pour ne pas avoir à ajouter du code dans le contrôleur (après un @objet.save par exemple).

Son exemple était l’utilisation d’un before_validation pour setter le slug en fonction du titre d’un article.

Scope avec paramètre

exemple :

scope :online, -> (online) { where(online: online) }

Scope par défaut

default_scope where(published: true)

Attention aux effets de bord, notamment si on met un order dans le defaut_scope et qu’on veut redéfinir le tri dans le contrôleur !

Pour éviter ces effets de bord, les méthode reorder, unscoped et unscope peuvent être intéressante.

render mon_objet

Si on a un partial du même nom qu’une classe, on peut directement utiliser cette syntaxe.

Exemple : si on boucle sur un tableau d’élément pour les afficher les uns sous les autres, on peut écrire :

@elements.each do |element|
  render element
end

à partir du moment où on a un fichier partial _element.html.erb.

On peut même écrire :

render "element", collection: @elements

ou même :

render @elements

qui donnera le même résultat. Et ça c’est fort !

content_for et yield

Penser à les utiliser !

content_for(:title, @element.titre)
 
yield(:title)
 
content_for?(:title)

exemple :

content_for?(:title) ? yield(:title) : "Mon super titre"

config.generators

On peut configurer les fichiers qui sont générés quand on fait un rails ... dans le application.rb. Par exemple, pour ne pas créer un helper à chaque fois qu’on génère un contrôleur, on peut écrire :

config.generators.helper = false

Plus d’infos sur guides.rubyonrails.org.

Lancer un serveur en production

rails s -e production

Attention, il faut du coup une base de données de production.

Supprimer un contrôleur et les fichiers associés

Quand on crée un contrôleur avec la commande rails g controller mon_controleur, plusieurs fichiers sont générés : le contrôleur, le fichier de test, un fichier SCSS et un fichier JS (ou Coffee).

Pour supprimer ce contrôleur et tous les fichiers associés, on peut utiliser la commande :

rails d controller mon_controleur

Utiliser un CDN

config.action_controller.asset_host = 'cdn.domain.fr'

Preview d’email

Quand on génère un mailer avec rails g mailer mon_mailer, il est généré un fichier mailer dans /app/mailers, un répertoire de vues dans app/views, mais aussi un fichier de preview dans test/mailers/preview.

Dans ce fichier, on crée des méthodes qui appellent celles du fichier mailer. Par exemple, si notre fichier mailer contient un méthode contact(prenom), on peut définir une méthode contact dans notre fichier preview :

ContactMailer.contact("Toto")

Ensuite, on peut accéder au preview via l’url rails/mailers/contact_mailer qui liste toutes les méthodes de preview.

Associations

.includes

Exemple : on affiche un tableau d’articles avec pour chaque article sa catégorie. Pour cela, dans le contrôleur on aurait écrit :

@articles = Article.all

Et dans la vue, quelque chose comme :

<%= article.categorie.titre %>

Cela n’est pourtant pas très efficace : on a une requête qui récupère les articles :

SELECT * FROM articles

et pour chaque article, une requête qui va chercher sa catégorie :

SELECT * FROM categorie WHERE id = 2 LIMIT 1

Soit n+1 requêtes (où n correspond aux nombres d’articles).

Pour contrer cela, on peut faire dans le contrôleur :

@articles = Article.includes(:categorie).all

Ce qui donnera, au niveau des requêtes :

SELECT * FROM articles
SELECT * FROM categories WHERE id IN (2,3)

On peut même faire :

@articles = Categorie.includes(articles: :tags).all

Pour récupérer toutes les catégories et les articles associés ainsi que les tags associés aux articles.

:dependent

Pour supprimer tous les articles associés à une catégorie à la suppression de la catégorie, on peut écrire :

class Categorie < ApplicationRecord
  has_many :articles, dependent: :destroy
end

qui supprimera les objets articles associés (et appellera donc les callbacks)

ou :

class Categorie < ApplicationRecord
  has_many :articles, dependent: :delete_all
end

qui supprimera directement les articles associés en base de données (et n’appellera pas les callbacks)

:counter_cache

Pour afficher le nombre d’associations sans faire de requête supplémentaire, on peut utiliser un « counter_cache » :

class Article < ApplicationRecord
  belongs_to :categorie, counter_cache: true
end

Il faut pour cela ajouter un champ posts_count dans la table catégorie (avec 0 comme valeur par défaut).

La valeur de posts_count sera mis à jour automatiquement quand on ajoute ou supprime un article à une catégorie.

has_and_belongs_to_many

Pour créer une table de liaison, on peut faire la migration :

rails g migration create_join_table_articles_tags article tag

et utiliser le has_and_belongs_to_many

class Article < ApplicationRecord
  has_and_belongs_to_many :tags
end
 
class Tag < ApplicationRecord
  has_and_belongs_to_many :articles
end

L’association has_many: :through permet de faire la même chose avec une classe de liaison. Cela peut être utile lorsqu’on veut stocker des choses dans la table d’association.

article.tags.delete(tag) supprime la liaison
article.tags.destroy(tag) supprime le tag et les liaisons

Traiter un formulaire avec fichiers en AJAX

Pour traiter le contenu d’un formulaire de façon asynchrone, on utilise AJAX.

Exemple à l’aide de jQuery :

var form = $("#mon-formulaire");
$.ajax({
  url: form.attr("action"),
  type: form.attr("method"),
  data: form.serialize()
});

La question que je me suis posée est la suivante : comment ajouter des champs de type file et les traiter également en AJAX ? En effet, par défaut les fichiers ne sont pas traités.

FormData

Mes recherches se sont tournées vers l’objet FormData. Un objet FormData permet de créer un ensemble de paires clef-valeur pour un envoi via XMLHttpRequest. Les données transmises sont alors dans le même format qu’utiliserait la méthode submit() pour envoyer des données si le type d’encodage du formulaire correspondait à « multipart/form-data ». Cela permet donc d’uploader des fichiers.

Exemple :

var form = $("#mon-formulaire");
$.ajax({
  url: form.attr("action"),
  type: form.attr("method"),
  contentType: false,
  processData: false,
  data: new FormData(form[0])
});

À noter que l’objet FormData est supporté par les navigateurs actuels (Firefox, Chrome, Safari, Edge, IE10+).

Sources :

Rails : validates_associated

En cours de développement et à la validation d’un formulaire de création d’élément, nous avons obtenu un comportement étrange : notre objet ne s’est pas enregistré. Pourtant, toutes les conditions des validates semblaient remplies.

La méthode .valid? appliquée sur notre objet retournait en effet false et la liste .errors.messages contenait le message suivant :

translation missing: fr.activerecord.errors.models.element.attributes.parents.invalid

En effet, notre élément est associé à un autre. Le problème est que ce dernier n’est pas valide (il avait été enregistré avant qu’on ajoute les validates dans le modèle).

Pour modifier le message d’erreur, on peut ajouter le validateur suivant à notre modèle :

validates_associated :parents, message: "Le parent associé n'est pas valide."

Et le lire en faisant :

@element.errors[:parents].last

Les service Workers

Un Service Worker est un script JavaScript qui s’exécute en arrière plan, en dehors du contexte de l’application (il n’a donc pas accès au DOM de la page ni aux interactions de l’utilisateur). Il joue le rôle de proxy et peut intercepter les requêtes exécutées. Il y répond alors selon le contexte (par exemple en faisant appel au cache si le réseau est indisponible). C’est également lui qui va envoyer des notifications push.

Un exemple courant de Service Worker que j’ai mis en place est celui expliqué ici : network or cache. L’application essaie d’abord de récupérer les ressources sur le réseau puis, si le réseau n’est pas disponible, il fait appel au cache.

Le site serviceworke.rs regroupe plein de cas d’utilisation des Sevice Workers.

Sources :

Lecture complémentaire :

Les Progressive Web Apps

Pour créer une application mobile, il existe plusieurs solutions :

  • Développer directement son application Android et iOS avec Java et Swift (mais aussi C# pour Windows Phone).
  • Utiliser Cordova ou Ionic pour développer notre application avec les technologies Web et ainsi créer une application hybride.
  • Développer une Progressive Web App (PWA) et ainsi utiliser tous les avantages du navigateur et des langages web.

Qu’est-ce qui définit une PWA ?

Cordova se définit comme un framework dont les applications « s’appuient sur des API conformes aux standards permettant l’accès aux capteurs de chaque appareil, aux données ainsi qu’à l’état du réseau ». Ça tombe bien, aujourd’hui on peut faire tout ça depuis un navigateur.

Une Progressive Web App utilise les possibilités du web moderne pour délivrer une expérience utilisateur similaire à une application native.

Les points essentiels d’une PWA sont listés ci-dessous.

Une PWA est progressive. Elle est développée selon le principe de l’amélioration progressive. Cela signifie qu’elle fonctionne pour tout utilisateur, peu importe le navigateur qu’il utilise. Elle est responsive et sera donc adaptée à tous les devices.

Elle est « App-like » : l’utilisateur a l’impresssion d’utiliser une application native. Elle est « installable » puisque l’utilisateur peut ajouter sur son écran d’accueil une icône qui ouvre l’application (voir la partie « manifest.json » dans l’article Quoi de neuf dans mon navigateur ?). Elle utilise le modèle « App Shell« , c’est-à-dire qu’elle se charge initialement dans une coquille basique de l’interface utilisateur puis charge le contenu au sein de cette coquille.

Comme tout site web, elle est référencée sur les moteurs de recherche. Elle peut également être partagée facilement puisqu’elle dispose d’URL propres à chaque état de l’application.

Elle est indépendante de la connexion internet et fonctionne sur des réseaux de faible qualité et en mode non-connectée (grâce aux Service Workers).

Elle est sûre puisqu’elle utilise obligatoirement HTTPS.

Une PWA est engageante. Elle donne envie à l’utilisateur de l’utiliser régulièrement.

Les avantages des PWA

  • Pas d’installation depuis le Play Store ou l’App Store.
  • Fonctionnent sur tous les devices (PC, tablettes et mobile, aussi bien Windows, Apple, Android que Linux)
  • Pour le développeur, pas besoin de connaître le langage natif de chaque device.

Sources :

Découvrons Gulp

Gulp est ce qu’on appelle un automatiseur de tâches (ou « task runner »). Il se base sur un fichier gulpfile.js situé à la racine du projet et exécute les tâches qui y sont définies.

Les tâches courantes automatisables auxquelles on pense sont les suivantes :

  • fusion de CSS/JS
  • minification de CSS/JS
  • compilation de fichier SCSS
  • optimisation d’image

mais Gulp en propose beaucoup d’autres. Pour automatiser des tâches, Gulp utilise des plugins. À l’heure où j’écris cet article, 2746 plugins sont répertoriés, soit presque autant de tâches différentes réalisables avec Gulp !

Installation

Gulp est basé sur nodeJS. Il faut donc au préalable avoir installer nodeJS sur sa machine. Une fois nodeJS installé, on peut initialiser un projet :

npm init

Cette ligne de commande va créer un fichier package.json qui sera nécessaire à l’installation de plugins. Gulp est un plugin de Node. Pour l’installer, il faudra taper la ligne de commande suivante :

npm install --save-dev gulp

Cette commande va ajouter une ligne dans le fichier package.json :

"devDependencies": {
  "gulp": "^3.9.1"
}

Chaque plugin présenté dans l’article est installable de cette façon. Un autre moyen d’installer des plugins est de récupérer un fichier package.json existant et de copier-coller son contenu au sein du notre, et de lancer l’instruction suivante :

npm install

Utilisation de Gulp

L’installation de Gulp va générer un fichier gulpfile.js dans lequel nous allons définir nos tâches.

Pour commencer nous pouvons ajouter les lignes suivantes dans notre gulpfile.js :

var gulp = require('gulp');
gulp.task('default', function() {
});

Pour utiliser un plugin, il faut l’inclure avec la directive require. Ici, le plugin gulp est inclus (il faudra toujours l’inclure).

On définit une tâche avec la fonction gulp.task. Ici, la tâche « default » est définie.

On peut définir autant de tâches que nécessaire et les exécuter indépendamment les unes des autres. Pour exécuter une tâche en ligne de commande, il faudra taper :

gulp <tache>
# ou simplement gulp pour la tâche default

On peut par exemple définir une tâche « build » qui va générer les fichiers CSS à partir de fichiers SCSS et une tâche « prod » qui va en plus le minifier. Pour cela, on peut écrire :

gulp.task('css', function(){...});
gulp.task('minify', function(){...});
gulp.task('build', ['css']);
gulp.task('prod', ['build',  'minify']);
gulp.task('default', ['build']);

Ici, cinq tâches sont définies. La tâche « build » appelle la tâche « css« , la tâche « prod » appelle la tâche « build » et la tâche « minify« .

La tâche « watch » est une tâche particulière qui surveille automatiquement les modifications des fichiers qu’on lui indique et exécute les tâches nécessaires dès qu’on enregistre nos fichiers. Pour cela, il suffit de lancer la tâche watch :

gulp watch

Si on veut créer une tâche qui transforme un fichier SCSS en CSS, on écrira :

var compass = require('gulp-compass');
gulp.task('css', function() {
  return gulp.src(chemin-fichier-scss)
    .pipe(compass())
    .pipe(gulp.dest(chemin-repertoire-css));
});

Ici, on prend un fichier (ou plusieurs avec un sélecteur *) auquel on va appliquer d’abord la méthode compass() (qu’on a importé au dessus) puis la fonction dest() de gulp. compass() va convertir le fichier source en fichier CSS, dest() définit la destination du résultat. On peut appliquer autant de fonctions qu’on le souhaite à un fichier en utilisant pipe.

Pour surveiller les fichiers SCSS et appliquer la tâche css dès qu’un fichier est modifié, on peut déclarer la tâche watch suivante :

gulp.task('watch', function() {
  gulp.watch('*.scss', ['css']);
});

Plugins intéressants

On trouve tout un tas de plugins sur gulpjs.com/plugins. Voici une liste non exhaustives de plugins intéressants :

  • gulp-compass (compilation SCSS vers CSS)
  • gulp-minify-css (minification CSS)
  • gulp-uglify (minification JS)
  • gulp-concat (concaténation de fichiers)
  • gulp-rename (renommage de fichier)
  • gulp-uncss (suppression de CSS non utilisés)
  • gulp-imagemin (optimisation d’images)
  • gulp-cssbomb (réordonne les propriétés CSS)
  • gulp-cssbeautify (ré-indente les propriétés CSS)

Pour ne pas avoir à importer tous les plugins, ce qui peut être assez fastidieux, on peut importer le plugin gulp-load-plugins :

var plugins = require('gulp-load-plugins')();

Puis utiliser les plugins de la façon suivante :

plugins.compass()

Sources :

Seriously, (Don’t) Use Icon Fonts

Après avoir vu comment générer une font-icon en Rails, il est intéressant de se poser la question : Est-ce une bonne pratique d’utiliser une font-icon ?

Pour essayer d’y répondre, j’ai confronté ces deux articles, le second étant écrit en réaction au premier :

Dans son article, Tyler Sticka compare l’utilisation des font-icons à la mise en page HTML à partir de tableaux. Pour lui, une font n’est pas faite pour afficher des icônes. Ses arguments sont les suivants :

  1. Cela pose des problèmes d’accessibilité. En effet, la plupart des lecteurs d’écran lisent de la même façon les font-icons que le reste du texte, ce qui donne quelque chose d’incompréhensible. De plus, beaucoup de personnes dyslexiques remplacent la police des sites web par une police plus lisible pour eux, ce qui remplace également les font-icons, ce qui rend les icônes illisibles.
  2. Dans certains cas, les font-icons qui sont générées automatiquement et auxquelles ont affecte un caractère Unicode aléatoire sont chevauchées par des emoji, ce qui peut donner des trucs sympas.
  3. Le chargement d’une font-icon échoue souvent et les fallbacks ne sont pas parlantes.
  4. Les icônes en SVG ne sont pas plus difficile à gérer. Il est tout à fait possible de faire un sprite en SVG qui ne sera pas plus lourd qu’un fichier de font. De plus le SVG est tout autant supporté par les navigateurs qu’une font-icon (96% de support).

Les contres-arguments de Ben Frain sont les suivants :

  1. Les font-icons sont supportées par Android 2.3. Ce n’est pas le cas des SVG.
  2. Si on utilise le bon rang Unicode (« private use area » ou PUA), les caractères ne seront pas lus par les lecteurs d’écran. IcoMoon utilise ce PUA, à voir si fontcustom le fait aussi.
  3. Pour résoudre les problèmes de chargement d’une font-icon, on peut l’inclure dans le CSS grâce au data-uri.
  4. Il est plus facile d’appliquer du CSS (text-shadow, transition, …) sur les font-icons.
  5. Le rendu d’une page avec des font-icons est plus rapide que celui d’une page avec des SVG inline.

Lecture complémentaire : The Great Icon Debate: Fonts Vs SVG

Bien configurer son Sublime Text

1. Télécharger Sublime Text

La dernière version de Sublime Text, tant qu’à faire.

2. Installer Package Control

3. Installer les bons packages

Pour installer un package :

  • cmd + maj + P
  • Saisir « install package » et sélectionner « Package Control: Install Package ».
  • Saisir le nom du package à installer et valider. La liste des packages proposés par Package Control est disponible sur : packagecontrol.io/browse.

st

SCSS

Permet d’avoir la coloration syntaxique sur les fichiers .scss.

CSS3

Support des propriétés CSS3.

Bracket Highlighter

Met en évidence les balises ouvrantes et fermantes.

All Autocomplete

Étend l’autocomplétion des mots à travers tous les fichiers ouverts.

Color HighLighter

Souligne les codes couleurs avec la couleur correspondante. Il souligne également les variables SCSS.

4. Bien définir les config par défaut

Dans Sublime Text > Preferences > Settings, modifiez le fichier Preferences.sublime-settings - User.

"highlight_line": true,
"highlight_modified_tabs": true,
"indent_to_bracket": true,
"tab_size": 2,
"translate_tabs_to_spaces": true,
"trim_trailing_white_space_on_save": true,
"word_wrap": true,
"show_definitions": false

5. Configurer des snippets

La procédure de création d’un snippet est expliquée en détails dans cet article : Sublime Text : créer un snippet. La liste des snippets que j’utilise est sur Github. Celui que j’utilise le plus fréquemment est le suivant :

<snippet>
  <content><![CDATA[
<%= $1 %>
]]></content>
  <tabTrigger><![CDATA[<]]></tabTrigger>
  <description><![CDATA[<% <%= ... %>]]></description>
  <scope>text.html.ruby</scope>
</snippet>

Il me permet d’ouvrir les balises Rails <%= ... %> simplement en tapant "<" + "TAB".