Derniers Posts

  • Ruby and oracle shared libraries

    You may experience the same problem as below if you are using the ruby-oci8 gem to connect to an oracle database : require ’oci8’ $ ruby test.rb /usr/lib/ruby/gems/1.8/gems/ruby-oci8-1.0.6/lib/oci8lib.so : libclntsh.so.10.1 : cannot open shared object (...)

  • Creating and Applying Patches

    You sometimes need to use patches to hold some code changes to keep it safe and eventually apply it again later. Using Subversion You can easily create a path file from a svn checkout like this : $ svn diff Index : testfile (...)

  • Max execution time with ruby threads

    This post will describe a way to define a maximum execution time for a operation using threads. Let’s start with a really simple program which takes 5 seconds to run : start=Time.now sleep 5 puts "Script finished in #Time.now-start" Let’s write a simple (...)

  • JVM Out Of Memory notification

    This is a quick tip to share a technic to be notified of an OutOfMemory exception. This has been made possible thanks to the new 1.6.0 -XX:OnOutOfMemoryError JVM option. Let’s write a simple memory consumer java program : public class Oom private (...)

  • Metaprogrammation ruby

    Pour suivre cet article sur les metaclass ruby, il faut bien avoir en tête certaines bases du langage ruby. Soupçonner l’existence des metaclass Ruby vous permet de définir et redéfinir à volonté des méthodes sur n’importe quel objet : o = Object.new def (...)

  • Quelques bases de ruby

    Ce billet en prépare un autre sur les metaclass de ruby. Mais pour bien pouvoir le comprendre, il faut bien avoir conscience de certaines notions fondamentales sur le langage. 1. Visibilité des méthodes public : toujours accessible. private : accessible (...)

  • Buffer overflow : détournement de variable

    Voici un deuxième tutoriel sur les buffer overflow, après le premier qui n’était pour moi qu’une initiation. J’ai depuis approfondis le sujet sur la base du mythique Smashing the stack for fun and profit duquel je me suis largement inspiré. Cet article (...)

  • Javascript et cross domain

    Comme vous le savez sûrement, les moteurs Javascript des navigateurs restreignent l’accès dynamique à des URLs qui sont sur des noms de domaine différents de la page courante, et cela pour des raison de sécurité ( voir XSS ). Ainsi, le script suivant ne (...)

  • Les alertes Zabbix

    Deuxième billet sur Zabbix en forme de mini tutoriel comme toujours. Aujourd’hui on va voir comment on configure Zabbix pour recevoir des alertes mail lorsqu’un trigger remonte une erreur. Ca n’est pas tellement compliqué mais on oublie facilement une étape. (...)

  • Zabbix 1.4.1 - premiers pas

    Un petit billet sur Zabbix, un outil de monitoring intéressant qui fait de plus en plus d’adeptes. Installation Note pour les utilisateurs de dédibox - Sept 08 : Si vous voulez installer Zabbix sur une dédibox en lenny vous pourriez avoir un problème avec (...)

  • Installer rubygems dans un répertoire user

    Cet article vous expliquera comment installer rubygems si vous n’avez pas le droit root sur votre machine. On va installer rubygems dans le répertoire $HOME/gems/ : % mkdir /home//gems/ On va s’appuyer sur la documentation officielle sur le site de (...)

  • Téléchargement d’un fichier en Java

    Vous avez un tableau de byte[] et vous voulez proposer de le downloader ? Ce post est fait pour vous. Je suppose que vous êtes dans votre Servlet/Controller/ActionBean ou autre et que vous avez à portée de main une jolie HttpServletResponse qui ne demande (...)

  • Tip : trigger javascript

    Je partage un petit bout de code qui pourrait vous servir pour réaliser des trigger en Javascript, c’est simple et rapide : Imaginons que vous avez une bibliothèque qui gère des appels asynchrones vers un webservice distant que vous attaquez en AJAX. Vous (...)

  • LVM : Ajouter un Volume Logique

    LVM, ou Logical Volume Manager, est un puissant outil qui permet de redimensionner ses partitions "à chaud" et surtout postérieurement à l’installation de la machine. Cet outil créé une couche d’abstraction entre les disques durs physiques et les "volumes" que (...)

  • Résoudre les conflits APT

    Voici un deuxième petit billet sur la résolution de conflits de packages (voir le premier article sur apt). Un beau matin je me retrouvais en plein upgrade de serveur et un conflit est apparu me signalant qu’un paquet essayait d’installer un fichier qui (...)

  • Recevoir les alertes mail de sa machine

    Ce petit tuto vous permettra de recevoir les mails qui sont envoyés à localhost, notamment ceux qui sont envoyés par votre machine pour communiquer avec vous lorsque quelque chose ne va plus. On considérera <user> comme le nom de votre utilisateur sur (...)

  • Tunnel SSH

    Après quelques mois d’absence, me revoila avec un nouveau post rapide sur la création d’un tunnel SSH. C’est une technique simple à mettre en place et qui peut dépanner dans certains cas :) Pourquoi faire ? Le tunnel SSH est utile lorsque vous voulez vous (...)

  • Debian : monter une partition windows

    On suppose que la partition à monter est /dev/sda1. Dans tous les cas, il faut créer un point de montage : # mkdir /windows Montage à la volée : # mount /dev/sda1 /windows # mount /dev/sda2 on / type ext3 (rw,errors=remount-ro) tmpfs on /lib/init/rw type (...)

  • Reverse Proxy

    Un petit billet sur le Reverse Proxy d’Apache. Voici comment le mettre en place de manière très simple : la conf apache : ProxyRequests Off Order allow,deny Allow from all Order allow,deny Allow from all ProxyPass / http://127.0.0.1:8080/ (...)

  • Apache : dynamic virtual hosts

    Un court article sur la gestion dynamique de virtual hosts par Apache. Mon problème était le suivant : comment faire pointer tout mes vhost vers une seule et unique appli ? La solution est donnée par la documentation Apache sur le mass virtual hosting. mod (...)

  • Installation de Munin

    Munin, outil de monitoring système incroyablement simple d’installation et d’utilisation. Installation Personnellement je ne m’en sers que pour monitorer mon serveur, mais munin fonctionne sur le mécanisme client-serveur pour la remontée d’infos. Là mon (...)

  • Git via ssh

    "dumb" protocols Au départ, j’ai voulu tester Git over https. Après quelques recherches et problèmes en tout genre, il semblerait que cette méthode soit très peu utilisable en pratique : il faut exécuter la commande ’git-update-server-info’ dans le repository (...)

  • Démarrage de votre appli rails

    Petit article qui fait la lumière sur les sources de ruby et de ses gems. load path Le "load path" est soit $LOAD_PATH ou $ : % irb irb(main):001:0> $ : => ["/usr/local/lib/site_ruby/1.8", "/usr/local/lib/site_ruby/1.8/i486-linux", (...)

  • Rester à jour sur Ruby & Rails

    Cet article est le résultat d’un sondage qui tente de regrouper différentes sources d’information pour rester à jour sur les évolutions de technos autour de Ruby et Rails. Les sources énoncées ici sont toutes gratuites. RSS Énormément de sites parlent de Ruby (...)

  • Mémoire sous linux

    Cet article regroupe pas mal d’infos que j’ai pu récupérer de ci de là, autour de la mémoire en général (morte, vive, cache ...) Disque dur Un disque dur est la superposition de plusieurs surfaces magnétiques circulaires : Chaque "disque" est découpé en (...)

  • Mantis Bug Tracker sur Lenny

    Installation $ apt-get install mantis Configuration : fichiers Dans le fichier /etc/mantis/config_db.php, paramétrez les valeurs par défaut. Je crois que cette étape est optionnelle puisque ces informations vois seront demandées par un formulaire par la (...)

  • JDBC, JRuby et DBI

    Comment utiliser un driver Java en Ruby ? Dans mon cas, il s’agit d’une base de données Adabas. Le driver ruby n’existe pas encore, mais il existe le driver Java (de.sag.jdbc.adabasd.ADriver). Installer JRuby Première chose à faire : installer JRuby. (...)

  • Trac, Integrated SCM - Project Management

    Cet article détaille l’installation "normale" de Trac puis l’installation sur Debian Sarge à partir d’une archive stable, avec le plugin pour Mercurial. Installation de Trac Trac is a minimalistic approach to web-based management of software projects. Its (...)

  • Tutoriel Mercurial + SSL + Apache2

    Mercurial : a fast, lightweight Source Control Management system designed for efficient handling of very large distributed projects. Voici brièvement les éléments mis en oeuvre pour monter un serveur Mercurial sécurisé (ssl) avec restriction d’accès (...)

  • Tutoriel : Apache - SSL - Subversion

    Cet article va détailler comment paramétrer Apache et Subversion en HTTPS. C’est pas bien méchant, on y retrouve les grandes étapes indispensables : Création d’un dépot Créer un nom de domaine sécurisé (Apache+SSL) Relier les deux avec libapache2-svn (webdav) (...)

  • Conseils simples pour un Windows stable

    Cet article s’adresse aux personnes qui ont un ordinateur et qui veulent configurer Windows pour qu’il fonctionne « correctement ». Tous les logiciels proposés sont gratuits. Cet article est en fait destiné à mon entourage et qui me demande souvent de réparer (...)

  • Des outils pour Ruby

    De la documentation au tests, Ruby fourmille d’outils qui aide les développements au quotidien. Cette liste n’est pas exhaustive mais je penses qu’elle permet d’avoir un bon aperçu. Cette liste est largement inspirée de l’article anglais (...)

  • Ajax.updater et Ajax.SlideUp

    Voici un petit article qui détaille la réalisation d’un appel Ajax qui va apparaître avec un petit effet. Le tout est réalisé avec Prototype et Scriptaculous. L’appel au serveur Dans notre code HTML, on va préparer un DIV qui va recevoir le contenu HTML renvoyé (...)

  • Mémo Ruby

    Après la conférence de Paris on Rails 2007, je penses que Ruby a de gros avantages, surtout en terme de temps de développement, alors je m’y mets une fois pour toutes. Cet article trace les grandes lignes des spécificités de Ruby, soit en terme de convention de (...)

  • FeedBurner avec SPIP

    Cet article détaille les étapes pour intégrer FeedBurner à un SPIP existant. Inscription à FeedBurner Créez un compte, renseignez le nom de l’url que vous souhaitez pour votre site, du type : Référencez ensuite sur votre site web un fil RSS avec l’URL de (...)

  • Stats : Awstats sur Debian Sarge

    Awstats est un soft qui a fait ses preuves en tant que analyseur de logs et qui va pouvoir vous sortir la fréquentation de votre site de manière extrêmement détaillée. Installation # apt-get install awstats Configuration Awstats D’abord allez dans (...)

  • Paris on Rails 2007

    Je reviens de la conférence sur Ruby on Rails (RoR) le 10 Décembre à la Villette : "Paris on Rails". J’ai beaucoup entendu parler de RoR, toujours en bien, j’ai eu à programmer rapidement quelques scripts pour récupérer des informations dans une BDD mais je ne (...)

  • Générer un sitemap SPIP

    Plugin Il y a un plugin tout prêt pour ça disponible sur SPIP zone au nom de sitemap_x_x.zip. Prenez la dernière version et décompressez le contenu dans le répertoire "plugins/" à la racine du SPIP (créez le si besoin). Vous aurez alors accès au lien vers la (...)

  • Fetchmail pour recevoir son Gmail

    Voila un petit article pour dire à Fetchmail de récupérer les emails stockés sur son compte Gmail. Installation # apt-get install fetchmail Configuration de Gmail Dans Gmail, allez dans "Settings", puis dans l’onglet "Forwarding and POP/IMAP". Dnas la (...)

  • Cryptographie : comprendre SSL

    Rivest Shamir Adleman, ce sont les noms des 3 grands mathématiciens qui ont conçus le très utilisé algorithme RSA. Le RSA s’est imposé comme réponse fiable aux problématiques de sécurité grandissantes. Le principe de la clef symétrique C’est la technique la plus (...)

  • Analyser ses logs avec Logcheck

    Logcheck est un petit outils très pratique de la Debian qui va vous éviter de devoir consulter vos fichiers de log régulièrement, il va vous envoyer un rapport par mail. # apt-get install logcheck syslog-summary Configuration Rajoutez simplement les (...)

  • Configurer Spamassassin pour Postfix

    Voici comment installer et configurer Spamassassin pour Postfix sur un Debian sarge. Spamassassin va scanner tous les mails entrant et les marquer ou non comme des spams. Installation Installez simplement Spamassassin : # apt-get install spamassassin (...)

  • Sécuriser Postfix avec Amavis et Clamav

    Vous voila donc avec un Postfix de configuré, et vous voulez maintenant mettre un antivirus pour filtrer les mails qui contiennent des virus, c’est article est pour vous. Amavis Amavis est un scanner de mail. Ce n’est pas un antivirus mais un outil qui (...)

  • Debian : Serveur de mail Postfix et Dovecot

    Le but de ce tutoriel est de faire le premier pas vers la configuration d’un serveur de mail. Il est fortement conseillé de lire l’article sur Amavis et Clamav pour la partie antivirus, puis l’article sur Spamassassin pour l’anti-spam. La sécurisation d’un (...)

  • Gérer ses backups sous Debian : backup-manager

    Une manière simple et rapide de mettre en place un système de sauvegarde automatique de ses données est d’utiliser le paquet "backup-manager". Installation Je suis sous Debian Sarge et j’utilise un dépot particulier (backport) pour backup-manager qui va me (...)

  • Backbone où est tu ?

    Backbones Quels sont les grosses architectures physiques qui gère le transport de données à travers le monde ? Qui a les liens très hauts débits qui constituent l’Internet mondial ? Voici quelques exemples de backbones dans le monde : Telia (Tele2...) : (...)

  • Une appli qui dessine

    Je vais vous décrire la manière dont je m’y suis pris pour reproduire approximativement des "dessins" genre motifs tribaux que je griffonne un peu partout, et qui (je n’ai pas honte de le dire), me plaisent beaucoup. Je fais des formes tentaculaires, qui (...)

  • XSS : Cross Site Scripting

    Les attaques par XSS consistent à réussir à passer les validations des filtres des applications web. Elles sont souvent à base de déspécialisations et d’encoding de code malicieux. Consultez une compilation de code à injecter qui peut passer ces filtres sur (...)

  • Mémo sur les scripts shell

    Petit mémo, cette fois sur le shell script. Ce sont donc quelques manipulations assez pratiques : Faire des opérations mathématiques $ a=1 ;echo $[a+1] 2 Créer un répertoire temporaire % mktemp /tmp/tmp.BHVdwY6358 Générer un timestamp timestamp=`date (...)

  • Une manière d’intégrer des vidéos dans SPIP

    Cet article n’est pas LA méthode unique et meilleure que les autres pour l’encodage et la publication de vidéos, c’est juste celle que j’ai été amené à utilisé dans le cadre d’un de mes projets. Le script D’abord un petit script qui va se charger de lister les (...)

  • 09
    24

    Ruby and oracle shared libraries

    You may experience the same problem as below if you are using the ruby-oci8 gem to connect to an oracle database :

    require 'oci8'
    $ ruby test.rb
    /usr/lib/ruby/gems/1.8/gems/ruby-oci8-1.0.6/lib/oci8lib.so: libclntsh.so.10.1: cannot open shared object file: No such file or directory - /usr/lib/ruby/gems/1.8/gems/ruby-oci8-1.0.6/lib/oci8lib.so (LoadError)
            from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
            from /usr/lib/ruby/gems/1.8/gems/ruby-oci8-1.0.6/lib/oci8.rb:20
            from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:36:in `gem_original_require'
            from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:36:in `require'
            from test.rb:1

    The OS cannot find libclntsh.so.10.1. You could try to add it directly to your LD_LIBRARY_PATH like that :

    LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/oracle/product/10.2.0/rsite/lib/ ruby test.rb

    This would work well but it is not recommended. A better way to add this path is to use the /etc/ld.so.conf.d/ folder. Create a file called /etc/ld.so.conf.d/oracle.conf inside and fill it with the path to the library folder :

    /usr/local/oracle/product/10.2.0/rsite/lib/

    Then run :

    sudo ldconfig

    And you’re done.

    Auteur: Benjamin Jaton

  • 09
    24

    Creating and Applying Patches

    You sometimes need to use patches to hold some code changes to keep it safe and eventually apply it again later.

    Using Subversion

    You can easily create a path file from a svn checkout like this :

    $ svn diff
    Index: testfile
    ===================================================================
    --- testfile        (révision 68)
    +++ testfile        (copie de travail)
    @@ -1,5 +1,6 @@
    1
    2
    +I've been inserted !
    3
    4
    end
    $ svn diff > insert.patch

    You use the output of svn diff to create the patch file and then you can use the patch command to reapply it.

    $ svn revert testfile
    'testfile' réinitialisé
    $ svn st
    ?      insert.patch
    $ patch -p0 < insert.patch
    patching file testfile
    $ svn st
    ?      insert.patch
    M      testfile
    $ svn diff
    Index: testfile
    ===================================================================
    --- testfile        (révision 68)
    +++ testfile        (copie de travail)
    @@ -1,5 +1,6 @@
    1
    2
    +I've been inserted !
    3
    4
    end

    Using Git

    $ git status
    # On branch master
    # Your branch is ahead of 'origin/master' by 1 commit.
    #
    nothing to commit (working directory clean)
    $ nano testfile
    $ git diff
    diff --git a/testfile b/testfile
    index 4765b14..67214bf 100644
    --- a/testfile
    +++ b/testfile
    @@ -1,5 +1,6 @@
    1
    2
    +I've been inserted !
    3
    4
    end
    $ git diff > insert.path

    Now you use the git apply command to apply the path file :

    $ git checkout testfile
    $ git status
    # On branch master
    # Your branch is ahead of 'origin/master' by 1 commit.
    #
    # Untracked files:
    #   (use "git add <file>..." to include in what will be committed)
    #
    #        insert.path
    nothing added to commit but untracked files present (use "git add" to track)
    $ git apply insert.path
    $ git diff
    diff --git a/testfile b/testfile
    index 4765b14..67214bf 100644
    --- a/testfile
    +++ b/testfile
    @@ -1,5 +1,6 @@
    1
    2
    +I've been inserted !
    3
    4
    end

    Auteur: Benjamin Jaton

  • 09
    10

    Max execution time with ruby threads

    This post will describe a way to define a maximum execution time for a operation using threads.

    Let’s start with a really simple program which takes 5 seconds to run :

    start=Time.now
    sleep 5
    puts "Script finished in #{Time.now-start}"

    Let’s write a simple ’timeout.rb’ program to have a timeout on this sleep action :

    start = Time.now
    timeout = ARGV[0].to_i

    call = Thread.new(start) do |s|
     puts "start thread"
     sleep(5)
     puts "stop thread"
    end
     
    loop do
     if not call.status then
       puts "thread finished"
       exit(0)
     elsif Time.now.to_i > start.to_i + timeout && call.status then
       puts "timeout"
       call.kill
       exit(1)
     end
     sleep(1)
    end

    Let’s test it :

    $ ruby timeout.rb 3
    start thread
    timeout

    $ echo $?
    1

    $ ruby timeout.rb 7
    start thread
    stop thread
    thread finished

    $ echo $?
    0

    This program will check every second if the thread has finished, if not it will check the execution time is still in the allowed time range.

    Auteur: Benjamin Jaton

  • 09
    4

    JVM Out Of Memory notification

    This is a quick tip to share a technic to be notified of an OutOfMemory exception. This has been made possible thanks to the new 1.6.0 -XX:OnOutOfMemoryError JVM option.

    Let’s write a simple memory consumer java program :

    public class Oom {
     
     private static void again(String s){
       again(s+s);
     }

     public static void main(String[] args) {
       again("eat me");
     }

    }

    Let’s run this program with a low memory limitation :

    javac Oom.java
    java -Xms32m -Xmx32m  Oom
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
            at java.util.Arrays.copyOf(Arrays.java:2882)
            at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
            at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:390)
            at java.lang.StringBuffer.append(StringBuffer.java:224)
            at Oom.again(Oom.java:4)
    (...)
            at Oom.again(Oom.java:4)
            at Oom.main(Oom.java:8)

    And now let’s try the magic JVM option :

    java -Xms32m -Xmx32m -XX:OnOutOfMemoryError="mail -s 'Out Of Memory occured (`hostname`, `date`)' root@localhost <<< ''" Oom
    #
    # java.lang.OutOfMemoryError: Java heap space
    # -XX:OnOutOfMemoryError="mail -s 'Out Of Memory occured (bja, jeudi 4 juin 2009, 11:48:50 (UTC+0200))' root@localhost <<< ''"
    #   Executing /bin/sh -c "mail -s 'Out Of Memory occured (bja, jeudi 4 juin 2009, 11:48:50 (UTC+0200))' root@localhost <<< ''"...
    Null message body; hope that's ok
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
            at java.util.Arrays.copyOf(Arrays.java:2882)
            at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
            at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:390)
            at java.lang.StringBuffer.append(StringBuffer.java:224)
            at Oom.again(Oom.java:4)
    (...)
            at Oom.again(Oom.java:4)
            at Oom.main(Oom.java:8)

    You should have noticed the extra lines about mail notification. A mail has been sent "Out Of Memory occured (bja, jeudi 4 juin 2009, 21:48:50 (UTC+0200))".

    You may want to use a dedicated shell script to execute more complex operations :

    -XX:OnOutOfMemoryError="/path/to/script/notifier.sh"

    Auteur: Benjamin Jaton

  • 09
    5

    Metaprogrammation ruby

    Pour suivre cet article sur les metaclass ruby, il faut bien avoir en tête certaines bases du langage ruby.

    Soupçonner l’existence des metaclass

    Ruby vous permet de définir et redéfinir à volonté des méthodes sur n’importe quel objet :

    o = Object.new

    def o.hello
     "Hello !"
    end

    puts o.hello            #=> "Hello !"
    puts Object.new.hello   #=> undefined method `hello'

    Ici on a définit la méthode hello uniquement pour une instance de Object : o. Les autres instances d’Object n’auront pas cette méthode.

    Comment est ce possible, puisque seuls les objets de type Class peuvent contenir des méthodes ?

    Il s’avère que la mécanique interne de Ruby créé ce qu’on appelle une metaclass pour contenir ces méthodes. Mais venons en au vif du sujet :

    Qu’est ce qu’une metaclass ?

    Si je devait définir moi même ce qu’est une metaclass, je dirai que c’est une classe cachée rattachée à un objet en particulier :

    - c’est une classe : elle peut donc contenir des méthodes.
    - cachée : les metaclass font plus ou moins partie de la magie de ruby et il n’est pas simple d’accéder ou de manipuler celles ci. Nous allons y revenir.
    - elle est spécifique à un objet, chaque objet a son unique metaclass.

    Il y a une syntaxe particulière pour accéder au contexte d’une metaclass. Ca ne s’invente pas :

    class Foo
     class << self
       # Ici on se trouve dans le contexte de la metaclass Foo
       puts self
     end
    end       #=> #<Class:Foo>

    puts Foo       #=> Foo
    puts Foo.new   #=> #<Foo:0xb7c6b148>

    Le résultat special #<Class:Foo> est spécifique aux metaclasses. Il faut le comprendre comme : "instance de Class ratachée à Foo".

    A partir de l’exemple précédent, on peut définir une méthode générique sur la classe Object pour accéder à la metaclass de n’importe quel objet :

    class Object
     def meta
       class << self
         self
       end
     end
    end

    Ainsi on peut écrire directement :

    class Foo
    end

    puts Foo.meta        #=> #<Class:Foo>
    puts Foo.new.meta    #=> #<Class:#<Foo:0xb7cb19cc>>

    On remarque bien que la metaclass d’une instance de Foo est spécifique à l’instance #<Foo:0xb7cb19cc> en particulier.

    Définir des méthodes sur les metaclass

    Vérifions ce que nous avons dit au départ, à savoir que les metaclass définissent des méthodes pour l’objet auquel elle est rattachée :

    class Foo
    end

    f = Foo.new

    def f.hello
     puts "Hello !"
    end

    f.hello                                          #=> Hello !
    puts f.meta.instance_methods.include?("hello")   #=> true
    puts f.singleton_methods.include?("hello")       #=> true

    La méthode définie directement sur l’objet f est bien contenue dans la metaclass de l’objet f.

    class Foo
    end

    f = Foo.new

    class << f
     def hello
       puts "Hello !"
     end
    end

    f.hello                                          #=> Hello !
    puts f.meta.instance_methods.include?("hello")   #=> true
    puts f.singleton_methods.include?("hello")       #=> true

    De la même manière, une méthode d’instance définie directement sur la metaclass de f la rend disponible pour cet objet.

    Comme vous le savez maintenant parfaitement, les classes sont elles même des objets, il est donc tout à fait possible, et même courant ; d’utiliser les metaclass pour leur définir des méthodes. En fait, sans le savoir c’est ce que vous faisiez depuis toujours :

    class Foo
     def self.hello
       puts "Hello !"
     end
    end

    Foo.hello                                          #=> Hello !
    puts Foo.meta.instance_methods.include?("hello")   #=> true
    puts Foo.singleton_methods.include?("hello")       #=> true

    Et donc, on peut écrire également :

    class Foo
    end

    class << Foo
     def hello
       puts "Hello !"
     end
    end

    Foo.hello                                          #=> Hello !
    puts Foo.meta.instance_methods.include?("hello")   #=> true
    puts Foo.singleton_methods.include?("hello")       #=> true

    Tout cela est cohérent, continuons.

    Gestion unifiée

    La gestion des classes est similaire à la gestion de n’importe quel autre objet. Ceci est un aspect qui m’a séduit. Les classes définissent des méthodes pour leurs instances, et si on veut que ces classes aient des méthodes elles aussi, alors on fait la même chose en faisant appel à leur metaclass. Cette vision unifiée est extrêmement agréable et rend sa compréhension et son utilisation facile et intuitive après un peu d’entraînement ( si si ! ).

    La classe Objet définit la méthode singleton_methods. Elle renvoie la liste des méthodes applicables sur l’objet receveur, que le receveur soit une classe ou une instance de Foo (par exemple).

    Matz’s Ruby Interpreter (MRI)

    L’implémentation en C de l’interpréteur ruby de Matz définit 3 structure de base :

    struct RBasic {  
       unsigned long flags;  
       VALUE klass;  
    };

    struct RObject {  
       struct RBasic basic;  
       struct st_table *iv_tbl;  
    };

    struct RClass {  
       struct RBasic basic;  
       struct st_table *iv_tbl;  
       struct st_table *m_tbl;  
       VALUE super;  
    };

    Un object au sens ruby est un RObject en C. Il définit :

    - une référence à une RClass : value klass
    - des flags d’état : unsigned long flags ( ils servent à stocker des informations comme taint, freeze, etc. )
    - une map de variable d’instance : struct st_table *iv_tbl

    Une classe au sens ruby est une RClass C avec :

    - une référence vers un RClass, des flags et des variables d’instance comme pour un Object. C’est la raison pour laquelle on considère qu’une classe ruby rempli toutes les conditions pour être considéré comme un objet.
    - une map de méthodes indexée par leur nom : struct st_table *m_tbl
    - une référence vers une RClass super qui représente généralement la superclass ruby : VALUE super

    Un point important sur les relations de ces structures C :

    Pour toute RClass rc, la super RClass de la klass de rc est la klass de la super RClass de rc.

    C’est compliqué, oui, je sais. J’ai mis du temps à le comprendre. C’est d’autant plus dur à comprendre que toute cette logique est cachée en ruby, et qu’il est difficile de le prouver avec un simple irb. Il est bien plus simple de se faire une idée avec le graph suivant :

    Cela correspond à :

    class Foo
    end

    class Bar < Foo
    end

    Ainsi on voit que :

    - la klass de la super RClass de Bar est : la metaclass de Foo
    - la super RClass de la klass de Bar est : la metaclass de Foo

    C’est juste la manière dont c’est codé. Tout ceci est indétectable du point de vue de ruby :

    class Foo
    end

    class Bar < Foo
    end

    puts Bar.meta.superclass   #=> #<Class:Class>
    puts Bar.superclass.meta   #=> #<Class:Foo>

    Ruby considère que la superclass de toute metaclass est la metaclas de Class.

    Ordre de recherche d’une méthode

    Lorsque l’on appelle une méthode sur un objet O, comment ruby fait il pour retrouver cette méthode ?

    - d’abord, il cherche dans les méthodes de la RClass du RObject correspondant à O
    - ensuite il cherche récursivement dans les super RClass

    Dans le graph précédent, un appel à Bar.new.hello suivra le chemin de recherche suivant :

    - recherche de la méthode dans Bar
    - recherche de la méthode dans Foo
    - recherche de la méthode dans Object

    Ceci assure l’héritage des "méthodes d’instance" (héritage classique).

    Similairement, un appel de méthode comme Bar.hello suivra le schéma :

    - recherche de la méthode dans Bar.meta
    - recherche de la méthode dans Foo.meta
    - recherche de la méthode dans Object.meta
    - recherche de la méthode dans Class

    Ceci assure l’héritage pour les "méthodes de classe".

    Classes ouvertes

    La définition des classes n’est jamais finie, elles restent ouvertes :

    class Foo
     def self.load_hello
       def hello
         puts "Hello !"
       end
     end
    end

    begin
     Foo.new.hello  #=> dynamic_methods.rb:9: undefined method `hello' for ...
    rescue
     Foo.load_hello
     Foo.new.hello  #=> Hello !
    end

    La méthode hello a été chargée dynamiquement.

    Etendre des classes

    Pour étendre un model existant, comme ActiveRecord par exemple, il y a une manière de faire qui n’est pas obligatoire mais reconnue et largement adoptée par la communauté.

    Dans la série des acts_as_*, on va faire le notre, très simple : acts_as_taggable, dont l’objectif va être de rendre les instances d’une classe "taggable", on pourra leur attacher un tag.

    La convention générale est d’intégrer une fonctionnalité de manière à avoir l’inclusion d’un module sur une classe mère ( comme ActiveRecord ::Base ), puis d’appeler la feature directement sur les classe voulues ( model.rb ) :

    require 'acts_as_taggable'

    class MyBase
     include Acts::Taggable
    end

    class Foo < MyBase
     acts_as_taggable :info
    end

    class Bar < MyBase
     acts_as_taggable :comment
    end

    Notre classe mère sera MyBase.

    D’abord on va créer dans le fichier ’acts_as_taggble.rb’ le module Acts ::Taggable :

    module Acts
     module Taggable

       def self.included(base)
         puts self   #=> Affiche Acts::Taggable

         # adds ClassMethods instance methods to the class object 'base'
         base.extend(ClassMethods)
       end

       module ClassMethods

         def acts_as_taggable( name )
           puts self   #=> Affiche Foo, puis Bar
         end

       end

     end

    Lorsque le module va être inclu par MyBase, la méthode included sera appelée sur le module Acts ::Taggable, en passant en paramètre la classe qui l’inclue.

    A cette objet Class, qui est MyBase dans notre exemple, on va rajouter les méthodes d’un module généralement nommé ClassMethods. Ici, acts_as_taggable sera ajoutée aux singleton_methods de Foo ( ca sera bien une méthode de classe )

    Cette méthode acts_as_taggable va être utilisée dans la déclaration des classes (elle va s’appliquer sur self=la classe). Ainsi dans la méhode acts_as_taggable on aura la variable self fixée à la classe tagguée.

    On va alors définir dynamiquement des méthodes sur ces classes :

    module Acts
     module Taggable

       def self.included(base)
         # adds ClassMethods instance methods to the class object 'base'
         base.extend(ClassMethods)
       end

       module ClassMethods
         
         def acts_as_taggable( name )
           class_eval <<-END_CE
             def #{name}=(t)
               @tag=t
             end
             def #{name}
               @tag
             end
           END_CE
         end

       end

     end
    end

    La méthode class_eval exécute du code dans le contexte du module courant. Ici, cela va rajouter les méthodes d’instance info et info= à la classe Foo, et comment et comment= à la classe Bar. Ces méthodes sont les accesseurs de la variable d’instance @tag qui stocke la valeur du tag.

    require 'model'

    f = Foo.new
    f.info = "This class is beautiful !"

    b = Bar.new
    b.comment = "I'm just another class"

    puts f.info               #=> "This class is beautiful !"
    puts b.comment            #=> "I'm just another class"

    Voila

    Voila c’est finit pour cet article. J’espère que certains y trouveront des réponses et/ou des questions :) A bientôt !

    Resources

    - Une conférence sur infoQ de Dave Thomas sur des bases de la metaprogrammation ruby
    - Un article expliquant les metaclass et leur utilité
    - Un article de Patrick Farley détaillant les implémentations de la MRI
    - Le ruby hacker guide qui détaille au chapitre 4 l’implémentation de la MRI

    Auteur: Benjamin Jaton