Comment ne pas se répéter dans ses templates d’e-mails sous Ruby on Rails ?

By Arnaud Doucerain, on April 21, 2016
5 min

Dans un précédent article, nous vous parlions de comment organiser vos différents types d’e-mails sous Rails, et dans un autre nous vous parlions de comment adapter ses e-mails à tous les clients de messagerie. Ce dernier article donne de nombreuses règles de formatage à suivre pour que les e-mails aient une apparence homogène.

Le problème de suivre ces règles de formatage est qu'on arrive assez vite à avoir du code lourd (tables...) et qui se répète (la balise font appelée à chaque td). En outre, il est normal que le design et l'architecture globale des e-mails se ressemble (logo de votre service en haut, par exemple) ; mais plus le nombre de ses templates d'e-mails grandit et plus on constate des répétitions. Ce n’est pas très DRY (Don’t repeat yourself) !

Pour tout cela, nous avons décidé d'avoir recours aux helpers de Rails. Mais des helpers un peu particuliers, puisqu'ils génèrent du HAML (un langage qui est compilé en HTML).

Tornade de Rails, Haml et E-mails

L'idée derrière cela est de n'avoir à appeler qu'une méthode de helper pour afficher :

  • une ligne de tableau (tr)
  • une cellule de tableau (td)
  • un contenu

Pour cela, on utilise la méthode haml_tag qui, comme son nom l’indique, crée un tag (tr, td, div…) de notre choix.

Pour afficher une ligne de tableau avec le logo d’elCurator, notre méthode est donc la suivante :

def header_row
    haml_tag :tr do
        haml_tag :td, id: 'header' do
            haml_tag :div, image_tag('mailer/logo.png', alt: 'elCurator', border: 0), id: 'logo'
        end
    end
end

Le code est assez transparent, cela génère le HTML suivant :

<tr>
  <td id="header">
    <div id="logo"><img src= "assets/mailer/logo.png" alt="elCurator" border="0" /></div>
  </td>
</tr>

Mais comme la plupart des e-mails contiennent autre chose que juste un logo, il est aussi utile d’avoir une cellule de tableau générique dans laquelle on peut mettre ce qu’on veut. Or, si l’on souhaite avoir une police particulière dans chaque cellule, il est nécessaire d'ajouter la balise font (<font face="arial"></font>) à chaque fois. Enfin, il peut être intéressant de pouvoir préciser une classe et/ou un id à une cellule (pour pouvoir lui ajouter un padding, par exemple). Voilà donc la méthode générique row de notre helper qui fait tout cela :

def row(options = {})
    haml_tag :tr do
        haml_tag :td, class: (options[:td_class] if options[:td_class].present?), id: (options[:td_id] if options[:td_id].present?) do
            haml_tag :font, face: 'arial, sans-serif' do
                yield if block_given?
            end
        end
    end
end

Cette méthode prend des options en paramètres, pour qui veut préciser une classe ou un id au td. Elle ajoute la police Arial. Puis elle affiche un bloc par l’intermédiaire de yield : cela permet de d’ajouter un contenu à la cellule, puisqu’on veut tout de même qu’elle contienne un texte !

Enfin, en pratique, ces méthodes de helper s’insèrent dans vos templates haml de la manière suivante :

#new-user-mail
  - main_table do
    - header_row
    - row td_id: 'manifest' do
      = "Bienvenue sur elCurator !"

Cela permet d’éviter beaucoup de répétitions, non ? ;)

Deux remarques :

  • main_table est une méthode qui ajoute seulement la balise table, nécessaire pour débuter un tableau
  • Pour pouvoir utiliser votre helper de mails (que l’on a très justement appelé MailersHelper) dans votre classe de Mailer, vous devez appeler manuellement ce helper en haut de votre classe, comme ceci : add_template_helper(MailersHelper)

Tester son Helper HAML avec Rspec

Faire un helper, c’est bien, mais le tester c’est mieux. Pour cela, il faut en premier lieu penser à inclure des classes relatives à HAML et permettant de tester ses méthodes :

  before :each do
    helper.extend Haml
    helper.extend Haml::Helpers
    helper.send :init_haml_helpers
  end

Cela étant fait, découvrons comment tester notre précédente méthode header_row qui affichait le logo de notre application :

  describe 'header_row' do
    it { expect(helper.capture_haml{ helper.header_row }).to match /<tr>\s+<td id='header'>\s+<div id='logo'><img alt=\"elCurator\" border=\"0\" src=\"\/assets\/mailer\/logo\.png\" \/><\/div>\s+<\/td>\s+<\/tr>\n/ }
  end

Ce test en une ligne est assez simple, et permet de s’assurer que le HTML généré est bien ce qui est attendu. Il utilise la méthode capture_haml qui permet de faire la conversion en HTML à partir de notre méthode header_row. Par la suite, on s’assure simplement que la valeur de retour “matche" (expression régulière) le HTML attendu. Les \s+ indiquent qu’il peut y avoir plusieurs espaces ou sauts de ligne entre chaque balise.

Pour une méthode plus compliquée telle que row, voici son test :

  describe 'row' do
    context 'without options without block' do
      it { expect(helper.capture_haml{ helper.row }).to match /<tr>\s+<td>\s+<font face='arial, sans-serif'>\s+<\/font>\s+<\/td>\s+<\/tr>\n/ }
    end

    context 'with options without block' do
      let(:options) { {td_id: 'cell'} }

      it { expect(helper.capture_haml{ helper.row options }).to match /<tr>\s+<td id='cell'>\s+<font face='arial, sans-serif'>\s+<\/font>\s+<\/td>\s+<\/tr>\n/ }
    end

    context 'with options with block given' do
      let(:options) { {td_id: 'cell'} }
      let(:cell_content_block) { lambda { helper.haml_tag :p, 'content of the cell' } }

      it { expect(helper.capture_haml(cell_content_block) { |block| helper.row options, &block }).to match /<tr>\s+<td id='cell'>\s+<font face='arial, sans-serif'>\s+<p>content of the cell<\/p>\s+<\/font>\s+<\/td>\s+<\/tr>\n/ }
    end
  end

Le premier context est similaire au test précédent. Le second context s’assure que si on précise un id pour le td depuis les options de la méthode, il apparaît bien dans le HTML généré.

Le troisième context est plus complexe et vérifie qu’on peut bien ajouter un contenu à l’intérieur de la cellule du tableau, en fournissant un bloc à la méthode. On crée donc un bloc grâce à lambda dans le deuxième let, et on le fournit à notre méthode row plus loin dans un bloc de la méthode capture_haml. Ce passage est assez compliqué, mais si vous suivez l’exemple, vous devriez avoir un test qui passe au vert !

Conclusion

Nous espérons qu’avec cet article les e-mails de votre application seront beaux et homogènes sur tous les clients mails, sans pour autant rendre vos templates lourds et plein de répétitions.

A noter qu’on pourrait pousser les tests pour vérifier que chaque e-mail ne contient pas une classe CSS “interdite" pour les vieux Outlook. C’est une piste d’amélioration de notre côté, nous ne manquerons pas d’en faire un prochain article !

Liked it ? Share it

4a5f562af35ae06dc6634d6d00edc988
About the author

Arnaud Doucerain is web developer at elCurator.