Je n’ai pas pour habitude de traduire les textes d’autrui, je ferai une exception pour cette fois sans rien y ajouter ni retrancher. D’abord parce que je ne vois pas ce que je pourrais dire de plus, ensuite parce que l’esprit même du texte rentre pour une bonne partie dans son intérêt, enfin parce que Rails manque trop de bonnes documentations en français, et que celle ci vaut le déplacement.

Ceci est la traduction de Ruby on Rails rake tutorial, de Greg Pollack.

Si vous développez en Ruby on Rail, vous avez certainement utilisé l’utilitaire Rake afin de lancer les tests, ou pour mettre à jour votre base de données en lançant rake db:migrate. Mais avez-vous vraiment conscience de ce qui se passe lorsque vous lancez une tâche Rake ? Savez-vous qu’il vous est possible de créer vos propres tâches rake, ou même de créer votre propre librairies de ces si pratiques descripteurs de tâches ?

Voici quelques cas d’usages des tâches Rake :

  • Récupérer une liste d’inscrits afin d’envoyer un email.
  • Lancer des traitements de calcul et de statistiques pendant la nuit.
  • Supprimer et régénérer le cache d’une application.
  • Sauvegarder ma base de données et mon dépôt subversion.
  • Exécuter n’importe quel traitement de données.
  • Me saouler la gueule.

Dans ce billet, vous allez découvrir le pourquoi de l’existence de Rake, et comment il peut vous aider dans le développement de vos applications Rails. À la fin de cette lecture, vous devriez être capable de créer vos propres tâches et de finir bourré comme un coing grâce à Rake en seulement trois petites étapes.

Pourquoi faire un retour en arrière ?

Pour comprendre les origines de Rake, il convient de s’intéresser un peu à son vénérable ancêtre, j’ai nommé Make.

Je vous propose un voyage dans le temps, à l’époque où le code devait obligatoirement être compilé, c’est à dire avant que l’iPhone et les langages interprétés ne parcourent la planète.

À cette époque, quand vous téléchargiez un logiciel, vous obteniez généralement des fichiers contenant le code source et un script shell. Ce script contenait tout ce que votre machine avait besoin de savoir afin de compiler, lier et construire votre application. Vous lanciez install_me.sh, chacune des lignes du fichier était exécutée, et vous obteniez au bout d’un moment un exécutable.

Ça marchait plutôt pas mal, il faut l’avouer, à part peut être pour les quelques développeurs de l’application. Chaque fois qu’ils changeaient la moindre ligne de code, il leur fallait relancer le script afin de tout recompiler. cela prenait évidemment un temps fou pour les gros logiciels.

En 1977, Stuart Feldman des laboratoires Bell inventa Make, qui résolut la majorité des problèmes liés aux compilations à répétition. Make est également utilisé afin de compiler des programmes, mais avec deux différences majeures :

  1. Make est capable de reconnaître quels fichiers ont été modifiés depuis la dernière compilation. Il pouvait donc ne compiler que ce qui avait changé d’une fois sur l’autre. Autant dire que cela a dramatiquement réduit les temps de compilation.
  2. Make peut aussi résoudre les dépendances. Il sait que pour être compilé, le fichier A a besoin du fichier B, qui lui-même a besoin de fichier C. Ce qui fait que si Make tente de compiler le fichier source A et que le fichier source B n’a pas été compilé, il commence par traiter ce dernier.

Je ne devrais pas continuer sans vous expliquer que Make est un simple exécutable, un peu comme “ls”, ou “dir”, et qu’il a besoin pour fonctionner d’un fichier annexe, appelé un “makefile”, qui va contenir toutes les sources et les dépendances du programme à compiler. Les “makefiles” ont leur propre syntaxe cabalistique qu’il ne nous sera pas donné de voir ici.

Au fur et à mesure, Make a évolué, et on a commencé à l’utiliser pour d’autres langages. En fait, de nombreux développeurs Ruby l’utilisaient avant l’avènement de Rake.

“Mais, Ruby n’a pas besoin d’être compilé, alors à quoi ça sert ?”. Vous ne l’avez peut-être pas dit, mais vous l’avez certainement pensé très fort.

Oui, je suis d’accord, Ruby est un langage interprété, et nous ne compilons pas notre code. Alors pourquoi utiliser Make ?

Eh bien, pour au moins deux raisons fondamentales :

  1. Automatiser des tâches : toute application d’envergure se termine presque fatalement par l’écriture de scripts à lancer en ligne de commande, qu’il s’agisse de lancer une maintenance, supprimer un cache ou faire évoluer la base de données. Et au lieu de créer une dizaine de petits scripts shell, ou un gros, vous pouvez aussi créer un seul “Makefile” dans lequel vous organiserez les choses à faire en tâches. Vous pouvez ensuite exécuter les tâches en lançant “make stupid”, c’est à dire littéralement “fait l’imbécile”, et cela lance la tâche “stupid”.
  2. Vérifier la résolution des dépendances entre les tâches. Quand vous lancez des opérations de maintenance en chaîne, vous en répétez fatalement certaines d’une tâche à l’autre. Par exemple, migrer une base de données et en faire une extraction ont toutes deux une connexion à la base pour prérequis. Vous pouvez donc créer une tâche de connexion qui sera lancée juste avant la migration.

Et Rake, dans tout ça ?

Il y a quelques années, Jim Weirich utilisait régulièrement Make sur un projet en Java. Tout en travaillant sur son Makefile, il réalisa combien il lui serait pratique d’avoir des bouts de Ruby dedans. Et Rake était né.

Jim donna à Rake la possibilité de réaliser des tâches, résoudre les dépendances, et même éviter de recommencer des tâches qui avaient déjà été réalisées. C’est évidemment quelque-chose que nous ne faisons pas vraiment, puisque Ruby n’est pas un langage compilé.

Comment ça marche ?

Imaginons que je veuille me saouler la gueule, quelles en seraient les étapes ?

  1. J’irais acheter à boire.
  2. Je me servirais à boire.
  3. Je boirais jusqu’à plus soif.

Si je voulais utiliser Rake pour chacune de ces tâches, je créerais un fichier nommé Rakefile, qui contiendrait quelque-chose comme ça :

task :acheteABoire do
  puts "Et hop, une bouteille de vodka"
end  

task :sersMoiUnVerre do
  puts "Mi vodka, mi Redbull"
end

task :ceSoirJeBois do
  puts "Wow, y'a du brouillard ce soir. Ggggarçon – hips – un autre !"
end

Je peux maintenant lancer ces tâches depuis le répertoire où se trouve mon Rakefile, comme ça :

$ rake acheteABoire
 Et hop, une bouteille de vodka
$ rake sersMoiUnVerre
 Mi vodka, mi Redbull
$ rake ceSoirJeBois
 Wow, y'a du brouillard ce soir. Ggggarçon – hips – un autre !

Sympa non ? Malheureusement, je ne vérifie pas que les tâches sont effectuées dans le bon ordre. Et bien qu’il puisse m’arriver de souhaiter être bourré avant d’avoir commencé à boire, ou même avant d’avoir acheté quoi que ce soit, ce n’est évidemment pas possible.

Rake et les dépendances

task :acheteABoire do
  puts "Et hop, une bouteille de Vodka"
end

task :sersMoiUnVerre => :acheteABoire do
  puts "Mi vodka, mi Redbull"
end

task :ceSoirJeBois => :sersMoiUnVerre do
  puts "Wow, y'a du brouillard ce soir. Ggggarçon – hips – un autre !"
end

Maintenant, il me faut acheter à boire et me servir un verre avant de pouvoir me saouler la gueule convenablement. Cela donne donc :

$ rake acheteABoire
 Et hop, une bouteille de vodka
$ rake sersMoiUnVerre
 Et hop, une bouteille de vodka 
 Mi vodka, mi Redbull
$ rake ceSoirJeBois
 Et hop, une bouteille de vodka 
 Mi vodka, mi Redbull
 Wow, y'a du brouillard ce soir. Ggggarçon – hips – un autre !

Comme vous pouvez le voir, avant que je ne puisse boire, il me faut acheter de l’alcool et me servir un verre. L’ordre des choses est rétabli.

Avec le temps, vous risquez d’être tenté d’accroître votre alcoolisme, et donc votre Rakefile. Vous serez aussi certainement tentés de m’inviter à boire un coup. Comme dans tout logiciel d’envergure, rien ne vaut une bonne documentation.

Comment documenter mes tâches ?

Rake possède une manière très simple de documenter les tâches. Il s’agit de “desc” et c’est simple comme bonjour :

desc "Cette tâche va aller acheter la vodka"
task :acheteABoire do
  puts "Et hop, une bouteille de Vodka"
end

desc "Cette tâche prépare le cocktail"
task :sersMoiUnVerre => :acheteABoire do
  puts "Mi vodka, mi Redbull"
end

desc "Ce soir je bois"
task :ceSoirJeBois => :sersMoiUnVerre do
  puts "Wow, y'a du brouillard ce soir. Ggggarçon – hips – un autre !"
end

Comme vous pouvez le voir, chacune de mes tâches a maintenant une description. Mes amis peuvent les lire en tapant rake -T, ou rake --tasks.

$rake --tasks  
 rake acheteABoire    # Cette tâche va aller acheter la vodka
 rake ceSoirJeBois    # Ce soir je bois
 rake sersMoiUnVerre  # Cette tâche prépare le cocktail

Facile non ?

L’espace de nommage de Rake

Maintenant que vous êtes devenu un bon alcoolique, vous utilisez un grand nombre de tâches Rake, et vous avez besoin d’un bon moyen de les classer. C’est là que viennent les espaces de nommage. Si je devais utiliser un espace de nommage dans l’exemple précédent, il deviendrait :

namespace :alcoolique do
  desc "Cette tâche va aller acheter la vodka"
  task :acheteABoire do
      puts "Et hop, une bouteille de Vodka"
  end

  desc "Cette tâche prépare le cocktail"
  task :sersMoiUnVerre => :acheteABoire do
      puts "Mi vodka, mi Redbull"
  end

  desc "Ce soir je bois"
  task :ceSoirJeBois => :sersMoiUnVerre do
      puts "Wow, y'a du brouillard ce soir. Ggggarçon – hips – un autre !"
  end
end

Vous n’avez plus qu’à classer vos tâches en fonction des catégories, et OUI, vous pouvez en avoir plusieurs dans un même fichier. Maintenant, rake --tasks me donne :

$rake --tasks  
 rake alcoolique:acheteABoire    # Cette tâche va aller acheter la vodka
 rake alcoolique:ceSoirJeBois    # Ce soir je bois
 rake alcoolique:sersMoiUnVerre    # Cette tâche prépare le cocktail

Tu ne pourrais pas être un peu sérieux pour une fois ?

Les applications Rails viennent avec un lot de tâches prédéfinies, dont vous pouvez obtenir la liste en allant dans le répertoire de travail et en lançant rake --tasks. Si vous n’avez pas encore essayé, allez y, je peux attendre deux minutes…

Afin de créer une nouvelle tâche rake pour votre application Rails, vous devez dans un premier temps vous rendre dans le répertoire /lib/tasks (créé par défaut). Si vous y placez vos Rakefiles avec le suffixe “.rake”, celles-ci seront automatiquement récupérées par Rake. Nous allons ajouter la tâche suivante à notre application Rails.

namespace :utils do
  desc "Crée des répertoires vides s'ils n'existent pas"
  task(:create_directories) do
  
    # Les répertoires dont j'ai besoin
    shared_folders = ["icons","images","groups"]
          
    for folder in shared_folders
        
      # Existent-ils ?
      if File.exists?("#{RAILS_ROOT}/public/#{folder}")
        puts "#{RAILS_ROOT}/public/#{folder} existe"
      else
        puts "#{RAILS_ROOT}/public/#{folder} n'existe pas, nous le créons donc"
        Dir.mkdir "#{RAILS_ROOT}/public/#{folder}"
      end
    end
  end
end

Remarquez comment j’ai pu utiliser #{RAILS_ROOT} pour obtenir le chemin complet de l’application. Maintenant, si je lance rake --tasks dans le répertoire de mon application, je trouverai les nouvelles fonctions mélangées aux autres tâches Rake.

..
rake tmp:pids:clear              # Clears all files in tmp/pids
rake tmp:sessions:clear          # Clears all files in tmp/sessions
rake tmp:sockets:clear           # Clears all files in tmp/sockets
rake utils:create_directories    # Crée des répertoires vides s'ils n'existent pas
...

Génial ! Maintenant voyons comment ça devient vraiment utile.

Et on peux accéder aux modèles Rails depuis une tâche ?

Évidemment ! En fait, c’est même pour ça que j’utilise Rake : écrire des tâches que je lance à la main ou que je planifie en utilisant Crontab. Comme je le disais au début de l’article, j’utilise Rake pour les tâches suivantes :

  • Récupérer une liste d’inscrits afin d’envoyer un email.
  • Lancer des traitements de calcul et de statistiques pendant la nuit.
  • Supprimer et régénérer le cache d’une application.
  • Sauvegarder ma base de données et mon dépôt subversion.
  • Exécuter n’importe quel traitement de données.
  • Me saouler la gueule.

Sacrément utile, et facile qui plus est. Le code suivant récupère la liste des utilisateurs dont l’abonnement arrive à expiration et leur envoie un courriel :

require File.expand_path(File.dirname(__FILE__) + "/../../config/environment")

namespace :utils do
  desc "Finds soon to expire subscriptions and emails users"
  task(:send_expire_soon_emails => :environment) do
        # Find users to email
        for user in User.members_soon_to_expire
                puts "Emailing #{user.name}"
                UserNotifier.deliver_expire_soon_notification(user)
        end
  end
end

Comme vous pouvez le voir, nous accédons au modèle en deux étapes : la directive require et le => :environment.

  1. require File.expand_path(File.dirname(__FILE__) + “/../../config/environment”)
  2. task(:send_expire_soon_emails => :environment) do

Pour lancer ça tous les soirs à minuit, je n’ai qu’à créer une tâche dans Crontab, de cette manière :

0 0 * * * cd /var/www/apps/rails_app/ && /usr/local/bin/rake RAILS_ENV=production utils:send_expire_soon_emails

Où trouver plus d’exemples ?

Maintenant que vous en savez assez pour écrire des tâches utiles, je me suis dit que je pourrais vous laisser repartir avec quelques ressources supplémentaires. La meilleure manière de vous améliorer est encore de lire le code des autres, comme ces tâches Rake :

C’est fait. Si vous en connaissez d’autres, n’hésitez pas à les publier dans les commentaires.

Toujours là ? Ça tombe bien, les gens de RailsEnvy à qui nous devons cet excellent article cherchent de nouveaux collaborateurs. Contactez Greg at RailsEnvy si ça vous intéresse.

Brasserie des Houillères

Perry the Platypus wants you to subscribe now! Even if you don't visit my site on a regular basis, you can get the latest posts delivered to you for free via Email: