Je vois beaucoup de développeurs anglophones qui ont des blogs techniques. Mais cela ne me semble pas si répandu que cela chez les francophones.
Du coup j’ai rédigé une petite liste de 10 raisons (en fait 11) pour lesquelles, toi, développeur, tu devrais avoir un blog technique.

  1. Le partage des connaissance

    Ne soyez pas égoïste, pensez aux autres développeurs qui vont affronter des problèmes similaires aux vôtres !
    Partager ces problèmes (en les sortant de leur contexte au besoin) vous permet ainsi de faciliter le travail de ceux qui tenteront de faire la même chose que vous après.

  2. La création d’une base personnelle de connaissances

    Parce que penser aux autres c’est gentil. Mais qui pensera à vous ! ;)
    Avec un blog, vous vous créez une base de connaissances personnelles voir un aide mémoire.
    Il m’est par exemple arrivé plusieurs fois de me rendre sur mon article Installer le gem MySQL sous mac.

  3. La veille technologique

    Avec un blog que vous tentez réellement de mettre à jour régulièrement, vous allez tenter de vous tenir toujours au courant des dernières nouveautés afin de faire de nouveaux articles.
    Ainsi j’ai écrit quelques articles sur Rails 3 dernièrement. Cela m’a forcé à me pencher sur cette version bêta. Chose que je n’aurai probablement pas fait tout de suite si ce n’est pour rédiger ces articles.

  4. Le dépassement de soi

    Lorsque vous allez tenter de découvrir ces nouvelles technologies, vous allez devoir vous plonger dans la documentation de l’application (personne n’a encore réellement fait d’article puisque la chose est toute neuve) voir dans le code même de celle-ci.
    C’est une excellente manière de vous dépasser personnellement et au passage d’améliorer vos compétences en développement.

  5. La reconnaissance des compétences

    En discutant avec Sarah il y a quelques semaines, celle-ci me disait ne pas rédiger d’articles techniques car beaucoup de monde le fait déjà en PHP. En même temps elle ajoutait avoir eu quelques personnes lui ayant dit ne pas savoir comment elle développe.

    Lorsque vous cherchez un emploi en tant que développeur, votre futur employeur (et/ou vos futur collègues) va/vont vouloir savoir comment vous développez. S’il faudra repasser derrière vous après chaque commit ou si vous allez devenir le nouveau cerveau de l’équipe.
    Deux manières existent afin de montrer du code : participer à des projets open source ou écrire des articles sur un blog :)

    Attention, ceci est également à double tranchant. En publiant du code, vous courez également le risque que tout le monde se rende compte qu’en fait, votre code n’est pas aussi propre que vous voudriez le faire croire ;)

  6. Remercier les gens qui ont pris du temps pour vous apprendre

    Bah oui, on a tous débuté un jour. Moi c’était en seconde avec Jordan et un livre sur PHP (qui est une référence en matière de mauvaises pratiques).

    Et à ce moment la, vous avez bien du demander de l’aide sur divers forums ou à des amis qui avaient déjà de la bouteille. Quelle meilleure manière de les remercier que de partager ensuite ce que vous avez appris ?

  7. La visibilité/e-réputation

    Ouais, c’est quand même l’une des raisons premières de créer un blog hein. Vous allez gagner en visibilité, en renommée (à condition de faire des articles de qualité), devenir un expert dans votre secteur (ou au moins donner l’impression que vous en êtes un :mrgreen: ).

    Les conséquences se font rapidement ressentir : vous recevrez beaucoup plus d’offres d’emploi (et serez donc mieux payé), de demandes de contact (j’ai beaucoup d’étudiants qui me contactent pour des « interviews » sur le métier de développeur web).

  8. Se la péter

    :mrgreen:

    Il ne se passe pas une semaine sans qu’un collègue, en cherchant quelque chose à propos de ruby, ne tombe sur mon blog. Et quelle meilleure manière de se « la péter » que de ressortir sur tous les résultats Google relatifs à un domaine technologique précis.

  9. Amour du prochain

    C’est pas moi qui ai dit « Aimez vous les uns les autres ». Mais ca s’applique bien ici. Et même si c’est un petit peu de la répétition avec la seconde raison, ça fonctionne tout de même.
    On ne vis pas dans un monde de bisounours. Mais en travaillant pour, on peut arriver à quelque chose de similaire ;)

  10. Se payer des bières avec les adsense

    On peut toujours rêver ! Mais rien ne vous empêche d’afficher quelques publicités sur votre blog.
    Pour info avec les revenus générés par ce blog, je peux me payer environ 3 bières par mois.

  11. Le fun

    Et oui, ça ne fait pas 10 mais 11. Et pourtant cette dernière raison est la plus importante.
    Il y a quelques jours en réfléchissant à cet article, j’ai sondé sur twitter en demandant « qu’est-ce qui vous motive à maintenir un blog technique ? ».
    S’en est suivi une discussion avec Raphael qui m’expliquait rapidement pourquoi il a fermé son blog.

    C’est la raison primordiale pour maintenir un blog technique : il faut que cela vous amuse. Si ce n’est pas le cas, autant ne rien faire.
    Vous n’arriveriez pas à vous motiver, vos articles perdraient fortement en qualité et votre blog n’aurait pas réellement d’intérêt.


Pour finir je ne donnerai que deux conseils à ceux que cela aurait (par le plus grand des hasards) motivé à créer leur blog :

  • Soyez constant.
    Évitez de publier trois articles le premier mois, puis plus rien pendant 6 mois.
    Je me force personnellement à faire un article par semaine (voir plus. La preuve, cet article est le second e la semaine). Mais cela peut être plus ou moins.
  • Relisez vous. Lors du Wordcamp Parisien de Février 2009, on m’a demandé « si j’avais un seul conseil pour un futur blogueur à donner, quel serait-il ? »
    Ma réponse fut : rédige tes articles le lundi, publie les le mardi.
    Et relis ton article avant de le publier.

Et vous, qu’est-ce qui vous pousse à maintenir (ou pas) un blog technique ?
Certains m’ont déjà répondu (merci Xavier et Bruno) et cela a fait les 6e et 9e raisons.

Il n’est pas rare d’avoir besoin de créer des tâches asynchrones dans vos applications. Si ces tâches sont à déclencher suite à l’action d’un utilisateur (édition d’une page, …), vous pouvez utiliser delayed job.
Mais si cette tâche doit être exécutée à interval régulier dans le temps, utiliser delayed job n’est pas faisable. Et il vous faudra créer un cron.

Pour cela je vais vous parler de Whenever.
L’utilisation est simpliste. Vous définissez tous vos crons dans config/schedule.rb. Et whenever se charge de générer automatiquement votre crontab.

Voici par exemple le document whenever pour RefStats.

every 1.minute do
  rake "crawler:work > #{RAILS_ROOT}/../shared/log/crawler.log"
end

every 1.day, :at => '2 am' do
  rake "maintenance:daily > #{RAILS_ROOT}/../shared/log/maintenance.log"
end

Toutes les minutes, le crawler récupérant les positions est lancé. Il s’agit d’une tâche rake.
Tous les jours à 2h du matin, la tâche « maintenance:daily » est lancée.

Suite à cela, depuis la base de votre projet, entrez en ligne de commande :

whenever

Vous verrez alors la crontab générée par whenever. Uniquement affichée, par mise à jour.
Si vous désirez mettre celle-ci à jour, c’est cependant simple.

whenever –update-crontab ApplicationName

L’option ApplicationName doit être différente pour chacune de vos applications. Ainsi whenever ne modifie que la crontab relative à cette application.

Du coup dans votre processus de déploiement Capistrano, vous ajoutez une tâche pour whenever :

after "deploy:symlink", "deploy:update_crontab"
namespace :deploy do
    desc "Update the crontab file"
    task :update_crontab, :roles => :db do
        run "cd #{release_path} && whenever --update-crontab #{application}"
    end
end

Et votre crontab sera remise automatiquement à jour à chaque fois que vous déploierez votre application. Plus à se soucier de cela manuellement :)

Dans toute application Rails, vous avez une tâche « rake » assez sympa : stats. Celle-ci vous retourne le nombre de lignes de code dans votre projet.
En séparant les contrôleurs, les modèles, les helpers et les librairies. Mais également en séparant le code applicatif du code de test.
Et en vous donnant un ratio code applicatif / code de test/

Voici un exemple de rake stats (avec des chiffres complètement fictifs).

+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers          |   419 |   289 |       7 |      43 |   6 |     4 |
| Helpers              |     9 |     9 |       0 |       1 |   0 |     7 |
| Models               |    34 |    29 |       4 |       2 |   0 |    12 |
| Libraries            |   359 |   212 |       2 |      36 |  18 |     3 |
| Model specs          |    78 |    60 |       0 |       0 |   0 |     0 |
| Controller specs     |   203 |   166 |       0 |       2 |   0 |    81 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total                |  1514 |  1028 |      13 |      85 |   6 |    10 |
+----------------------+-------+-------+---------+---------+-----+-------+
  Code LOC: 539     Test LOC: 489     Code to Test Ratio: 1:0.9

Ici, en utilisant RSpec, nous avons les statistiques de nos specs car l’outil modifie cette fonctionnalité afin d’avoir les bons chiffres.
Cependant si en plus de RSpec, et comme moi, vous utilisez Cucumber, vous n’aurez pas ces chiffres. Normal ! RSpec redéfinit la tâche stats. Mais pas cucumber.

Modifions donc cette tâche afin d’avoir nos chiffres. Ouvrez le fichier suivant :

lib/tasks/rspec.rake

La aux alentours de la ligne 110, vous allez trouver la définition des répertoires de code à include dans la tâche stats.

task :statsetup do
    require 'code_statistics'
    ::STATS_DIRECTORIES << %w(Model\ specs spec/models) if File.exist?('spec/models')
    ::STATS_DIRECTORIES << %w(View\ specs spec/views) if File.exist?('spec/views')
    ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers) if File.exist?('spec/controllers')
    ::STATS_DIRECTORIES << %w(Helper\ specs spec/helpers) if File.exist?('spec/helpers')
    ::STATS_DIRECTORIES << %w(Library\ specs spec/lib) if File.exist?('spec/lib')
    ::STATS_DIRECTORIES << %w(Routing\ specs spec/routing) if File.exist?('spec/routing')
    ::STATS_DIRECTORIES << %w(Integration\ specs spec/integration) if File.exist?('spec/integration')
    ::CodeStatistics::TEST_TYPES << "Model specs" if File.exist?('spec/models')
    ::CodeStatistics::TEST_TYPES << "View specs" if File.exist?('spec/views')
    ::CodeStatistics::TEST_TYPES << "Controller specs" if File.exist?('spec/controllers')
    ::CodeStatistics::TEST_TYPES << "Helper specs" if File.exist?('spec/helpers')
    ::CodeStatistics::TEST_TYPES << "Library specs" if File.exist?('spec/lib')
    ::CodeStatistics::TEST_TYPES << "Routing specs" if File.exist?('spec/routing')
    ::CodeStatistics::TEST_TYPES << "Integration specs" if File.exist?('spec/integration')
  end

Il nous faut ajouter nos features Cucumber, et ce deux fois : une pour les statistiques « générales ». Et une seconde afin de comptabiliser ces données comme des données de test. La tâche statsetup sera donc la suivante :

task :statsetup do
    require 'code_statistics'
    ::STATS_DIRECTORIES << %w(Model\ specs spec/models) if File.exist?('spec/models')
    ::STATS_DIRECTORIES << %w(View\ specs spec/views) if File.exist?('spec/views')
    ::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers) if File.exist?('spec/controllers')
    ::STATS_DIRECTORIES << %w(Helper\ specs spec/helpers) if File.exist?('spec/helpers')
    ::STATS_DIRECTORIES << %w(Library\ specs spec/lib) if File.exist?('spec/lib')
    ::STATS_DIRECTORIES << %w(Routing\ specs spec/routing) if File.exist?('spec/routing')
    ::STATS_DIRECTORIES << %w(Integration\ specs spec/integration) if File.exist?('spec/integration')
    ::CodeStatistics::TEST_TYPES << "Model specs" if File.exist?('spec/models')
    ::CodeStatistics::TEST_TYPES << "View specs" if File.exist?('spec/views')
    ::CodeStatistics::TEST_TYPES << "Controller specs" if File.exist?('spec/controllers')
    ::CodeStatistics::TEST_TYPES << "Helper specs" if File.exist?('spec/helpers')
    ::CodeStatistics::TEST_TYPES << "Library specs" if File.exist?('spec/lib')
    ::CodeStatistics::TEST_TYPES << "Routing specs" if File.exist?('spec/routing')
    ::CodeStatistics::TEST_TYPES << "Integration specs" if File.exist?('spec/integration')

    #
    # Adding Cucumber features to the stats
    #
    ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features')
    ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features')
  end

Et du coup nous avons maintenant nos données Cucumber ajoutées à nos stats.
Avec les même données fictives que plus haut, nous aurons donc un rake stats équivalent à celui-ci :

+----------------------+-------+-------+---------+---------+-----+-------+
| Name                 | Lines |   LOC | Classes | Methods | M/C | LOC/M |
+----------------------+-------+-------+---------+---------+-----+-------+
| Controllers          |   419 |   289 |       7 |      43 |   6 |     4 |
| Helpers              |     9 |     9 |       0 |       1 |   0 |     7 |
| Models               |    34 |    29 |       4 |       2 |   0 |    12 |
| Libraries            |   359 |   212 |       2 |      36 |  18 |     3 |
| Model specs          |    78 |    60 |       0 |       0 |   0 |     0 |
| Cucumber features    |   412 |   263 |       0 |       1 |   0 |   261 |
| Controller specs     |   203 |   166 |       0 |       2 |   0 |    81 |
+----------------------+-------+-------+---------+---------+-----+-------+
| Total                |  1514 |  1028 |      13 |      85 |   6 |    10 |
+----------------------+-------+-------+---------+---------+-----+-------+
  Code LOC: 539     Test LOC: 489     Code to Test Ratio: 1:0.9

Nos statistiques de projet sont plus réalistes et le ratio entre le code applicatif et le code de test est plus révélateur de ce que vous avez réellement dans votre application.

Avec rails comme avec tout framework, vous allez rapidement avoir besoin de gérer l’identification des utilisateurs et leurs autorisations.
Pour leur identification, il existe divers plugins. Notamment Restful Authentication, AuthLogic et (mon préféré) Clearance.
Pour la gestion des autorisations, je vais vous parler de ACL9.

L’idée est simple. Après avoir identifié votre utilisateur, vous avez accès à un objet current_user, qui contient l’objet relatif à l’utilisateur actif (généralement une instance du model User. Mais cela peut être n’importe quoi).
ACL9 va ajouter deux tables :
- Role, qui contiendra tous les rôles utilisateurs existants. Un peu comme des groupes. Chaque rôle pouvant être associé à une instance d’un objet.
Vous pourrez ainsi avoir un rôle « admin », global, qui permettra de définir les accès de l’administrateur.
Et un rôle « éditeur », relatif uniquement à l’objet ayant pour id « 1″ du modèle « News ».

- UserRole, qui fera la relation entre un rôle et un utilisateur (has_and_belongs_to_many).

Voici les migrations nécessaires pour ces deux tables :

create_table :roles, :force => true do |t|
    t.column   :name,                            :string,                    :limit => 40
    t.column      :authorizable_type",      :string,                    :limit => 40
    t.column      :authorizable_id,           :integer
    t.timestamps
end
create_table :roles_users", :id => false, :force => true do |t|
    t.column :user_id,                          :integer
    t.integer  :role_id,                           :integer
    t.timestamps
end

Vous devez bien évidemment créer un modèle « Role », qui représentera cette table. Il est inutile de créer un modèle RolesUser. Cela sera géré automatiquement par Rails.

class Role < ActiveRecord::Base
    acts_as_authorization_role
end

Vous noterez l’ajout de « acts_as_authorization_role », qui va implémenter les méthodes de rôles relatives à ACL9.
Dans notre table user, nous allons également devoir ajouter un appel à une méthode spécifique de ACL9 afin d’ajouter les méthodes relatives à l’utilisateur.

class User < ActiveRecord::Base
    acts_as_authorization_subject
end

Et dans tous les modèles ou nous désirons avoir une gestion des droits d’accès, nous allons ajouter :

class Foo < ActiveRecord::Base
    acts_as_authorization_object
end

ACL9 vous a maintenant ajouté diveres méthodes dans vos objets :
Dans votre modèle utilisateur :

  • has_role?(role, object = nil) – Retournera « true » si l’utilisateur a le rôle demandé en global ou sur l’objet précisé.
  • has_role!(role, object = nil) – Donnera le rôle nommé à l’utilisateur
  • has_no_role!(role, object = nil) – Supprimera le rôle nommé pour l’utilisateur
  • has_roles_for?(object) – Retourne true si l’utilisateur a un quelconque rôle sur l’objet
  • has_role_for?(object) – Alias de has_roles_for?
  • roles_for(object) – Retourne tous les rôles de l’utilisateur pour cet objet
  • has_no_roles_for!(object) – Supprime tous les rôles de l’utilisateur pour l’objet
  • has_no_roles! – Supprime tous les rôles de l’utilisateur

Et dans chacun de vos modèles objets (Foo plus haut) :

  • accepts_role?(role, subject) – Retourne true si subject a accès au rôle courant
  • accepts_role!(role, subject) – Donne accès à subject au rôle courant
  • accepts_no_role!(role, subject) – Supprime l’accès utilisateur au rôle
  • accepts_roles_by?(subject) – Retourne true si l’utilisateur a un rôle quelconque sur l’objet
  • accepts_role_by?(subject) – Alias de accepts_roles_by?
  • accepted_roles_by(subject) – Retourne tous les utilisateurs ayant un rôle pour cet objet

Maintenant que nous avons implémenté nos autorisations et que nous avons vu les diverses méthodes pour les utiliser, limitons les accès à nos actions !

class MyOwnController < ApplicationController
    access_control do
        # Autorise tous les utilisateurs ayant le rôle "superadmin" à accéder à l'objet
        allow :superadmin

        # Autorise tous les utilisateurs ayant le rôle "creator" sur @news
        allow :creator, :at => :news

        # Autorise les utilisateurs anonymes et ceux qui sont enregistrés sur la page d'accueil
        # Et refuse les utilisateurs ayant le rôle "banned"
        action :index do
            allow anonymous, logged_in
            deny :banned
        end

        # Autorise les utilisateurs ayant le rôle "manager" sur l'objet @news. Sauf pour l'action "destroy"
        allow :manager, :at => :news, :except => [:destroy]
    end

    def index
        @news = News.find_by_id params[:id]
        # Le code relatif à l'action "index"
    end
end

Du coup on peut maintenant gérer de manière particulièrement avancée les rôles dans notre application.

Le service de raccourcissement d’url tr.im vient de rendre public le code de son application.
Je ne critique pas l’idée qui est particulièrement bonne. C’est cool :)

Mais pas dans la manière dont la chose est développée et qui serait presque un cas d’école.
Si on se rends à la base du projet, on vois trois applications Rails :

  • trim
  • trim_api
  • trim_redirect

Et en comparant trim et trim_api, on se rends compte que les deux applications sont identiques, à l’exception près que l’une retourne de l’HTML et l’autre du XML.
Urk donc. Une répétition de code particulièrement flagrante et strictement inutile.
Si, avec Rails, vous désirez retourner deux pages différentes pour le même code, une en HTML et l’autre en XML (ou tout autre format), il suffit d’utiliser respond_to.
Nous avons donc déjà une application que l’on peut fusionner avec la principale.

Quant à la troisième, qui gère les redirections, elle est nettement fusionnable à l’application principale. Il s’agit simplement d’une route par défaut.
Un petit :

map.connect ':redirect_id', :controller => 'redirect', :action => 'index'

Avec le code déjà présent dans le contrôleur redirect pour que cela fonctionne très bien.
Cette troisième application est donc également inutile. Nous avons trois applications différentes en une, et deux d’entre elles sont inutiles. Pas cool.

Enfin regardons trois dossiers similaires : les tests.
Le premier (qui est censé être le plus important vu que c’est le site) n’a strictement aucun test.
L’API a quelques tests. Je n’ai pas passé de rcov dessus cependant. Et vu leur nombre, je doute que l’on ait un pourcentage de code testé convenable.
Quand aux redirections, c’est pas testé non plus.

Alors oui, mettre ses applications en open source c’est cool.
Mais le minimum est d’éviter ce genre d’erreur de débutant (je parle surtout de la première. Le manque de tests, vu le peu de développeurs qui en font encore aujourd’hui, c’est pas spécialement étonnant) est un strict minimum.
D’ailleurs j’ai également ouvert une issue sur le projet.
Et vous vous en pensez quoi ?

Certaines personnes, notamment Clément (et d’autres) m’ont demandé il y a quelques semaines de cela de rédiger un article sur les commandes GIT de base pour une utilisation console.
Déjà, sachez qu’il existe de multiples interfaces graphiques pour Windows et Mac.
Il en existe également pour Linux. Mais je n’ai rien trouvé qui ne convienne à mes exigences. Donc je fais tout en ligne de commande et j’utilise gitk pour visualiser les commits faits et en cours.
Je n’aborde ici que les commandes de base, pour le développement de tous les jours. Pas de notion de branches ou de tags donc.

A chacune des commandes ci-dessous, il faut précéder l’exécutable « git ». Nous aurons donc : git <nom de commande>

  • add

    Permet d’ajouter des fichiers au prochain commit. Cette commande prends comme paramètre le nom du fichier ou du répertoire à ajouter.
    Exemples :

    git add app/models/mon_model.rb

    Ajoutera le fichier app/models/mon_model.rb à la liste des fichiers à committer.

    git add app/models

    Ajoutera tous les fichiers présents dans le dossier app/models dans la liste des fichiers à committer

    git add .

    Ajoutera tous les fichiers du projet à committer.
    Evidemment seuls les nouveaux fichiers ou ceux modifiés seront ajoutés. C’est le principe même d’un SCM ;)

  • rm

    Vous l’aurez peut-être deviné, cette commande est l’inverse de add. Elle vous permet de supprimer un fichier du repository.
    En effet un fichier supprimé physiquement n’est pas supprimé dans le repository. Il faut donc le faire via ce rm.
    Note : si vous supprimer un fichier existant avec cette méthode, il sera supprimé physiquement.

    Exemples :

    git rm app/models/mon_model.rb

    Supprimera le fichier app/models/mon_model.rb

    git rm -r app/models

    Supprimera tous les modèles du projet.
    Notez le -r qui force la récursivité car vous supprimez un répertoire.

  • commit

    Après avoir ajouté et supprimé tout plein de fichiers à notre prochain commit, il faut faire celui-ci !
    La commande commit permet donc de valider les changements faits. Tant que vous n’avez pas committé, vous pouvez revenir en arrière (même après en fait. Mais on verra pas cela dans cet article).

    Exemple :

    git commit -m "Un super message de description"
  • reset

    Comme je disais, tant que vous n’avez pas committé, vous pouvez annuler des changements particulièrement simplement avec git reset.
    Cette commande réinitialise tous les fichiers dans votre repository à leur état lors du dernier commit. Vous annulez donc tous les changements non committés et revenez sur une base propre.

    Exemple :

    git reset --hard HEAD
  • push

    Après avoir committé vos changements, il vous faut probablement pousser ceux-ci sur le repository distant afin de pouvoir partager vos modifications avec les autres développeurs du projet.
    Ou bien tout simplement de pouvoir récupérer ces changements sur une autre machine (celle de test ou de production par exemple).

    Exemples :

    git push

    Poussera la branche courante sur le repository distant nommé « origin »

    git push backup

    Poussera la branche courante sur le repository distant nommé « backup »

    git push backup V2

    Poussera la branche nommée « V2″ sur le repository distant nommé « backup ».

  • pull

    Pousser c’est bien. Mais il faut récupérer les données qui ont été poussées par vous sur une autre machine ou par un autre développeur.
    Cette commande fonctionne avec exactement les même arguments que la précédente mais pour récupérer les données de votre repository.

Voila. Avec tout cela, vous pouvez :

  • Ajouter, supprimer des fichiers et les committer
  • Annuler une modification non committée
  • Envoyer et récupérer les données sur un repository distant

Ce qui est, au final, les commandes GIT de tous les jours (dans l’utilisation que j’en fais du moins).
Peut-être ferais-je d’autres articles plus avancés au niveau de la gestion des branches ou de l’annulation d’un commit. Mais cela viendra dans un prochain article.

Si vous développez vos applications de manière un chouilla propre, vous devez avoir des environnements de développement et de production bien séparés.
Par exemple, l’API RefStats est disponible en production. Mais vous pouvez également visualiser l’interface qui me sers de test en développement.

Pour diverses raisons pratiques, je ne désire pas mettre d’identification sur cette version de développement.
Il n’y a de toute façon pas énormément de risques. La base de données y est remise à zéro particulièrement régulièrement et il est impossible de s’inscrire vu que c’est par le forum que cela se passe.
Par contre étant donné que les pages sont publiques, je ne tiens pas spécialement à ce que les moteurs de recherche ne les indexent.

Pour cela, je désire insérer un robots.txt. Mais uniquement en développement.
Inutile de s’embêter à faire un contrôleur, une action, une vue etc. Je vais utiliser une super fonctionnalité de Rails 2.3 : un metal.

Générons le en ligne de commande dans notre projet :

script/generate metal robots

Ceci nous crée un répertoire app/metal et un fichier robots.rb dedans.
Nous désirons que notre metal surveille l’url « /robots.txt » et que, si on est en développement, il nous retourne son contenu.

Et puisque c’est particulièrement simple, je vous le donne directement :)

require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class Robots
    def self.call(env)
        if env["PATH_INFO"] == '/robots.txt'
            [200, {"Content-Type" => "text"}, [ENV['RAILS_ENV'] == 'production' ? '' : "User-agent: *\r\nDisallow: /"]]
        else
            [404, {"Content-Type" => "text"}, [""]]
        end
    end
end

Ainsi dev.api.refstats.net/robots.txt interdit l’indexation.
Tandis que en production, l’indexation est autorisée puisque le robots.txt sera vide (à l’heure ou j’écris ces lignes, l’application n’est pas encore déployée car j’ai pas mal de commit en suspens. Notamment tout ce qui est de la gestion des comptes pro. Donc vous pouvez pas le voir).

Plutôt simple et efficace non ? :)

Lorsque vous définissez, dans une application rails, des url spécifiques avec map.resources, celles-ci finissent de la forme suivante :

/post/1
Ou 1 est l’identifiant de l’uplet dans la base et blog est le nom de la ressource. C’est cool parce que ça permet d’avoir des url assez courtes simplement.
Mais d’un point de vue utilisateur, y’a mieux. Avoir une url du genre de
/post/titre-du-billet
Serait un peu mieux non ? C’est particulièrement simple :)

Map.resources utilise la méthode to_param des modèles. Méthode qui, par défaut, retourne la valeur de l’identifiant.
Si vous la modifiez en placant :

def to_param
    title
end

Ce n’est plus l’identifiant de votre uplet qui sera retourné mais la valeur du champ titre.
A vous donc ensuite d’avoir un titre correct.

Et lorsque vous appellerez la méthode vous permettant d’avoir l’url vers votre billet :

post_url @post

Ce n’est plus /post/1 qui sera retourné mais /post/titre-de-votre-billet.

Bien évidemment la valeur de title doit être correctement formatée pour rendre une url (suppression des accents et espaces etc). Rails ne fait pas cela automatiquement.
Mais ce genre de méthode ne fait pas partie du sujet de cet article. Cela sera pour un autre :)

Regardons un peu les formulaires Django. Et plus particulièrement les ChoiceField.
Avec ceux-ci, nous plaçons une liste déroulante dans notre formulaire.

champ = forms.ChoiceField(choices=((1, 'valeur'), (2, 'seconde_valeur')))

Nous créons ici un ChoiceField dans notre formulaire en y placant également deux valeurs avec les identifiants 1 et 2.

C’est sympa. Mais ça serait mieux de pouvoir récupérer les valeurs de notre base. Gogo alors !

champ = forms.ChoiceField(choices=[[r.id,r.name] for r in Model.objects.all()])

Pour définir les valeurs, nous récupérons toutes les données disponibles avec le modèle « Model » et plaçons les champs id et name. Vous avez donc un ChoiceField dynamique.

Malheureusement (ou heureusement, cela dépends du point de vue), Django place le contenu des formulaires en cache. La requête SQL de récupération de vos modèles ne sera donc executée que si ceux-ci sont modifiés.
Dans un usage basique, cela suffit amplement. Si c’est le cas, je vous invite fortement à conserver cette méthode car ce que j’explique plus bas n’est absolument pas DRY.

Dans mon cas par exemple, j’ai une condition sur mon modèle. Le site est multilingue et je cherche à ne récupérer que les uplets relatifs à la langue en cours.
Avec la mise en cache donc, je n’ai pas les bons éléments car Django ne fait pas particulièrement attention au fait que j’ai changé la requête entre deux (notemment parce que la condition n’est pas dans la requête mais au niveau supérieur, dans le manager).

Je ne peux donc ajouter les éléments directement depuis le formulaire.
Je définis mon champ dans mon formulaire :

champ = model.ChoiceField()

Et dans mon contrôleur (oui c’est pas DRY je l’ai déjà dit) :

if request.method == 'POST':
    form = MyForm(request.POST)
    form.fields['champ'].choices = [[r.id,r.name] for r in Model.objects.all()]
else:
    form = MyForm()
    form.fields['champ'].choices = [[r.id,r.name] for r in Model.objects.all()]

Mon formulaire est correctement défini et avec les bonnes valeurs :)

Et vous, avez-vous déjà eu un problème similaire de cache sur des ChoiceFields de Django ? Avez-vous résolu le problème de manière similaire ou avez-vous réussi à faire quelque chose de plus propre ?

On ne le répètera jamais assez et je ne vous l’apprends théoriquement pas : cryptez les mots de passe de vos utilisateurs en base.
Supposons qu’un hacker découvre une faille de sécurité quelconque sur votre site et que grâce à celle-ci, il puisse obtenir un accès à l’intégralité de votre base (c’est déjà de la grosse faille. Mais mieux vaut être trop prudent).
Vos mots de passe ne sont pas cryptés et tous vos utilisateurs nagent dans leurs excréments (oui oui j’aurais pu dire ça autrement).

Bon du coup on hash (voir md5 ou sha1). Et on crypte pas !
La différence est flagrante : une chaine de caractères hashée n’est pas déhashage.
Une chaine cryptée est décryptable ! Autant dire que le cryptage est, dans notre cas, inutile.

Vous faites déjà perdre au CPU du hacker une bonne semaine de calculs (voir moins si vos utilisateurs ne sont pas des geeks et que leurs mots de passe ne sont donc pas sécurisés).
C’est bien. Mais pas suffisant. Il faut que trouver le mot de passe prenne trop de temps et ne soit pas intéressant à cracker.

Pour cela, il faut les « saler » (urgh la francisation à 2$).
Le principe est simple.

Au lieu de faire :

Digest::MD5.hexdigest 'my secure password'

Vous ajoutez divers éléments dans le hash. J’ai tendance à mettre les éléments suivants :

  • Une chaine aléatoire unique pour chaque utilisateur et stockée dans la base de données.
  • Une chaine aléatoire unique pour tout le site et stockée dans mon fichier de configuration

Le hash est donc généré de la manière suivante :

Digest::MD5.hexdigest user_random_hash + 'my secure password' + site_random_hash

Ainsi pour décoder le hash, il faut non seulement avoir accès à la base de données. Mais également au code source de l’application afin de connaitre le hash du site.
Par ailleurs il est impossible de tester un hash sur tous les utilisateurs. Il faut tester chaque hash indépendament du visiteur. Cela démultiplie donc le temps de calcul de ceux-ci.

Maintenant notre gentil hacker devra donc, pour trouver les mots de passe de vos utilisateurs, avoir accès à :

  • Tous les champs de votre base de données. Et pas uniquement le champ de mot de passe.
  • Le code source de votre application.

Ce qui diminue fortement les risques. Ne les élimine pas évidemment. Mais le risque zéro en sécurité n’existe, de toute façon, pas.
Et vous, comment cryptez-vous les mots de passe de vos utilisateurs ?

 
Fork me on GitHub