Parlons un peu de rack :)
Utilisé par rails et de nombreuses autres applications ruby afin de pouvoir être lancées par une large majorité de serveurs web (mongrel, unicorn, …) rack est particulièrement puissant.

La création d’une première application est on ne peut plus simple.
Dans un fichier test.rb :

class Test
    def call(env)
        [200, {"Content-Type" => "text/html"}, ["<html><head></head><body>Hello world!</body></html>"]]
    end
end

Exécutez votre application avec la ligne de commande suivante :

rackup test.rb

Simple mais efficace :)
Bien évidemment il faut ajouter des librairies à cela afin d’avoir quelque chose de plus puissant.

Maintenant découvronr autre chose : les middlewares.
En effet rack peut embriquer les méthodes call et ainsi vous permettre d’exécuter du code juste après l’initialisation de rack. Ou bien juste avant l’exécution du code de votre page.

Le développement d’un rack ressemble fortement à celui d’une tâche rack « normale ».
Voici un exemple de middleware vous permettant d’ajouter, juste avant la balise , le temps d’exécution de la page.

Dans un fichier response_timer.rb

class ResponseTimer
    attr_reader :message, :app

    def initialize(app, message = "Response Time")
        @message = message
        @app = app
    end

    def call(env)
        start = Time.now
        status, headers, response = app.call(env)
        stop = Time.now

        body = ''
        response.each { |part| body << part }
        index = body.rindex('</body>')
        if index
            body.insert(index, "<!-- #{message}: #{stop - start} -->\n")
            headers["Content-Length"] = body.length.to_s
            response = [body]
        end
        [status, headers, response]
    end
end

En revanche, nous allons maintenant avoir besoin d’un fichier de configuration pour rack, afin de pouvoir définir quel middleware nous incluons.
Nous devons pour cela créer un fichier nommé config.ru

require 'test'
require 'response_timer'

use ResponseTimer
run Test.new

Et nous n’avons plus qu’à exécuter le serveur

rackup

Vous noterez trois choses :

  • Nous incluons les deux librairies que nous venons de créer. Notre application et le middleware
  • Avec la méthode « use », nous ajoutons le middle à ceux natif à rack
  • Nous exécutons notre application

Comment ce middleware fonctionne-t-il ?
Comme votre application ! :)
Vous constaterez que les deux ont une méthode call. Et que les deux retournent un tableau contenant status, headers et réponse.

La seule différence entre les deux étant cette ligne :

status, headers, response = app.call(env)

Dans le middleware :
Grace au app.call, nous appellons le middleware suivant. Et ce, jusqu’à ce que nous arrivions au bout de la chaine : notre classe « test ».
Nous calculons donc assez aisément le temps d’exécution de notre application. Il nous suffit de faire la différence entre le timestamp avant l’exécution de cette méthode et après.
Et comme nous avons accès au contenu de la page retournée même après l’exécution de la méthode, nous pouvons y ajouter un commentaire juste avant la balise (si elle existe) contenant le temps d’exécution de la page.

Et dans rails ?

Comme dit plus haut, toute application rails tourne avec rack. Il est donc tout à fait possible d’ajouter notre middleware dans votre application.
Dans rails, les middlewares sont à ajouter dans le fichier config/environment.rb.

Voici comment j’ai inclu le rack ResponseTimer :

Rails::Initializer.run do |config|
    config.middleware.insert_before Rack::Lock, "ResponseTimer", "Load Time"
end

Le middleware Rack::Lock est le tout premier à être exécuté (vous pouvez avoir la liste de tous vos middlewares avec rake middleware).
Nous exécutons donc, juste avant celui-ci, le calcul du temps d’exécution de notre page :)

Trois méthodes vous permettent d’ajouter de nouveaux middlewares :

  • config.middleware.use – Le middleware sera ajouté à la fin de la pile
  • config.middleware.insert_before – Le middleware sera ajouté avant celui passé en premier paramètre
  • config.middleware.insert_after – Le middleware sera ajouté après celui passé en premier paramètre

Vous pouvez également supprimer un middleware précédemment ajouté : config.middleware.delete
Et remplacer un middleware par un autre : config.middleware.swap

Par ailleurs de nombreux middlewares sont disponibles en Open Source. Vous en trouverez une liste sur le wiki de rack.
Ainsi que dans le projet rack-contrib.

Rails 3 étant proche de sa première beta, j’en profite pour multiplier les articles à son propos !
Qui plus est il y a matière à écrire. Je ne m’en prive pas donc :)

Hier matin je suis tombé sur un article particulièrement intéressant The path to Rails3: Introduction, qui explique plutôt bien le maitre mot de cette nouvelle version du framework : découplage.
Ce découplage a pour but de faciliter l’utilisation de rails par blocs uniquement lorsque son utilisation en entier n’est pas forcément nécessaire.

Vous pouvez ainsi valider vos modèles sans forcément être dans une application rails, ni utiliser Active Record.
Avec arel, vous pouvez générer des requêtes SQL sans dépendre de rails (à terme du moins. Pour le moment, vous dépendez toujours d’ActiveRecord).

Avec bundler, vous gérez les dépendances de votre projet, qu’il utilise rails ou pas.
Regardez comment je fais pour jesus !
Je ferai une présentation de bundler au prochain apéro Ruby à Lyon. Venez donc y assister !

Je découvre donc cet article. Et le trouvant intéressant, je le partage avec Julien qui bosse dans le même bureau que moi.
Sa réaction a été « c’est vraiment en train de devenir inaccessible pour les débutants, rails. Trop compliqué » (je répète pas les choses mot pour mot, désolé).

Ce à quoi je réponds : NON !.
Pour un débutant découvrant le framework, celui-ci reste toujours aussi simple. Le screencast créer un blog avec rails en 15 minutes est parfaitement adaptable pour rails 3.

Et ce parce que de base, rien ne change !
L’idéologie de rails est toujours convention over configuration et les API ne changent pas fondamentalement (sauf peut-être pour ActionMailer. Mais ce n’est pas encore implémenté).

Ainsi votre contrôleur ressemblera toujours à ceci :

class PostController < ApplicationController
  def index
    @posts = Post.all
  end
end

Votre modèle ressemblera toujours à cela

class Post < ActiveRecord::Base
  validates_presence_of :title, :content
end

Et votre vue ressemblera toujours à cela :

<% @posts.each do |post|  %>
    <p>
        <%= post.title %>
        <%= post.content %>
    </p>
<% end %>

Vos routes quant à elles, au lieu de ressembler à cela :

ActionController::Routing::Routes.draw do |map|
  map.resource :post
end

Ressembleront à cela :

ApplicationRails3::Application.routes.draw do |map|
    resources :post
end

Et rien qu’avec ça, vous avez le début de votre blog vous permettant déjà de visualiser la liste de tous vos articles.

« seul » le fonctionnement en interne change (et ce radicalement). Les API utilisées dans votre application ne changent, pour la plupart pas.
Et si elles changent, la retro compatibilité devrait évidemment être assurée pendant pendant une version.

Moralité : non, rails ne se dirige pas vers une usine à gaz tel que J2EE. Oui, rails conserve sa simplicité. Et oui rails prends énormément en puissance.

Il y a une semaine de cela, Yehuda Katz a fait un article expliquant comment, dans Rails 3, obtenir les fonctionnalités de ActiveModel dans un modèle n’utilisant pas ActiveRecord.
Cela permet notamment de pouvoir utiliser les méthodes de sérialisation de Rails (export json et xml) et les validations.
C’est ce second point que je vais détailler aujourd’hui parce que c’est celui que je trouve le plus intéressant.

Notez que vous devez tout d’abord avoir le gem ActiveModel 3.0.pre installé sur votre machine.
Il est disponible sur gemcutter. Il vous suffit donc de faire

sudo gem install activemodel -v=3.0.pre -source=http://gemcutter.org

Prenons un fichier « test.rb », totalement intépendant de Rails. C’est juste du ruby pur et dur.
Mais parce qu’on y définit une classe, on souhaite pouvoir valider le format des données passées à celle-ci.

Voici donc le contenu de notre fichier test.rb

#
# Nous ne sommes pas dans Rails. Rubygems n'est donc pas inclus par défaut.
# ActiveModel ne l'est pas non plus. On doit donc les appeller.
#
require 'rubygems'
require 'active_model'

class Test
  #
  # On charge les validations de ActiveModel
  #
  include ActiveModel::Validations

  #
  # First Name et Last Name doivent absolument être présent
  #
  validates_presence_of :first_name, :last_name

  #
  # On définit les attributs First Name et Last Name
  # Et on les initialise dans le constructeur
  #
  attr_accessor :first_name, :last_name
  def initialize(first_name, last_name)
    @first_name, @last_name = first_name, last_name
  end
end

Vous noterez qu’il nous suffit d’inclure ActiveModel::Validations afin d’avoir accès à toutes ces méthodes de validations. Plutôt cool ! :)

Dès à présent, nous pouvons tester si nos validations passent !

test = Test.new('Jane', 'Doe')
p test.valid?

Inévitablement, nos validations passent puisque nous vérifions simplement la présence de First Name et de Last Name et que sans ceux-ci, nous ne pourrions même pas instancier la classe.

Mais … Comme nous avons accès à toutes les méthodes de validation d’ActiveModel, nous pouvons créer notre propre validateur !

class Test
  # ... Contenu de la classe définit précédemment dans l'article

  #
  # Et on ajoute une validation, empêchant quelqu'un de prendre le nom de famille "Doe"
  #
  validate do |t|
    t.errors[:last_name] << 'You must know your own name. Doe is only for anonymous guys' if t.last_name == 'Doe'
  end
end

Constatez le « validate » que nous avons ajouté et qui empêche quelqu’un de prendre le nom de famille « Doe ».

Maintenant, réexecutons notre code précédent. Notre méthode « valid? » nous retournera bien évidemment false.
Et si nous faisons un print de test.errors, nous constaterons que celui-ci est un Hash contenant chacune de nos erreurs.

#<OrderedHash {:last_name=>["You must know your own name. Doe is only for anonymous guys"]}>

N’oubliez pas bien évidemment de vérifier que la validation de vos données est correcte avant de sauvegarder celles-ci. Sans quoi l’utilité est particulièrement restreinte :)

Vous pouvez du coup, d’une manière similaire à ce que vous faites déjà avec vos modèles ActiveRecord, valider les données de tous vos objets Ruby en utilisant ActiveModel.
Par exemple si vous utilisez une solution NoSQL telle que CouchDB ou MongoDB.
Ou encore sur des modèles vous permettant de faire le lien avec une API distante (XML ou JSON par exemple).

Tous vos modèles utilisent ainsi la même API, celle d’ActiveModel. Ils sont tous homogènes et vous pouvez d’autant plus aisément manipuler ceux-ci.

Vous pouvez, en Ruby, comme avec tout langage évolué, générer et gérer des exceptions.

Exemple rapide :

begin
    raise "Only a test"
rescue
    # Le raise nous emmene ici
end

Ainsi, nous pouvons aisément gérer les erreurs générées par notre application et éviter de tout casser pour un simple enregistrement non trouvé dans la base de données.
Tous vos modèles et vos contrôleurs dans Rails pourront soulever des exceptionset elles seront gêrées par l’application.

Ainsi si, dans votre contrôleur, vous avez :

Post.find params[:id]

Et que l’uplet ayant pour clé primaire params[:id] n’existe pas, une exception ActiveRecord::RecordNotFound sera soulevée.
En développement vous verrez un beau message d’erreur et en production une belle erreur 500.
Mais nous ne voulons pas de cette erreur 500. Si l’enregistrement ne peut pas être trouvé, cela signifie que la page n’existe pas et alors, on désire une erreur 404.

En Ruby pur, nous ferions donc :

begin
    Post.find params[:id]
rescue ActiveRecord::RecordNotFound
    # On affiche l'erreur 404
end

Vade retro beurk !

Rendons nous plutôt dans application_controller et utilisons rescue_from (ou dans rails 3).
Nous allons donc avoir, dans ApplicationController :

rescue_from ActiveRecord::RecordNotFound, :with => :render_missing
def render_missing
    render :file => "#{RAILS_ROOT}/public/404.html", :status => 404
end

Rails s’occupe de faire l’appel à cette méthode lorsque l’exception est soulevée et nous avons bien notre erreur 404 générée :)

Bien évidemment, vous pouvez gérez n’importe quelle exception avec rescue_from et ainsi éviter les erreurs 500 pas jolies et qui anéantissent l’expérience utilisateur afin de les remplacer par des joli messages.

Attention cependant à ne pas partir dans des excès ! En voici typiquement un.
Ok pour gérer les exceptions dans le contrôleur lorsqu’elles ont un impact direct sur l’utilisateur (service indisponible, erreur 404, …).
Mais pas pour gérer toutes les erreurs comme ceci.
Ne vous amusez donc pas à utiliser rescue_from afin de gérer les enregistrements invalides. Travaillez intelligemment :)

Après n’avoir tenu quasiment aucune des résolutions que j’ai prises l’an passé, j’ai décidé de ne pas en prendre de trop cette année.

Pour celles de l’an dernier, je garde uniquement celle « me remettre à la natation ». Je vais maintenant avoir un après midi par semaine de RTT et je compte en profiter pour mettre ceci à exécution.

Pour la seconde résolution, je resterai vague. Comprendra qui pourra : Je m’achète des c*.

Et puis sinon, bonne année à vous aussi.


Arbre de noel sur la place de l’hôtel de ville de Poznan, Pologne

 
Fork me on GitHub