Rails 5 et les tests de mailer

Les tests se passent dans le dossier test/mailers.

Créer un nouveau fichier avec le format suivant : nom_du_mailer_test.rb (ex : notification_test.rb)

Squelette du fichier :

require 'test_helper'

class NomDuMailerTest < ActionMailer::TestCase

end

/!\ Vérifier dans le fichier de config/test.rb, que la ligne suivante soit bien présente :

config.action_mailer.delivery_method = :test
  1. On crée le mail en appelant la méthode du mailer :
    email = MonMailer.envoi_mail('expediteur@example.com', 'destinataire@example.com', 'sujet')
  2. On envoie le mail et on teste qu’il est bien parti :
    assert_emails 1 do
      email.deliver_now
    end
  3. On teste l’expéditeur, le sujet, le destinataire …. :
    assert_equal ['expediteur@example.com'], email.from assert_equal ['destinataire@example.com'], email.to assert_equal 'sujet', email.subject

Pour plus d’informations sur les tests de mailers, et notamment l’utilisation avec des fixtures, aller lire l’article suivant : http://guides.rubyonrails.org/testing.html#testing-your-mailers.

Fixtures into fixtures

Lors de tests, on peut être amenés à jouer avec les associations has_many, belongs_to & co.

Si vous travaillez avec des associations, vous pouvez simplement définir une référence entre deux fixtures différents. Voici un exemple avec une association belongs_to / has_many :

# Dans fixtures/categories.yml
a-propos:
  nom: About
# Dans fixtures/articles.yml
one:
  title: Coucou monde !
  contenu: Hello world !
  categorie: a-propos

La clé « one » trouvé dans fixtures / articles.yml a une clé-valeur categorie => « a-propos ». Ceci indique à Rails de charger la catégorie « a-propos » dans « categorie » de l’article concerné.

 

Sources : http://api.rubyonrails.org/v5.1.1/classes/ActiveRecord/FixtureSet.html

Rails streaming de videos

Ayant voulu mettre une vidéo en utilisant le streaming de rails je me suis heurté à un problème sur Internet Explorer 11

def recuperer_fichier
  @fichiers = Fichier.find(params[:id])
  Dir.glob("mon_chemin_vers_fichier/fichiers/*").each do |nom_fichier|
    send_file nom_fichier, disposition: "inline"
  end
end

Ci-dessus la méthode employé pour récupérer les vidéos (format mp4 et webm) qui marche sous firefox et chrome.

cependant sur Internet explorer 11 et safari une erreur survient.

Concernant Internet explorer, si au chargement de la page dans l’onglet reseau la vidéo possède le mime type suivant Application/octet-stream c’est qu’il faut passer un attribut type dans le send file.

  send_file nom_fichier, type: mime_type_du_fichier ,disposition: "inline"

Concernant Safari je n’ai trouvé aucune solution pour le moment.

Rails 5 : Comment récupérer les erreurs de validate d’un model enfant dans le model parent

Le cas : Un model Utilisateur peut avoir 0+ model Enfant (définit par un has_many). Enfant a un validate_presence_of sur le prénom. On veut savoir dans le modèle parent Utilisateur quand il y a une erreur de validation dans le modèle enfant Enfant.

Pour ce faire, il faut rajouter un hook au validate du modèle parent :

En clair, on ajoute les errors.full_messages des enfants dans le errors[:enfants] d’un utilisateur

Ensuite, pour les récupérer en front par exemple, il suffit de :

 

Gestion de la cohérence des migrations Rails 5 en utilisant des branches Git

Nous nous sommes posés la question théorique suivante : que se passe t-il si une branche master, représentant le site de production, continue d’évoluer en parallèle d’une branche de développement, représentant un site de démonstration de fonctionnalités ?

Est-ce que le framework Rails serait capable de retrouver la cohérence entre des migrations en se basant sur leurs timestamp de génération, sachant que tous les fichiers du dossier db/ sont correctement rassemblés lors des merges, mais que le schema.rb perd l’information des champs ajoutés en base au fur et à mesure ?

Nous avons tenté l’expérience, voici notre retour :

1. Pour mettre la branche dev sur la branche master, on commence par merger master dans dev pour que les conflits aient lieu en dev.

Lors d’un conflit de schema.rb, conserver toujours la migration la plus ancienne :

Après conflit, il doit rester ceci :

2. On applique ce nouveau schema.rb sur la branche dev avec l’habituel rake db:migrate et on observe la réécriture du schema.rb :

3. On va sur la branche master et on merge dev dans master. Normalement, on est content.

Délai de répétition d’une vidéo

Si un jour l’envie vous prend de vouloir lancer une vidéo en lecture automatique et que vous voulez que celle-ci ce relance automatiquement après un délai de quelques secondes, voici comment faire :

il suffit d’écrire la balise vidéo habituelle avec les options que vous souhaitez :

<video width="561" height="374" controls="controls" autoplay="true" id="video" >
  <source src="video.mp4" type="video/mp4" />
  <source src="video.webm" type="video/webm" />
</video>

puis d’y inclure le JS suivant :

<code>
<script>
  $(function() {
    var video = $("#video").get(0);
    video.addEventListener("ended", function() {
      setTimeout(function(){
          video.play();
      }, 5000);
    });
  });
</script>
</code>

Petite explication du js :

On récupère avec l’id notre balise vidéo via un get(0).

Puis on écoute l’événement ended du gestionnaire d’événement, dans lequel on aura au préalable configuré un setTimeout avant de relancer la vidéo avec la fonction .play()

Dans notre cas la vidéo se relance 5 secondes après s’être arrêter.

Astuce : si l’attribut loop est présent dans la balise vidéo l’événement ended n’arrive jamais.

 

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 😉