Ruby

Métaprogrammation et réflectivité Ruby

ruby est particulièrement appréciable pour sa dynamicité C'est ce que nous allons voir dans cet article avec une présentation de la métaprogrammation et de la réflexivité dans ce langage.

Qu'est-ce que la métaprogrammation et la réflexivité ?

Le livre The Ruby Programming Language chez O'Reilly dit :

Loosely defined, metaprogramming is writing programs (or frameworks) that help you write programs.
To put it another way, metaprogramming is a set of ways for extending ruby's syntax in ways that make programming easier.

Et en Français :

En définissant rapidement, la métaprogrammation consiste à créer des programmes (ou des frameworks) qui aident à écrire des programmes. Pour dire autrement, la métaprogrammation est une liste de méthodes permettant d'étendre la syntaxe de ruby afin de faciliter le développement.

La réflexivité consiste examiner l'état et la structure d'un objet. Ainsi, un programme pourra obtenir la liste de toutes les méthodes définies dans un objet

En gros : nous allons voir, dans cet article, comment un objet peut analyser son état ... Et altérer celui-ci en créant ou remplaçant des méthodes.

Réflectivité

Supposons la classe suivante :

class Testing < Hash

    def basic_method
        "Hello World"
    end
end

Plusieurs méthodes de réflexivité nous sont alors disponibles pour celle-ci.

t = Testing.new
t.class        #=> Testing
t.superclass #=> Hash
t.instance_of? Testing    #=> true
t.is_a? Hash      #=> true
t.respond_to? :basic_method    #=> true

Quelles sont ces diverses méthodes que nous exécutons ici ?

  • class - Retourne la classe de l'objet. Ici, Testing.
  • superclass - Retourne la classe parente de l'objet. Ici, Hash.
  • instance_of? - Détermine si l'objet est de la classe passée en paramètre.
  • is_a?/kind_of? - Détermine si l'objet est de la classe passée en paramètre ou d'une classe qui en hérite. Si il s'agit d'un module, elle indique si la classe inclus celui-ci.
  • respond_to? - Indique si l'objet définit la méthode (privée ou protégée) dont le nom est passé en paramètre. Il est possible de passer un second paramètre true si vous souhaitez également vérifier les méthodes privées.

Ajouté à celles-ci sont disponibles divers raccourcis permettant de tester de manière similaire divers objets.
Supposons les modules et la classe suivante :

module A
end

module B
    include A
end

class C
    include B
end

Nous avons alors accès aux méthodes suivantes :

C < B  #=> true
B < A  #=> true
C < A #=> true

Avec l'opérateur <, nous vérifions si une classe hérite d'une autre. Ou bien qu'un module est inclus.
Le test est bien évidemment fait pour toutes les classes parentes.

Bien évidemment, nous pouvons également récupérer une liste de toutes les classes parentes à la notre.
Si nous reprenons notre classe Testing plus haut, nous aurons donc :

Testing.ancestors #=> [Hash]

Qui nous retourne un tableau de toutes les classes dont dépends la classe courante.

L'équivalent pour les modules existe également, nous premettant d'obtenir la liste de tous les modules inclus dans la classe ou le module fourni.

A.included_modules  #=> []
B.includes_modules  #=> [A]
C.includes_modules #=> [B, A, Kernel]

Enfin une fonction booléen existe également pour savoir si la classe ou le module inclus la classe ou le module fournis.

C.includes?(A) #=> true

Avec ces fonctions de réflexivité, vous pouvez ainsi analyser vos classes lors de l'exécution de votre programme.
Ceci est particulièrement pratique lors de l'écriture de tests mais peut également s'avérer très utile lorsque vous créez dynamiquement de nouvelles méthodes.

Evaluation de code

Chaines de caractères et blocs

L'une des fonctionnalités les plus puissantes, mais aussi les plus dangereuses de Ruby est la méthode eval.
Celle-ci vous permet d'évaluer une chaine de caractères comme s'il s'agissant de code ruby.

x = 1
eval "x + 1" #=> 2

Eval est particulièrement puissant. Mais à moins que vous ne cherchiez à écrire un shell ruby (tel que irb), vous ne l'utiliserez jamais.

Plus que "vous ne l'utiliserez jamais", on devrait même dire "vous ne devriez jamais l'utiliser". C'est la meilleure manière d'obtenir des failles de sécurité gigantesques. Qui plus est, il est toujours possible de faire sans.

instance_eval, class_eval et module_eval

Instance_eval et class_eval sont, quant à eux, beaucoup plus utilisés. Dans rails par exemple, supposons que vous ayez, dans vos routes :

resources   :users

Cette route de ressources, créera plusieurs méthodes accessibles dans les contrôleurs et dans les vues :

users_path
users_url

user_path(user)
user_url(user)

edit_user_path(user)
edit_user_url(user)

new_user_path
new_user_url

Quand les quatre premières sont gerées différemment, les quatre dernières sont générées dynamiquement. Vous pouvez voir ceci à la ligne 145 de polymorphic_routes.rb

instance_eval, class_eval et module_eval permettent de modifier dynamiquement le contenu d'un module, d'une classe ... Ou d'une instance de classe.

Ainsi, nous pouvons ajouter une nouvelle méthode dans une chaine de caractères, nous permettant ainsi de la nommer de la manière que nous désirons.

class_eval et module_eval

nous avons module_eval utilisé dans rails plus haut. Class_eval fonctionne de la même manière pour un objet.

Lorsqu'il est utilisé, il permet de modifier dynamiquement le contenu d'un module ou d'une classe ... Et de répercuter ces modifications sur tous les modules ou classes qui incluent celui-ci on en héritent.

Voyons par exemple :

class A
end

class B < A
end

La méthode "hello" n'est pas définie dans la classe A. Donc elle ne l'est pas non plus dans la classe B.

B.hello #=> NoMethodError

Nous pouvons avec class_eval définir celle-ci pour A et pour B.

A.class_eval do
    def self.hello
        "world"
    end
end

La méthode hello est maintenant définie et nous pouvons l'utiliser dans A et toutes les classes qui en étendent.

A.hello #=> "world"
B.hello #=> "world"

module_eval fonctionne exactement de la même manière en modifiant toutes les classes qui le module.

instance_eval

Instance_eval est encore plus puissant puisque, comme son nom l'indique, il permet de modifier une seule instance d'une classe ...

Reprenons nos classes A et B de tout à l'heure.

class A
end
class B < A
end

Et supposons le code suivant :

b = B.new
c = B.new

Maintenant, nous utilisons instance_eval afin de définir une méthode hello sur l'instance "b" de la classe B.

b.instance_eval do
    def hello
        "world"
    end
end

Et maintenant si nous faisions appel à cette methode :

b.hello #=> "world"
c.hello #=> NoMethodError

Les deux instances "b" et "c" sont tous des instances de B.
Mais maintenant que nous avons utilisé instance_eval, elles n'ont plus les même méthodes.

Supprimer des méthodes

Nous avons vu comment définir de nouvelles méthodes.
La logique veut que, si nous pouvons ajouter de nouvelles méthodes à un objet, nous puissions également en supprimer.

C'est ce que permettant remove_method et undef_method.

Remove_method supprime la méthode spécifiée de la classe. Mais si la même méthode est définie par une classe parente alors, c'est celle-ci qui sera maintenant appelée.

Under_method est plus stricte. Il supprime la méthode dans la classe fournie mais également dans toutes les classes parentes.

Aliaser des méthodes

Lorsque vous surchargez une méthode, c'est probablement dans le but de modifier son fonctionnement originel ou d'y ajouter une fonctionnalité.
Mais dans ce cas, vous perdez la fonctionnalité originelle et êtes obligé de la réimplémenter.

Afin d'éviter cela, il est possible d'aliaser des méthodes.
Supposons la classe suivante :

class B
    def hello
        "world"
    end
end

Supposons maintenant que vous souhaitiez que cette méthode hello ne retourne plus "world" mais "hello world".
Nous allons évidemment utiliser class_eval afin de surcharger la classe. Cependant nous souhaitons utiliser le code déjà implémenté dans la classe originelle.

B.class_eval do
    alias       :old_hello    :hello
end

Ce code va dynamiquement faire une copie de la méthode hello dans une nouvelle méthode old_hello. Nous pouvons donc maintenant surcharger notre classe sereinement, nous avons toujours accès à l'ancienne méthode.

B.class_eval do
    alias       :old_hello    :hello

    def hello
        "hello #{old_hello}"
    end
end

Si maintenant, nous faisons appel à B, nous aurons :

B.new.hello #=> "hello world"

Freeze

Vous avez vu que nous pouvons modifier autant que nous le désirons toute classe.
Cependant vous pouvez désirer, pour une raison ou une autre, que l'une de vos classes ne doit surtout pas être modifiée.

class B; end
B.freeze
B.class_eval do
    def hello
        "world"
    end
end #=> TypeError: can't modify frozen class

Vous pouvez ainsi empêcher volontairement toute modification dynamique de l'une de vos classes.

Conclusion

Comme vous pouvez le constater, les fonctionnalités dynamiques de ruby sont particulièrement puissantes.
Attention cependant. Avec de grands pouvoirs viennent de grandes responsabilités.

Définir dynamiquement des méthodes à mauvais escient peut fortement nuire à la vitesse de votre application, voir à sa sécurité.
Réflechissez-y à deux fois avant de modifier dynamiquement l'une de vos classes.

Les WebSockets facile avec Cramp

Les WebSockets sont l'une des nombreuses nouveautés de HTML5. Actuellement supportés par peu de navigateurs, la fonctionnalité qu'ils apportent les rends particulièrement intéressant.

Keep-Alive

Il est possible, en HTTP 1.1, de préciser combien de temps la connexion entre le navigateur et le serveur web restera ouverte. Ainsi, si dans vos entêtes HTTP vous ajoutez les headers suivants :

Kepp-Alive: 300
Connection: keep-alive

La connexion entre votre navigateur et le serveur restera ouverte 300 secondes après la réception du dernier message. A chaque message reçu, le timeout est réinitialisé.

En conséquent, tant que nous sommes connectés, nous pouvons recevoir des messages en temps réel du serveur. A condition de rester sur cette page bien évidemment. En HTML4, ceci est assez peu utile vu que nous ne pouvons utiliser ce genre de connexion en JavaScript.

WebSocket

Avec HTML5 cependant, les WebSocket font leur apparition. Implémentés uniquement dans Chromium pour le moment, ils permettent de se connecter à une page distante, de conserver la connexion et d'exécuter du code à la réception d'un message.

Voici un exemple de WebSocket relativement simple :

window.onload = function() {
    if (!window.WebSocket)
        alert('Votre navigateur ne supporte pas les web sockets');
    else
        socket.load();
}

socket = {
    _ws: null,
    load: function() {
        var location = 'ws://localhost/socket'
        this._ws = new WebSocket(location);
        this._ws.onopen = this._onopen;
        this._ws.onmessage = this._onmessage;
        this._ws.onclose = this._onclose;
    },

    _onopen: function() {
        alert('Socket ouvert !');
    },
    _onclose: function() {
        alert('Socket fermé !');
        this._ws = null;
    },
    _onmessage: function(m) {
        alert('Je viens de recevoir un message : ' + m.data);
    }
}

Décortiquons la chose.

window.onload = function() {
    if (!window.WebSocket)
        alert('Votre navigateur ne supporte pas les web sockets');
    else
        socket.load();
}
Au chargement de la page, nous initialisons l'objet socket que nous créerons ensuite. Cependant, nous ne faisons cela que si le navigateur supporte les WebSockets. Si la méthode window.WebSocket n'est pas présente, on affiche donc un gentil message d'information. Sinon on charge la suite !

load: function() {
    var location = 'ws://localhost/socket'
    this._ws = new WebSocket(location);
    this._ws.onopen = this._onopen;
    this._ws.onmessage = this._onmessage;
    this._ws.onclose = this._onclose;
}

Lorsque l'on appelle cette méthode, on initialise le WebSocket. Pour cela, on définit son url (la variable location). Puis on crée l'objet WebSocket et on le stocke dans une variable de classe afin de le récupérer plus tard.

Chaque objet WebSocket possède quelques callbacks qui sont appelés à un moment particulier de la vie de l'objet. onopen est appelé à l'ouverture du socket; onmessage est appelé à chaque fois que l'on a un nouveau message. Et onclose est appelé à la fermeture du socket.

On définit ces méthodes sur des méthodes internes à notre objet.

_onopen: function() {
    alert('Socket ouvert !');
}
Comme on est super gentil et que c'est juste une application de test, on informe l'utilisateur que son socket a bien été ouvert.

_onclose: function() {
    alert('Socket fermé !');
    this._ws = null;
}
Lorsque le socket est fermé, on libère la mémoire.

_onmessage: function(m) {
    alert('Je viens de recevoir un message : ' + m.data);
}
A chaque fois qu'un message (une nouvelle ligne dans le document) est envoyé, on affiche celui-ci à l'utilisateur.

Comme vous pouvez le voir, utiliser les sockets en eux même n'est pas très difficile.

Application en Ruby avec Cramp

Et en Ruby (merci Pratik), il existe déjà un framework compatible rack permettant de créer et maintenir des pages pour websockets : cramp.

J'ai créé un projet nommé twisocket sur github qui reprends les websockets avec Cramp afin d'afficher mon dernier tweet et le remettre à jour automatiquement.

Voyons un peu lib/twisocket/server.rb

require 'cramp/controller'
require 'cramp/model'
require 'twitter'

module Twisocket
    class Socket < Cramp::Controller::Websocket
        attr_accessor   :twitter, :timer
    
        on_start   :start_timer
        on_finish  :end_it
    
        def start_timer
            self.twitter = Twitter.user('dmathieu')
      
            #
            # We check the tweets once
            # And we set a timer to check them every 30 seconds
            #
            check_tweets
            self.timer = EventMachine::PeriodicTimer.new(30) { check_tweets }
        end
    
        def check_tweets
            render self.twitter.status.text
        end
    
        def end_it
            self.timer.cancel
        end
    end
end

Décortiquons à nouveau.

on_start   :start_timer
on_finish  :end_it
L'appel à ces deux méthodes permet de définir quelles méthodes de notre classe seront appelées lorsque la page sera lancée et lorsque son exécution sera arrêtée.

Lorsque la page est lancée, on appelle la méthode start_timer.

def start_timer
    self.twitter = Twitter.user('dmathieu')
    check_tweets
    self.timer = EventMachine::PeriodicTimer.new(30) { check_tweets }
end

Dans cette méthode, on définit l'objet "twitter", qui persistera dans la classe tant que celle-ci existera. Puis on fait un premier appel à la méthode check_tweets. Ensuite on définit un timer avec eventmachine. Notre méthode check_tweets sera appelée toutes les 30 secondes.

Lorsque la page est arrêtée, on exécute la méthode end_it

def end_it
    self.timer.cancel
end
On libère la mémoire du timer.

Enfin dans la méthode check_tweets

def check_tweets
    render self.twitter.status.text
end
On affiche sur la page le status twitter.

Si vous chargez cette page directement dans votre navigateur, vous verrez votre status twitter s'afficher sur une nouvelle ligne toutes les 30 secondes. Cependant si vous reprenez le code précédent, le status twitter sera vous sera transmis en alert() toutes les 30 secondes. Et dans le projet twisocket que j'ai créé, ce même status twitter sera affiché dans la page et remis à jour automatiquement toutes les 30 secondes ...

Plutôt sympa non ?

Et en dehors de Ruby ?

Keep-Alive fait partie du protocole HTTP. Il est donc bien évidemment possible de développer des applications utilisant les WebSockets dans n'importe quel langage De nombreux projets et langages font leur apparition depuis quelques temps afin de faciliter le développement d'applications asynchrones de ce type. Par exemple node qui permet de développer des applications web en javascript (exécuté avec V8) et qui fera peut-être l'objet d'un article dans quelques semaines.

Conclusion

La faible implémentation des WebSockets rends leur utilisation assez difficile pour le moment. Cependant leur potentiel étant particulièrement élevé, tous les navigateurs (sauf un peut-être) devraient les supporter assez rapidement. En attendant il est toujours possible de faire une version alternative en flash si vous êtes des grands fous (n'espérez pas d'article à ce propos en revanche ;) ).

Créer un tag GIT lors d'un déploiement

Non ceci n'est pas un poisson ;)

Je suis persuadé que vous utilisez tous Capistrano pour déployer vos applications web. Et vous utilisez GIT afin de versionner vos fichiers !

Cependant vous aimeriez bien vous souvenir de quand vous déployez, afin de pouvoir faire plus aisément un rollback. Ou tout simplement à but informatif. Et y'a justement une fonctionnalité cool de GIT prévue exactement pour ce genre de choses : les tags ! Vous aimeriez donc bien créer automatiquement un tag lorsque vous déployez votre application.

Comme ces tags doivent être lisibles et que nous travaillons avec Scrum, nous allons nommer chacun de ces déploiements en fonction de ce que l'on nommera des releases. Ces releases seront généralement calées sur des sprints. Mais pas forcément (un sprint peut n'avoir aucune release par exemple).

A vous de choisir comment vous nommez vos releases. Ici, on a décidé de prendre les super vilains de comics, dans l'ordre alphabétique (un en A; puis un en B, ...).

Commençons donc par nommer notre release. Nous utilisons multistage. Et nous souhaitons ne créer de tag que lorsque nous déployons en production.

Dans le fichier de configuration de la production (config/deploy/production.rb), configurons notre release.

set :current_tag_release, 'demolisseur'

Nous allons ensuite créer un recipe permettant de créer ce tag. Dans un fichier qui sera inclu pour votre déploiement (chez moi, config/deploy/recipes/deploy.rb)

require 'grit'
Capistrano::Configuration.instance.load do
    namespace :deploy do
        
        desc "Tags the local git branch and pushes it"
        task :tag do
            base_dir = File.join(File.dirname(__FILE__), '..', '..', '..')
            repo = Grit::Repo.new(base_dir)</p>

<p>            count = 1
            repo.tags.each do |tag|
                count += 1 if tag.name =~ Regexp.new("^#{current_tag_release}")
            end
      
            puts "  * -> Creating and pushing tag #{current_tag_release}-#{count.to_s}"
            system "cd #{base_dir}; git checkout #{branch}; git tag #{current_tag_release}-#{count.to_s}"
            system "cd #{base_dir}; git push --tags"
        end
    end

Que faisons-nous ?

Tout d'abord, avec grit, nous chargeons le repository git courant (donc celui de votre projet). Puis nous récupérons la liste de tous les tags et nous comptons combien de tags ont pour nom "release-x". Ou release est le nom de la release et x est le numéro du déploiement (de 1 à n). Nous ajoutons 1 à ce nombre afin d'obtenir le nom du tag à créer.

Par la suite, nous ne pouvons plus utiliser grit car celui-ci ne gère pas la création de tags. Nous devons donc utiliser system.

Tout d'abord :

system "cd #{base_dir}; git checkout #{branch}; git tag #{current_tag_release}-#{count.to_s}"
Nous nous rendons sur la branche déployée (normalement vous êtes déjà dessus mais bon ...). Puis nous créons le tag.

Puis :

system "cd #{base_dir}; git push --tags"
Nous poussons les tags sur le repository distant.

Pour finir, nous devons exécuter cette tâche à chaque déploiement en production. Retournons donc dans config/deploy/production.rb et ajoutons, en fin de fichier :

after "deploy:update_code", "deploy:tag"
Notre tag sera donc créé, juste après que le code n'ait été mis à jour sur le serveur.

Y'a plus qu'à déployer ! Notre tag sera créé et poussé sur le repository distant. Vous saurez maintenant exactement quand vous déployez votre application.

Comprendre les symboles en Ruby

Vous êtes probablement déjà tombé sur des symboles en Ruby, souvent utilisés pour les clés des hash. Exemple :

{:foo => 'bar'}

:foo est un symbole. 'bar' est une string. Mais nous pourrions faire

{'foo' => 'bar'}

Alors pourquoi utiliser les symboles ? Supposons le cas suivant :

x = :sym
y = :sym</p>

<p>(x.__id__ == y.__id__ ) && ( :sym.__id__ == x.__id__)

La comparaison ici retournera vrai. En effet, un même symbole sera toujours le même objet en mémoire, dans toute votre application. Cela ne pose pas de problèmes car un symbole est également non mutable.

Et le même avec des strings :

x = "string"
y = "string"</p>

<p>(x.__id__ == y.__id__ ) || ( "string".__id__ == x.__id__)

Ici, x, y et "string" sont tous les trois des objets différents avec des __id__ différents. 3 objets ont donc été initiés en mémoire !

Maintenant reprenons notre hash de tout à l'heure avec la comparaison string/symbole. Mais en créant deux hashes !

Avec la version symboles :

{:foo => 'bar'}
{:foo => 'doe'}

Nous créons 5 objets en mémoire : deux hashes; un symbole et deux string.

Dans la version strings :

{'foo' => 'bar'}
{'foo' => 'doe'}

Ici, nous créons 6 objets en mémoire : deux hashes et 4 strings.

Dans un cas d'école comme celui-ci, cela ne change pas grand chose. Mais dans un cas réel d'application, si vous remplacez tous vos symboles par des strings, votre consommation mémoire va rapidement s'élever. Alors commencez dès maintenant à remplacer toutes vos strings qui sont non mutables (en gros, toutes vos clés de hashes et les options dans les méthodes) par des symboles :)

Apéro Ruby Lyon de février et introduction à bundler

Demain soir aura lieu le 4e apéro Ruby à Lyon. Bien évidemment, je ne peux que vous conseiller de vous y rendre ! Je serai présent et j'entrainerai un jeune padawan avec moi afin de le trainer du côté clair de la force :)

C'est à l'Antre Autre, un petit bar sympa à côté des Terreaux. Vous pouvez venir à partir de 19h. Et nous serons probablement plusieurs à manger sur place par la suite (mais bien évidemment, vous partez quand bon vous semble).

Que vous veniez ou pas, vous avez entendu parler de rails 3 ! Et peut-être de bundler, dont je ferai une présentation d'une dizaine de minutes ("lightning talk").

Du coup, que vous veniez ou pas, vous pouvez visualiser en ligne cette introduction à bundler. Enjoy! comme on dit. Si vous avez des remarques à faire dessus, n'hésitez pas à me les faire remonter (ou alors à forker le projet et à faire les modifictions). Et je vous attends demain.

Créer un rack middleware

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</p>

<p>    def initialize(app, message = "Response Time")
        @message = message
        @app = app
    end</p>

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

<p>        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'</p>

<p>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: Valider des modèles non Active Record

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 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 ! 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.

Ruby console : demander un mot de passe

J'ai déjà mentionné la récupération d'informations par l'utilisateur en ruby console dans l'article Read Eval Print Loop. Cependant il arrive qu'il soit nécessaire de récupérer des informations sensibles via la console. Un mot de passe par exemple. Celui-ci ne devra pas s'afficher sur l'écran pendant que vous le taperez. La librairie Ruby Password permet de faire cela de manière simple. Cependant cela implique dépendre de cette librairie. Et c'est quelque chose que je ne désire pas, surtout pour quelque chose d'aussi simple.

La solution que je vous propose ici utilise donc simplement les fonctionnalités de toute console linux (pas testé sous windows. Mais qui utilise encore windows de nos jours ? :mrgreen: ).

begin
    print "Username: "
    username = $stdin.gets.chomp</p>

<p>    print "Password: "
    # We hide the entered characters before to ask for the password
    system "stty -echo"
    password = $stdin.gets.chomp
    system "stty echo"
rescue NoMethodError, Interrupt
    # When the process is exited, we display the characters again
    # And we exit
    system "stty echo"
    exit
end

Que fait-on ? Nous commençons par demander un nom d'utilisateur.

print "Username: "
username = $stdin.gets.chomp
Celui-ci n'est pas une donnée sensible et peut donc être affiché sans problème. Rien de particulier à faire.

Ensuite nous demandons un mot de passe. La il faut le masquer.

print "Password: "
system "stty -echo"
password = $stdin.gets.chomp
system "stty echo"

Le "stty -echo" masquera tous les caractères qui devraient être affichés en console par la suite. Le "stty echo" permet d'afficher les caractères qui seront tapés ensuite.

Jusque la, ça fonctionne. Cool ! Mais d'un coup j'ai un utilisateur chiant (oui toi là-bas à côté du radiateur), qui décide au dernier moment de faire Ctrl-C pour quitter le programme et se retrouve dans une console en -echo et ne voit donc plus ce qu'il tape. On vient pas de se faire un ami.

Heureusement on gère l'arrêt du système notre rescue, ou l'on indique que lorsqu'un Ctrl-C est envoyé, on désire quitter l'application. Lorsque c'est le cas, nous n'avons donc plus qu'à également réafficher les caractères et le tour est joué !

begin
    # ...
rescue NoMethodError, Interrupt
    system "stty echo"
    exit
end

Hop ! :) Et le projet qui utilise ceci, c'est glynn :)

Faire un screenshot du navigateur en Ruby avec Selenium

Hier, un PHP wannabee (berk), Romain cherchait à faire un screenshot d'une page web en "ligne de commande". Plusieurs solutions lui ont été proposées. Pour ma part, ma préférée est Selenium ! Sachant bien qu'il ne ferait jamais la chose en Ruby, j'ai donc décidé de regarder cela d'un peu plus près.

Tout d'abord, vous devez avoir Selenium RC installé et lancé. C'est simple. Téléchargez le, rendez-vous dans le dossier selenium-server-1.0 et entrez

java -jar selenium-server.jar

Votre serveur Selenium est lancé sur le port 4444. Y'a plus qu'à l'utiliser ! Vous devez également avoir la librairie selenium-client installée.

sudo gem install selenium-client

Votre disque dur étant maintenant un peu moins vide, on peut s'attaquer au code en lui même ! :) Je vais faire un screenshot de mon tout nouveau portfolio (encore en chantier).

On commence par la magie et on explique après.

require 'rubygems'
require 'selenium'

# We load Selenium
@selenium = Selenium::SeleniumDriver.new("localhost", 4444, "*firefox", "http://42.dmathieu.com/", 10000);
@selenium.start
@selenium.set_context("test_new")

# We go to the main page and take the screenshot
@selenium.open "/"
@selenium.capture_entire_page_screenshot(File.expand_path(File.dirname(__FILE__)) + 'screenshot.png', '');

# We unload Selenium
@selenium.stop

On commence par charger les librairies requises. Pas compliqué, on a besoin que de Selenium.

require 'rubygems'
require 'selenium'

Puis on charge Selenium en lui renseignant l'URL que l'on désire visiter et le navigateur avec lequel on désire visiter celle-ci. Et en définissant le contexte (ici, un firefox vierge).

@selenium = Selenium::SeleniumDriver.new("localhost", 4444, "*firefox", "http://42.dmathieu.com/", 10000);
@selenium.start
@selenium.set_context("test_new")

On charge la page, on prends le screenshot et on sauvegarde l'image créée :)

@selenium.open "/"
@selenium.capture_entire_page_screenshot(File.expand_path(File.dirname(__FILE__)) + 'screenshot.png', '');

Et on oublie pas de libérer la mémoire.

@selenium.stop

Et zou, c'est super magique ! Notre beau screenshot (de la page entière, pas seulement de l'écran) est alors généré.

Vous noterez que le javascript est exécuté (tentez de désactiver le javascript de votre navigateur et regarder ce qu'il advient de mon email affiché sur la page). Et que le rendu est effectivement le même que ce que l'on a dans le navigateur.

A quand un outil open source utilisant Selenium et générant des thumbnails ? :p

CouchDB et Ruby: CouchREST

Je vous ai présenté il y a quelques jours CouchDB. La librairie que je vous ai présenté dans le précédent article était très bien dans un but éducatif. Mais en pratique, c'est vraiment pas puissant. Aujourd'hui nous allons donc parler de CouchREST.

Commencons par installer le gem. Si vous n'avez pas Rubygems configuré pour Gemcutter, faites le maintenant !

sudo gem install couchrest

Zou ! Maintenant on se connecte, on crée une base et un document dans celle-ci !

require 'rubygems"
require "couchrest"</p>

<p>@db = CouchRest.database!("http://127.0.0.1:5984/my_database")
response = @db.save_doc({:name => 'My Document', :content => 'The content'})
p @db.get(response['id']).inspect

Super, mais jusque la, ça ne va pas beaucoup plus loin que ce que nous avons fait précédemment. Voyons donc comment créer une classe qui représentera un modèle de données CouchDB.

class Document < CouchRest::ExtendedDocument
    use_database 'my_database'</p>

<p>    property  :name
    property  :content</p>

<p>    timestamps!
end

Nous créons un modèle "Document", qui possèdera les champs name et content. Mais également created_at et updated_at (remplis automatiquement grâce à timestamps!). Maintenant nous pouvons faire les choses d'une manière similaire de ce que nous faisons avec ActiveRecord.

Créer un nouveau document.

doc = Document.new(:name => 'My Doc', :content => 'The Content')
doc.save!

Récupérer tous les documents existants

docs = Document.all

Et également appeller n'importe quelle vue grâce à des méthodes virtuelles. Par exemple si vous avez une vue "by_name".

docs = Document.by_name

CouchRest gère même la pagination des documents (avec les méthodes de pagination internes à CouchDB).

docs = Document.all.paginate(:page => 1, :per_page => 10)

Vous pouvez donc utiliser CouchDB de manière particulièrement avancée, comme vous le faites déjà avec ActiveRecord pour vos SGBDR de type SQL. Cependant avec CouchREST il vous faut toujours gérer la connexion à votre base de données "manuellement", dans un fichiers que vous placez dans config/initializers/ par exemple. Pas super pratique. Heureusement il existe CouchREST Rails, qui implémente CouchREST (et uniquement CouchREST) mais gère automatiquement la connexion au serveur.

Commencez par installer le plugin.

script/plugin install git://github.com/hpoydar/couchrest-rails.git

Puis générez les divers fichiers de configuration relatifs à celui-ci.

script/generate couch_rest_rails

Vous allez maintenant notamment avoir un fichier config/couchdb.yml, qui contient la configuration relative à CouchDB en fonction de votre environnement. Voici le contenu par défaut de ce document.

base: &base
 database_prefix:
 database_suffix: _<%= RAILS_ENV %></p>

<p>development:
  host: localhost
  port: 5984
  <<: *base</p>

<p>test:
  host: localhost
  port: 5984
  <<: *base

A vous de configurer celui-ci comme vous le désirez en fonction de vos divers environnements et de vos besoins :) Attention : vos modèles CouchREST ne doivent maintenant plus dépendre de ExtendedDocument mais de CouchRestRails::Document

Notre modèle précédent sera donc maintenant ainsi

class Document < CouchRestRails::Document

Redémarrez votre application et accédez directement à chacun de vos documents CouchDB via le modèle approprié :) Si vous regardez le contenu de votre base de données, vous verrez que CouchREST fonctionne d'une manière similaire aux Single Table Inheritance de ActiveRecord. Chaque document a un champ "couchrest-type", qui définit le modèle relatif au document. Vous pouvez donc particulièrement aisément transformer un document en un autre document.

Supposons que vous ayez des documents de type "File" et d'autres de type "Image", vous transformerez toutes vos images en fichiers de la manière suivante.

File.all.each do |file|
    doc['couchrest-type'] = 'Image'
    doc.save!
end
Ne faites pas cela sans avoir intégralement testé toutes les conséquences possibles bien évidemment (sauf si avez le désir de mourir dans d'affreuses souffrances ;) ).

Comme vous le constatez, avec CouchREST, (même si le projet a quelques problèmes de communication), vous pouvez gérer vos données stockées dans CouchDB de manière aussi aisée que toute donnée stockée dans un SGBDR geré par ActiveRecord.

Dans un prochain (et dernier) article à propos de CouchDB, nous ferons un peu de javascript avec les vues, comment les implémenter (avec CouchREST) et comment s'en servir afin de profiter de toutes les fonctionnalités du SGBD.