TinyMCE et Ruby On Rails autoriser certaines balises html

Sur un projet Rails où vous avez installé un tinyMCE suivant cette méthode, par défaut tinyMCE filtrera un certain nombre de balise HTML.
Si vous souhaitez autoriser la balise script (attention c’est assez dangereux si vous avez des rédacteurs mal formés ou mal intentionnés) il faut modifier le fichier config/tinymce.yml pour ajouter ceci :

extended_valid_elements: "script[*]"

Cela permet d’étendre la liste d’éléments valides par défaut de tinyMCE (plus d’infos sur cette liste) avec l’élément script et le « [*] » indique que cet élément peut avoir n’importe quel attribut (plus d’infos ici).

Et voilà, très pratique pour intégrer des tweets ou autres éléments dans la page.

Enjoy 😉

Gestion des erreurs 404 et 500 en Ruby on Rails

Concept

La gestion native des erreurs en ROR souffre de deux manques : d’abord, les erreurs ne sont pas chartées selon le style de votre application, ensuite elles n’alertent pas les développeurs de ce qui s’est produit. Le code ci-dessous peut être placé directement dans l’application ou au sein d’une gem.

Étape 1 : Intercepter les erreurs 404, 422 et 500

Dans le routeur d’URL, ajouter les lignes suivantes :

  match "/404", to: "erreurs#page_introuvable", via: :all
  match "/422", to: "erreurs#erreur_interne", via: :all
  match "/500", to: "erreurs#erreur_interne", via: :all

Lorsqu’une erreur se produit, le moteur de Rails appelle automatiquement ces routes. En ajoutant les routes ci-dessus, on va pouvoir modifier le comportement natif de votre appli en les interceptant dans nos méthodes.

Étape 2 : Méthodes dans le contrôleur

Tout commence par la création d’un contrôleur dédié :

class ErreursController < ::ApplicationController
  skip_before_action :verify_authenticity_token, only: [:erreur_interne]
 
  def page_introuvable
    # On envoie le mail
    Erreurs404500mailer.envoi_erreur_404(request, session).deliver
 
    # Et on affiche de jolies erreurs
    respond_to do |format|
      if defined?(UTILISER_CHARTE) && UTILISER_CHARTE
        # Ici on utilise Rails.root pour remonter dans l'application qui utilise la gem, et on va chercher la page statique des 404
        format.html { render "#{Rails.root}/public/erreur_404.html", layout: false, status: 404 }
      else
        # Sinon on utilise le template de ce controleur de manière générique (le fichier 404.html présent dans public/ quand on créé un projet Rails, par exemple)
        format.html { render template: "erreurs/page_introuvable", layout: false, status: 404 }
      end
      format.all  { render nothing: true, status: 404 }
    end
  end
 
  # La même pour les 500
  def erreur_interne
    exception = env['action_dispatch.exception']
    Erreurs404500mailer.envoi_erreur_500(request, session, exception).deliver
 
    respond_to do |format|
      if defined?(UTILISER_CHARTE) && UTILISER_CHARTE
        format.html { render "#{Rails.root}/public/erreur_500.html", layout: false, status: 500 }
      else
        format.html { render  template: "erreurs/erreur_interne", layout: false, status: 500 }
      end
      format.all  { render nothing: true, status: 500 }
    end
  end
end

Ce code est simplifiable si vous ne le placez pas au sein d’une gem, mais bon, ça peut rapidement devenir un couteau suisse dans tous vos projets.

Étape 3 : le mailer

Bon là rien d’exceptionnel :

class Erreurs404500mailer < ActionMailer::Base
  def envoi_erreur_404(requete, session)
    @nom_application = NOM_APPLICATION
    @requete = requete
    @session = session
 
    # Une petite astuce si vous avez une delivery_method mailjet par exemple
    #ActionMailer::Base.delivery_method = :smtp if ...
 
    mail(to: "web@groupebatteur.fr", subject: "[#{@nom_application}] Erreur 404", from: "")
 
    # Bien remettre mailjet sinon les prochains mails partiront avec le smtp du serveur
    ActionMailer::Base.delivery_method = :mailjet if ...
  end
 
  # Pareil pour les 500
  def envoi_erreur_500(requete, session, exception)
    @nom_application = NOM_APPLICATION
    @requete = requete
    @session = session
 
    @exception = exception
 
    #ActionMailer::Base.delivery_method = :smtp if ...
 
    mail(to: "web@groupebatteur.fr", subject: "[#{@nom_application}] Erreur 500", from: "")
 
    #ActionMailer::Base.delivery_method = :mailjet if ...
  end
end

Étape 4 : Les vues des mailers fraichement créés

Pour les 404 je vous suggère d’extraire quelques infos utiles telles que @requete.remote_ip, @requete.user_agent, @requete.request_method, @requete.original_url ou encore @requete.filtered_parameters.to_s (les paramètres de la requête).
Pour les 500, vous pouvez ajouter :

<% if defined?(@exception.cause) && @exception.cause.present? %>
  <p>Message parent : <%= @exception.cause.inspect %></p>
  <p>Backtrace parente :<br /><%= @exception.cause.backtrace.join("<br />").html_safe %></p>
<% end %>
<p>Message : <%= @exception.inspect %></p>
<p>Backtrace :<br /><%= @exception.backtrace.join("<br />").html_safe %></p>

Étape 5 : les vues des méthodes en question

N’oubliez donc pas de créer les vues correspondant aux méthodes selon les choix que vous aurez réalisé à la lecture de l’article.

Nous sommes à l’écoute de toute suggestion car la gestion des erreurs ne semble pas très élaboré d’origine.

Google autocomplete sur la france et les dom-tom

Si vous avez un champs autocomplete fait avec l’api Google maps places vous pouvez restreindre, la zone de recherche à un « territoire » par exemple la France métropolitaine assez facilement avec :

new google.maps.places.Autocomplete(document.getElementById("MonInput"), { componentRestrictions: { country: "fr" } });

Si vous souhaitez, allez plus loin en ajoutant plusieurs « territoires », comme par exemple ajouter les dom-tom, il vous faut faire comme suit :

new google.maps.places.Autocomplete(document.getElementById("MonInput"), { componentRestrictions: { country: ["fr", "gp", "mq", "gf", "re", "pm", "yt", "nc", "pf", "mf", "tf"] } });

Pour comprendre les différents identifiants de territoires vous pouvez consulter la page Wikipédia sur la norme ISO 3166-1

Et voilà la zone de recherche sera plus large.

Enjoy 😉

Sources : https://developers.google.com/maps/documentation/javascript/reference?hl=fr#GeocoderComponentRestrictions, http://codepen.io/fchaussin/pen/VKboYd, https://fr.wikipedia.org/wiki/ISO_3166-1

Google maps Autocomplete déclencher la recherche au chargement de la page

Si vous avez mis en place un champ autocomplete de google maps et que ce champs et prérempli au chargement de la page et que vous souhaitez déclencher la recherche en conséquence, voici une petite astuce.

Cela consiste une fois tous les éléments chargés à simuler flèche du bas puis entrée sur la zone de recherche (souvent un input). Problème comment savoir que tous les éléments sont chargés car faire cette simulation trop rapidement ne fonctionne pas et aucun évènement n’est lancé d’après la doc google.

Du coup l’astuce consiste à attendre que la liste de propositions soit chargés et ensuite simuler la touche flèche bas puis entrée comme suit :

// On vérifie que la liste est prete
liste_prete = setInterval(function(){
  if ($(".pac-container").html() != undefined && $(".pac-container").html() != ""){
    google.maps.event.trigger(document.getElementById("VOTRE_INPUT"), 'keydown', { keyCode: 40 });
    google.maps.event.trigger(document.getElementById("VOTRE_INPUT"), 'keydown', { keyCode: 13, triggered: true });
    clearInterval(liste_prete)
  }
}, 200);

Voilà

Enjoy 😉

Problème innerHeight et height en JS sur mobile

Petite astuce qui peut éviter de perdre du temps à débugger.

Si lors de l’utilisation de la méthode .height() ou .innerHeight() vous rencontrez des difficultés pour récupérer la bonne valeur de la hauteur d’un élément (principalement sur mobile), Il faut vérifier dans le content de la méta viewport que la valeur height=device-height est bien présente.

Normalement, cela devrait résoudre pas mal de problèmes.

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, -&gt; (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 :

&lt;%= article.categorie.titre %&gt;

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 &lt; ApplicationRecord
  has_many :articles, dependent: :destroy
end

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

ou :

class Categorie &lt; 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 &lt; 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 &lt; ApplicationRecord
  has_and_belongs_to_many :tags
end
 
class Tag &lt; 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

Bonnes pratiques concernant l’envoi de newsletters

Ma séance de veille du jour concerne une conférence Alsacréations de Sébastien Lejeune en 2016.

  • Pas plus de 50 caractères dans le sujet. Un sujet court et précis est plus percutant, surtout sur des emailing qui ne sont pas sollicités par les internautes.
  • Souvent dans un client mail le bouton SPAM en haut et près du corps du mail, Sébastien recommande de placer le lien de désinscription tout en haut du mail car il vaut mieux qu’un internaute se désinscrive plutôt que de signaler notre serveur et adresse comme SPAM
  • Avoir un lien miroir de l’email est pratique si l’usager n’arrive pas à visualiser correctement l’email dans son client, car les clients lourds ont souvent un mauvais moteur de rendu
  • On peut mettre ces deux liens côte côte
  • On peut mettre juste après un résumé de l’email en 100 caractères si le mail est long, car les gens passent vite à autre chose
  • On peut mettre un footer avec encore le lien de désinscription + un lien pour gérer ses préférences s’il y a un abonnement à plusieurs newsletters + des liens vers les réseaux sociaux
  • Utiliser le moins d’images possible et le plus de CSS possible car les images chargent lentement ou sont bloquées
  • Le choix d’un lien vs un bouton n’a pas toujours l’impact qu’on imagine, les liens fonctionnent très bien dans les mailing
  • Utiliser des call to action type « Lire plus sur le blog », « Acheter maintenant », « Télécharger l’application », « Réserver maintenant », « Comparer encore d’autre » etc
  • Mettre le prénom de la personne personnalise l’email, c’est très efficace
  • Sur desktop il propose en 500 et 640px de large pour éviter les scroll latéraux
  • Sur mobile, entre 280 et 320px de large
  • HTML5/CSS3 on oublie, il propose XHTML 1.0, les breakpoints sont faits en prenant les propriétés CSS par browser (par exemple pour les iPhone et compagnie), les doctypes ne sont pas pris en compte par GMAIL Yahoo Hotmail.
  • mettre un meta viewport avec un initial-scale à 1.0 pour éviter que le mail soit zoomé par défaut par les clients mails
  • Outlook jusqu’à la version 2007 utilise le moteur de rendu de Microsoft Word
  • Tester, tester, tester. Il utilise deux services en ligne, litmus et Email on acid

Les Rails Engines, poupées russes d’applications Rails

Il n’y a pas si longtemps, dans une galaxie pas si lointaine, nous souhaitions factoriser du code au sein d’une Gem Ruby. Son but, traiter à notre façon les erreurs 402, 404 et 500 que Rails affiche par défaut aux visiteurs. Ces erreurs disposent de routes automatiques native (/402, /404 et /500) qui ne sont que des routes par défaut pointant vers des fichiers statiques présent dans le dossier public.

Notre Gem devait :
– intercepter les redirections qu’opère Rails en repassant dans le routeur d’URL lors d’une erreur ou page introuvable avec match « /404 », to: « erreurs#page_introuvable », via: :all
– redéfinir un template de vue un peu plus présentable pour le visiteur (charté selon le style de l’application)
– intercepter les exceptions de l’application principale
– nous envoyer un email avec le contenu de l’exception
– éventuellement appeler notre webservice d’enregistrement de logs (selon une variable d’environnement dans l’appli)

En bref, le code déporté dans la Gem était une quasi application Rails à part entière, faisant intervenir des routes, des actions dans un contrôleur, des fichiers de vue, des actions dans un mailer et ses vues associées.

Pour cela, nous avons utilisé un Rails Engine :

rails plugin new ma_sous_app --full

Le code généré peut être facilement déporté et intégré dans n’importe quelle application Rails habituelle. Nous l’avons packagée au sein d’une gem et installée sur notre serveur de gem interne sans rencontrer de difficulté.

Pour en savoir plus, je vous invite à lire ce superbe article de Brian Leonard.

Programmation asynchrone en Ruby 3 : les Guilds (Partie 5)

Nous avons fait un tour plutôt large des diverses solutions pour réaliser un programme asynchrone en Ruby. Je vous propose de terminer cette série d’articles par une ouverture sur ce que pourrait apporter la prochaine version majeure de Ruby.

C’est en tombant sur cet article d’Olivier Lacan que j’ai entendu parler de la révision du modèle concurrentiel de Ruby.

Comme évoqué dans la partie 2, l’interpréteur Ruby souffre d’une limitation dans sa gestion des threads : ces derniers ne peuvent pas être exécutés simultanément en parallèle mais le sont chacun à leur tour. Ce mécanisme a été introduit pour offrir l’API des mutex aux développeurs et ainsi permettre de synchroniser les threads. Cette limitation a un impact important sur la performance de nos applications multi-processus.

Koichi Sasada est un développeur core de CRuby, l’interpréteur Ruby « officiel ». Il a proposé un nouveau modèle mêlant Threads et Fibers.

Voici une traduction de l’article d’Olivier cité plus haut, avec mes ajouts personnels entre crochets :
« Les Guilds sont composées d’au moins un Thread, qui à son tour a au moins une Fiber. Les Threads de différentes Guilds peuvent s’exécuter en parallèle, tandis que les Threads dans la même guilde ne peuvent pas [ils le sont à tour de rôle]. Les objets d’une Guild ne peuvent ni lire ni écrire sur les objets d’une autre Guild. »

ruby_3_guilds_threads_and_fibers

« Les Threads appartenant à la même Guild ne peuvent pas s’exécuter en parallèle car il existe un GGL (Giant Guild Lock). Cependant, les Threads de différentes Guilds peuvent s’exécuter en parallèle.

Vous pouvez penser un programme Ruby 2.x comme ayant une Guild unique. »

ruby_3_guilds_concurrency

« Un objet d’une Guild ne sera pas capable de lire ou d’écrire sur un objet mutable d’une Guild différente. Empêcher la modification [d’objets] permet aux Guilds de fonctionner en parallèle sans courir le risque d’accéder et de modifier les mêmes objets. »

ruby_3_guilds_object_access_restrictions

En revanche il sera possible de réaliser des « deep copy » d’objets d’une Guild à l’autre.

ruby_3_guilds_channels_object_copy

Il existera une méthode .freeze pour rendre immuable (constant) tout objet mutable :

# While Numeric types like Integers are immutable by
# default, Hash instances aren't.
mutable = [1, { "key" => "value" }, 3].freeze
 
# But if you freeze String or Hash instances and the
# Array instances that reference them, then you have
# a "deeply frozen" immutable object.
immutable = [
  "bar".freeze,
  { "key" => "value".freeze }.freeze
].freeze

Je vous invite à lire cette synthèse de Maciej Mensfeld qui récapitule ce que sont les Guilds.