Ruby on Rake, ou comment Rake m'a rendu alcoolique

Le 11 juin 2007 à 20h38 | 6 commentaires

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, ya 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, ya 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

Des menus déroulants à choix multiples sans select

Le 23 mai 2007 à 23h36 | 12 commentaires

On se retrouve tous un jour ou l’autre confronté au problème de questions à réponses multiples et encore plus nombreuses, sans possibilités de filtrage préalable. Dans ce cas, la première solution qui vient à l’esprit est celle du menu déroulant à choix multiple, et elle peut sembler alléchante à plus d’un point de vue.

Grave erreur ! Il n’y a pas pire pour votre utilisateur que cette extension de notre bon vieux select, dont j’avais sérieusement remis la fiabilité en question il n’y a pas si longtemps que cela. Je me suis rendu compte à mes dépends que les menus déroulants à choix multiples étaient aux formulaires ce que le blink et les gifs animés étaient aux pages web des années 90 : une plaie.

On fait tous des conneries, et moi le premier. Confronté à des formulaires dans lesquels je rencontrais de nombreux champs à réponse multiple sans aucun contrôle de ma part sur leur nombre, j’ai mis du menu déroulant partout, persuadé de détenir la bonne méthode. Horreur, malheur, dirait la sorcière si elle était là, le truc s’avère épouvantable à utiliser.

  • Un clic de souris à coté, et on perd toute sa sélection.
  • La sélection de cases non adjacentes relève du défi olympique.
  • Difficile à styler, on s’y perd facilement entre les réponses.

J’ai cherché un moment avant de trouver une solution satisfaisante vues les contraintes qui m’étaient imposées :

  • Un nombre infini de réponses potentielles.
  • Accessibilité totale, donc pas de javascript.
  • Facilement personnalisable.

La solution

Pas besoin de chercher bien loin. Remonter à l’origine du problème et aux avantages apparents de la solution choisie devaient me permettre de prendre la bonne direction.
Dans un formulaire, la première chose à laquelle on pense quand il s’agit de réponses multiples, c’est “cases à cocher”. Malheureusement, celles-ci deviennent rapidement impossible à afficher au delà de quelques éléments. D’où l’utilisation du menu déroulant à choix multiple, dont l’ascenseur permet d’occuper un minimum de place, mais au détriment de l’utilisabilité.

Il me fallait donc combiner les avantages des cases à cocher et des menus déroulants à choix multiples, sans sacrifier à l’accessibilité :

  • Espace à l’écran restreint.
  • Réponses multiples.
  • Nombre de réponses indéfini et potentiellement infini.

Pour cela, je commence par englober mes réponses dans une div dont je force la hauteur et à laquelle j’attribue la propriété CSS overflow-y : auto. Voilà pour la partie déroulante.

J’ajoute ensuite une série de label dans lesquels j’englobe ma case à cocher. Si je trouve ça sémantiquement moyen – le label désigne le champ de formulaire, ce dernier ne saurait donc y être inclus – c’est structurellement valide. J’applique ensuite à chacun de mes labels la propriété CSS display: block

Ce dernier point est très important. En effet, si l’id de la case à cocher et la propriété for= du label sont renseignées correctement, la sélection du champ quand je clique sur le label s’étend à toute la ligne. Pratique non ?

Il ne me reste qu’à mettre un peu de couleur une ligne sur deux pour bien différencier les réponses, et le tour est joué. Évidemment, le résultat présenté est tout sauf joli, mais je vous rappelle que mon sens artistique avoisine le zéro absolu.

Télécharger le code source de l’exemple.

Les standards du web expliqués à ma mère

Le 20 mai 2007 à 17h10 | 10 commentaires

Les standards du web expliqués à ma mère

Ma mère utilise un ordinateur sous Windows Millenium “le seul avec lequel je n’ai jamais eu de problèmes”, appelle une unité centrale un “moteur” car ça fait du bruit et c’est ce qui fait propulse la machine, et a depuis longtemps oublié sa propre adresse courriel peu peu qu’elle l’ait jamais connu. Le jour où elle m’a demandé de lui expliquer ce qu’étaient ces fameux standards du web dont je parlais si régulièrement et qui avaient l’air tellement importants, je savais que la partie était tout sauf gagnée.

– Ces standards du web, c’est quoi ?
– Eh bien, c’est un code de bonne conduite pour créer des sites web qui ont la même apparence sous tous les navigateurs du marché.
– Ah, parce qu’il y a plusieurs navigateurs ? Je ne connais qu’Internet Explorer.
– Oui, il y en a tout un tas : Firefox, Netscape, Flock, Opéra…
– Et ça sert à quoi d’en avoir autant, ce ne serait pas plus simple de n’en avoir qu’un seul ?
– Hmmm… peut-être, si. Au fait, pourquoi roulez-vous dans une Clio verte ?
– Eh bien parce que c’est une bonne voiture, et qu’elle me plaît bien.
– Ce ne serait pas plus simple que tout le monde roule en Golf diesel bleue ? Plus de problèmes de pièces détachées, un seul type d’essence à la pompe… le bonheur quoi.
– Et laisser à une entreprise allemande le monopole de l’automobile en France ?
– Ben voilà, vous avez compris.
– Quel est le navigateur le plus utilisé ?
– Internet Explorer, avec 90% de parts de marché (la conversation est assez ancienne, ndlr).
– Et Internet Explorer suit ce code de bonne conduite ?
– Non, pas vraiment. En fait il ne suit que ce qu’il veut.
– Alors pourquoi parler de “standards”, s’ils ne sont pas suivis pas 90% des utilisateurs ?
– C’est que les standards du web n’ont pas valeur normative.
– On ne peut donc pas parler de norme. Dans ce cas, à quoi servent-ils ?
– Vous connaissez les normes ISO ? La norme ISO9002 par exemple.
– Oui, prend ta vieille mère pour une imbécile, je ne te dirai rien.
– L’institut ISO part d’une implémentation pour définir une norme. Le W3C définit des codes de bonne conduite à priori pour que les navigateurs puissent s’y conformer dans le futur.
– Le W3C ?
– Oui, une organisation indépendante composée de grands noms de l’industrie informatique. Elle élabore les préconisations que l’on appelle les standards du web.
– Et qui les rédige ?
– Des groupes de travail. L’activité du W3C va bien au delà de l’unification du rendu des pages web.
– Et Microsoft en fait partie ?
– Oui.
– Donc, si j’ai bien compris, les standards du web sont directives à valeur non normatives créées par des sociétés qui ne les appliquent pas dans leur propre produit, produit qui se trouve de fait être le navigateur standard sur le web. C’est bien ça ?
– En quelque sorte, oui…
– Eh beh, nous voilà biens…

Note : cette conversation a été à peine déformée, dans un soucis de concision et de clarification.

tu perds la boule ?

Hi, I'm Ruby on Rails! (2)

Le 15 mai 2007 à 20h05 | 2 commentaires

Après Java, c’est au tour de PHP de subir l’humour ravageur de Gregg et Jason, dans une autre parodie des publicités Apple. Et c’est bon !

Je trouve juste dommage qu’ils tournent sous Mephisto quand Typo leur tend les bras…

Hi, I'm Ruby on Rails !

Le 14 mai 2007 à 22h02 | 4 commentaires

Histoire de bien terminer la journée, je vous propose de découvrir cet amusant détournement des fameuses publicités Hi, I’m a PC / Hi I’m a Mac de la firme à la pomme sur les avantages comparés de Ruby on Rails et du champion toutes catégories des usines à gaz inutilisables, j’ai nommé Java. C’est drôle, fin, et tout à fait vrai.

Via le blog officiel de Ruby on Rails.

Développer une galerie web en rails, avec slideshow, resize des images, vignettes et RSS en 20 minutes, en mangeant un plat de sushis

Le 18 avril 2007 à 21h45 | 4 commentaires

On n’est jamais mieux servi que par soi-même, dit le proverbe. Aussi, afin de remplacer Photostack vraiment trop limité et brouillon ai-je décidé de développer ma propre galerie d’images, mais vite (et bien), car je n’ai pas vraiment que ça à faire.

En termes de fonctionnalités, je compte partir sur des bases simples, que j’améliorerai au fur et à mesure.

  • De multiples galeries.
  • La prise en charge du redimensionnement des images.
  • La création automatique de vignettes.
  • Un slideshow qui permette aux visiteurs de faire défiler l’ensemble d’une galerie sans aucun effort.
  • Un flux XML pour du RSS ou de l’atom.

Pour le côté technique, j’ai dit vite – le temps d’avaler un plat de sushi – et propre. Cela exclue donc forcément PHP, au profit de Ruby on Rails. Maintenant, on fait comme dans la chanson, on monte sur les tables, on lève les bras bien haut, allez c’est parti !

Création de l’application

Nous commençons par créer une nouvelle application Rails.

powerbook:~/Documents/code$ rails imagilia
powerbook:~/Documents/code$ cd imagilia

Et pour fêter ça, j’attrape un premier sushi à l’oursin. Miam !

Ajout des galeries

Nous créons maintenant tout ce dont nous avons besoin ajouter, parcourir, modifier et supprimer des galeries.

powerbook:~/Documents/code$ script/generate scaffold_resource gallery name:string description:text status:boolean position:int created_at:date updated_at:date

Notre galerie possédera les attributs suivants :

  • Un titre.
  • Une description.
  • Un état (en ligne / hors ligne).
  • Une position.

Nous avons utilisé les fonctionnalités de génération automatique de Rails. Elles nous permettent maintenant de disposer de :

  • Le formulaire de création et de modification de notre galerie /galleries/new.
  • L’affichage HTML (sommaire) de notre galerie: /galleries/1.
  • Un flux XML, for fun and profit : /galleries/1.xml

Aurais-je omis de dire que l’application est RESTful ? Oui ? Comme c’est dommage. Je vais donc noyer mon chagrin dans un second sushi, au saumon cette fois.

Ajout des images

Il nous manque le plus important : l’ajout et la visualisation des images. Comme précédemment, nous allons laisser Rails travailler pour nous et générer tout ce dont nous avons besoin, ou presque.

powerbook:~/Documents/code$ script/generate scaffold_resource picture gallery_id:int content_type:string filename:string thumbnail:string size:integer width:integer height:integer position:int title:string description:text status:boolean created_at:date updated_at:date

Nos images auront pour attribut :

  • Une galerie parente. Nous créerons le menu déroulant plus tard.
  • Un nom de fichier.
  • Une vignette.
  • Un sushi aux oeufs de saumon.
  • Un poids en ko.
  • Une hauteur en pixels.
  • Une largeur en pixels.
  • Une position.
  • Un titre.
  • Une description.
  • Un état (en ligne / hors ligne).
Gestion de l’upload et du redimensionnement

Pourquoi faire compliqué et réinventer la roue quand on peut faire simple et laisser les autres travailler pour soi ? Je vous propose donc d’installer le greffon attachment_fu, et de manger un sushi à l’anguille fumée avant qu’il ne soit complètement froid.

powerbook:~/Documents/code$ script/plugin install http://svn.techno-weenie.net/projects/plugins/attachment_fu/

C’est fait, il ne nous reste plus qu’à lier les briques ensemble.

Dans app/model/gallery.rb

Nous spécifions la relation entre les galeries et les images : à une galerie correspond plusieurs images.

class Gallery < ActiveRecord::Base has_many :pictures end

Et ligne 18 :

Il s’agit du champ correspondant à l’image que notre greffon s’attend à recevoir. Ne le décevons pas.

<%= f.file_field :uploaded_data %>

Dans app/model/picture.rb

Nous appelons notre greffon avec toutes les informations dont il a besoin :

  • Nous stockons nos images sur le disque local. Nous pourrions aussi le stocker sur le service S3 d’Amazone de manière totalement transparente.
  • L’image fera maximum 500 ko.
  • Elle sera redimensionnée en 600*450 pixels.
  • La vignette fera 120*90 pixels.
class Picture < ActiveRecord::Base belongs_to :gallery has_attachment :content_type => :image, :storage => :file_system, :max_size => 500.kilobytes, :resize_to => 600x450>, :thumbnails => { :thumb => 120x90> } validates_as_attachment end

Dans app/views/pictures/new.rhtml ligne 5

<% form_for(:picture, :url => pictures_path) do |f| %> <% form_for(:picture, :url => pictures_path, :html => { :multipart => true }) do |f| %>

Tout ça m’a donné faim. Et vous ?

Dans app/vews/galleries/show.rhtml

<% for image in @gallery.pictures %> <p> <%= link_to (image_tag(image.public_filename(:thumb), :alt => image.title), image.public_filename) %><br /> <%= image.title%> <% image.description %> </p> <% end %>
L’affichage plein écran et le slideshow

Une fois de plus, pas question de réinventer la roue quand d’autres le font pour moi. Nous nous en allons donc faire un tour du côté de chez Xuan et télécharger Splash Image, un script clica convi qui remplace avantageusement Lightbox sans nécessité 2go de RAM. Et pourquoi ne pas manger un sushi au thon tant qu’on y est ?

wget http://www.chez-xuxu.net/ressources/javascript/splash.image/splash.image.zip

Nous décompressons l’archive dans public/javascripts, et faisons les inclusions qui vont bien.

Dans app/views/layout/galleries.rhtml

<%= stylesheet_link_tag ‘splash.image/splash.image.css’ %> <%= javascript_include_tag splash.image/splash.image.js %>

Dans app/vews/galleries/show.rhtml

Nous remplaçons la ligne précédemment ajoutée par celle-ci :

<%= link_to image_tag(image.public_filename(:thumb), :alt => image.title ), , {:rel => "splash.image|groupe1"}, image.public_filename %>

Elle nous permet de grouper les images de cette galerie et de les faire défiler en pleine fenêtre comme si de rien n’était.

Conclusion

Voilà, c’est fini, ou presque. Il ne me reste plus qu’à ouvrir mon plat de maki et à nettoyer tout ça. Certes, ce n’est pas bien joli, mais ça marche. Je vais faire un peu de nettoyage dans le code, rajouter une feuille de style correcte, et mettre les sources sur un subversion afin que vous puissiez récupérer tout ça. Merci beaucoup à Bastien pour m’avoir montré le plugin upload_fu et avoir supporté mes blagues vaseuses toute la journée.

Vendredi, nous ajouterons une authentification basique, l’upload de masse depuis un système de fichier ou un ftp, les jolies URL search engine friendly et les tags le temps de nous siffler une ou deux pintes de Guiness que vous aurez eu la gentillesse de m’offrir par SMS comme il se doit.

[Edit] J’ai mis une démonstration du résultat final en ligne. Elle est accessible sur 20 minutes gallery et la démonstration est totalement fonctionnelle.

Il est bien grand ce petit

Passer d'une application single user à une application multi users

Le 14 avril 2007 à 21h09 | 0 commentaire

Vous avez développé une petite application pour vos propres besoins, par exemple un outil de publication en ligne. Des gens l’ont remarquée et ont commencé à s’y intéresser, au point que vous la publiiez sous une licence quelconque, pour le plus grand bien de la communauté. Et un jour, ce qui allait très bien pour une seule personne ne suffit plus, et vous décidez de franchir le pas : rendre votre application multi utilisateurs. Belle initiative, mais par où commencer ?

Quels utilisateurs ?

Commencez par remettre à plat l’ensemble des fonctionnalités de votre outil. Recensez chacune d’entre elles et classez les par groupe en fonction des utilisateurs “type” qui seront amenés à les utiliser.

Dans notre cas, nous séparons les fonctionnalités de l’application en 3 groupes distincts :

L’administration
  • La configuration.
  • Le paramètrage du thème.
  • La sélection des greffons.
  • La gestion des utilisateurs.
La publication
  • La gestion / publication des billets.
  • La gestion des catégories.
  • La gestion des média.
  • La modération des commentaires.
La contribution
  • Ajout de commentaires.

Et voilà, nous avons défini 3 profils types, et défini leurs attributions possibles. Nous aurions pu en définir plus, par exemple un rédacteur qui aurait des droits d’écriture mais pas de publication, ou un modérateur pour les contributions qui n’aurait pas de droits en écriture, mais nous choisissons la simplicité. Reste maintenant à l’implémenter dans notre application, et là, deux choix s’offrent à nous.

Implémentation par profils

L’implémentation par profils est la plus simple à mettre en oeuvre, au détriment de la souplesse. On attribue un profil à chaque utilisateur, puis, pour chacune des actions possibles, on contrôle le profil de l’utilisateur, et on agit en conséquence. Pour cela, on va créer 3 méthodes, vérifiant que l’utilisateur dispose bien du profil voulu :

def is_admin si notre utilisateur a le profil administrateur alors la condition est vérifiée sinon elle est fausse end def is_publisher si notre utilisateur a le profil rédacteur ou que notre utilisateur a le profil administrateur alors la condition est vérifiée sinon elle est fausse end def is_contributor si notre utilisateur a le profil contributeur ou que notre utilisateur a le profil a le profil rédacteur ou que notre utilisateur a le profil administrateur alors la condition est vérifiée sinon elle est fausse end

Vous remarquerez que pour chaque profil inférieur, on vérifie également que l’utilisateur dispose du profil supérieur. En effet, on considère que les utilisateurs hiérarchiquement les plus élevés disposent de fait des droits les plus bas.

Implémentation par droits

Beaucoup plus complexe à mettre en place, l’implémentation par droit est en revanche extraordinairement souple. Elle ne convient cependant pas à n’importe quelle application car elle a son petit effet “usine à gaz”, vous pèserez donc bien le pour et le contre avant de la mettre en place.

Le principe est simple : vous définissez une série de droits ; chacun de ces droits correspond à une action possible sur l’application : créer une catégorie, modifier un billet, effacer un commentaire, afficher la liste des utilisateurs… Vous remarquerez au passage que ce système de droits repose sur le CRUD (Create, Read, Update, Delete) et trouve tout à fait sa place dans une application RESTful, mais je m’égare. Une fois définis les droits, vous créez les profils fonctionnels que nous avons vus tout à l’heure, et vous leur attribuez les droits qui vont bien. L’administrateur les aura à priori tous, le contributeur n’en aura que très peu. Pour cela, faites un tableau de correspondance entre les deux. Il y a d’ailleurs de fortes chances pour que vous ayez le même dans votre application :

Droits Administrateur Rédacteur Contributeur
Créer un billet X X
Ajouter un utilisateur X
Ajouter un commentaire X X X

Vous assignez ensuite un profil à chaque utilisateur. Pour chaque action possible, vous vérifierez que l’utilisateur dispose bien du droit adéquat. Pour cela, on va créer une méthode toute simple :

def has_right(droit) si notre utilisateur a le droit correspondant alors la condition est vérifiée sinon elle est fausse end

Deux choses rendent cette implémentation par droits formidablement souple :

  • Vous pouvez ajouter autant de profils que vous le souhaitez, puisque ceux-ci ne sont que des conteneurs à doits. Les droits sont en effet liés à l’utilisateur et le profil ne sert qu’à les grouper.
  • Vous pouvez très facilement surcharger un profil en attribuant ou supprimant des droits à un utilisateur spécifique.

Et maintenant ?

Il ne vous reste plus qu’à vous mettre au travail, en réfléchissant bien à ce que vous souhaitez faire de votre application. Chaque système a ses avantages et ses inconvénients, et passer de l’un à l’autre est tout sauf évident.

jupiler, je sais pourquoi

Et pour ceux qui pensent que ce billet est une réflexion sur ce que pourrait devenir Typo dans un futur proche, vous vous trompez. 50% du code est déjà fait, il me reste à le propager à l’ensemble de l’application.

Le Google developpers day passera à Paris

Le 11 avril 2007 à 19h35 | 1 commentaire

Le 31 mai prochain, Google organise une journée d’ateliers et de conférences autour de ses nombreuses API à destination des développeurs du monde entier. L’étape française se déroulera à Paris, dans un lieu tenu secret, de 13 à 21 heures. Cette journée représente une excellente opportunité de rencontrer les ingénieurs qui réalisent ces applications que nous utilisons au quotidien et pour vous familiariser avec l’énorme potentiel de ces outils.

Une API est un protocole permettant à une application de communiquer avec une application tierce. Les API ont joué un très grand rôle dans l’essor du web 2.0, créant notamment la vague des mashups, des applications utilisant de nombreuses API pour un résultat parfois étonnant.

Je m’y rendrai certainement, car je suis particulièrement motivé par les API Google Mobile, Google Map, et Google Aps.

Les inscriptions sont gratuites, et les premiers arrivés seront les premiers servis.

deux chérubins

Quel CMS ou framework utiliser pour faire un site de rencontres ?

Le 09 avril 2007 à 16h14 | 5 commentaires

Parcourir ses statistiques de fréquentation à la recherche des mots-clé amenant le visiteur sur votre site est toujours intéressant. Ces derniers fourmillent souvent de surprises amusantes, mais ils vous permettent surtout de vérifier l’adéquation des résultats de recherche avec le contenu publié. Parmi les phrases revenant régulièrement, cms ou framework pour faire un site de rencontre m’a donné envie d’offrir un semblant de réponse aux pauvre âmes en peine rentrées bredouilles après un passage sur ce blog. La question étant relativement large, et ne fixant notamment pas de limitations technologiques, il m’a semblé pertinent de définir le besoin fonctionnel d’un tel site afin de proposer l’option technique la plus performante.

Inscription des utilisateurs

Le système utilisé doit permettre l’inscription libre des utilisateurs. Idéalement, celle-ci doit permettre le choix de son profil minimal :

  • Sexe : masculin / féminin.
  • Type du compte : gratuit / payant.
  • Âge.
  • Cible recherchée (âge et sexe).

On pourra choisir de créer des règles spécifiques en fonction du type de compte choisi et du sexe de la personne, selon les modèles éprouvés du genre :

  • Gratuit pour les filles.
  • Compte gratuit aux fonctionnalités limitées.
  • Compte gratuit, complet, mais limité dans le temps.

Cela impliquera donc un système d’inscriptions particulièrement souple avec un workflow et la gestion du paiement en ligne, ce qui n’existe pas à ma connaissance sur les CMS du marché. L’utilisation d’un framework sera donc recommandée dans ce cas précis, à moins de souhaiter modifier en profondeur le gestionnaire d’inscription du CMS. Une étude de rentabilité des deux solutions serait intéressante.

Gestion des profils et droits

L’application devra proposer une solution de gestion de profils et de droits, que l’on opte pour des profils utilisateurs multiples ou pour un profil unifié. Un site de rencontre nécessite en effet des fonctionnalités avancées d’animation et de modération afin de pallier aux problèmes régulièrement rencontrés sur des services de genre : prostitution, harcèlement…

En ce qui concerne la fiche des utilisateurs, qui sera visible par l’ensemble des inscrits sur le site, il faudra à un moment ou un autre trancher entre deux solutions :

  • La fiche et le profil des utilisateurs ne sont qu’une seule et même chose, certaines informations étant publiques et d’autres privées.
  • La fiche de l’utilisateur est une composition à part entière, complètement indépendante du profil de l’utilisateur, minimaliste.

La seconde solution me semble la plus intéressante. Certains des éléments du profil pourront en effet servir de fiche temporaire le temps que la fiche des utilisateurs soit validée par les modérateurs de la plate-forme, et pourront de plus être affichés en “fiche minimale” pour les comptes gratuits ou les visiteurs, par essence pas encore enregistrés : vous voulez en savoir plus ? Eh bien payez maintenant.

En ce qui concerne la fiche elle-même, elle devra comporter un très grand nombre de champs, précis, et si possible remplis par des menus déroulants, afin de simplifier au maximum les contraintes techniques des fonctions de recherche.

Workflow de publication de la fiche

Je me suis interrogé un instant sur le bien fondé de l’utilisation d’un CMS pour un site de rencontres. Cette solution m’avait semblé un peu curieuse jusqu’au moment où je me suis rappelé que la majorité des sites du genre modéraient les fiches des utilisateurs à priori. On peut dès lors considérer cela comme un workflow de publication avec plusieurs profils :

  • Les visiteurs, qui peuvent visualiser une partie des fiches.
  • Les membres, qui peuvent visualiser l’ensemble des fiches.
  • Les rédacteurs (qui sont aussi les membres), qui rédigent leur propre fiche, mais sans les droits de publication.
  • Les responsables de publication, qui ont le droit de modération à priori sur les fiches des membres.

Se pose alors la complexité des profils utilisateurs des sites de rencontres, généralement très fournis, et peu compatible avec les fonctionnalités de publication de base d’un CMS. Les fonctionnalités de workflow rendent néanmoins ces derniers intéressant.

Recherche interne

Le site doit proposer deux types de recherche :

  • Une recherche simple, basée sur le profil.
  • Une recherche avancée, basée sur la fiche descriptive.

La recherche simple devra principalement porter sur trois critères :

  • Qui ? (âge, sexe…).
  • Quoi ? (relation durable ou éphémère)
  • Où ? (localisation géographique).

La recherche avancée devra refléter la fiche descriptive des utilisateurs autant que faire se peut, afin de sortir des fiches les plus pertinentes possibles. L’utilisation d’un thesaurus dans le moteur de recherche sera un plus particulièrement appréciable.

Messagerie interne

Qui dit rencontre dit contact, donc messagerie interne, au moins dans un premier temps.

Celle-ci devra être particulièrement simple d’utilisation, tout en proposant un minimum de fonctionnalités :

  • Classement par thread.
  • Insertion du texte dans les réponses.
  • Ajout de pièces jointes.

L’utilisation de cette messagerie ne devra pas être le but du site en soi, les utilisateurs étant évidemment invités à (commu)niquer de manière plus directe le plus rapidement possible.

Alors, CMS ou framework ?

Comme diraient les humoristes Chevalier et Laspales, c’est vous qui voyez. Certains CMS comme Joomla proposent par défaut un bon nombre des fonctionnalités citées dans ce billet, mais des développements lourds seront nécessaire. L’avantage d’un framework dans ce cas résiderait dans les fonctionnalités existantes pour conduire un développement spécifique, comme celles offertes par Ruby on Rails et ses nombreux greffons (comme Authorization pour les gestion des utilisateurs). Seule une estimation des temps de développements en fonction du périmètre fonctionnel recherché permettra de trancher.

roses are black

Rails, ou pourquoi faire compliqué quand on peut faire simple ?

Le 29 mars 2007 à 09h52 | 1 commentaire

Je découvre avec émerveillement l’attribut :include d’ActiveRecord::Base, qui efface de mon esprit des années de cauchemars SQL.

Jugez plutôt, quand une horreur pareille

self.find_by_sql([%{ SELECT categories.id, categories.name, categories.permalink, categories.position, COUNT(articles.id) AS article_counter FROM #{Category.table_name} categories LEFT OUTER JOIN #{Category.table_name_prefix}categorizations#{Category.table_name_suffix} articles_categories ON articles_categories.category_id = categories.id LEFT OUTER JOIN #{Article.table_name} articles ON (articles_categories.article_id = articles.id AND articles.published = ?) GROUP BY categories.id, categories.name, categories.position, categories.permalink ORDER BY position }, true]).each {|item| item.article_counter = item.article_counter.to_i }

devient tout simplement :

self.find( :all, :include => :articles, :conditions => [contents.published = ?, true], :order => position).each { |cat| cat.update_attributes(:article_counter => cat.articles.length) }

C’est beau, pour un peu j’en pleurerais.

Le long de la Seine

Billets précédents :