Gilliek
5avr/124

La Metaprogrammation en Ruby

Voici (enfin !) mon premier billet en 2012 ! L'année passée, je vous avais promis quelques articles techniques (notamment sur la programmation). Aujourd'hui, c'est chose promise chose due : on va parler de métaprogrammation en Ruby.

Cet article sera accompagné d'un autre qui traitera de la fonction yield(). Et ces deux articles précéderont une série d'autres à propos du célèbre framework Ruby : Ruby On Rails.

Qu'est-ce que la metaprogrammation ?

J'imagine que beaucoup d'entre-vous ont ouvert de grands yeux à la lecture de ce mot barbare : "la métaprogrammation". En fait, cela n'a rien de bien compliqué : La métaprogrammation consiste à écrire des programmes qui décrivent eux-mêmes des programmes. Autrement dit, ça permet (entre autres) de générer du code directement au runtime. Comme vous allez pouvoir le constater, c'est vraiment très puissant.

La définition de Wikipedia énumère les différentes façons de procéder :

  • l'utilisation de générateur de code (ce que j'ai mentionné juste avant)
  • la programmation avec des templates
  • l'utilisation d'un protocole à méta-objets
  • l'utilisation de macros.

A quoi peut bien servir la metaprogrammation ?

La métaprogrammation peut servir à grandement simplifier la lecture du code (notamment via le principe Don't Repeat Yourself) ou dans le cas de Rails à simplifier la vie du développeur.

Deux petits exemples pour la route !

Admettons que l'on fasse un programme qui gère une base de données client et qui manipule donc plein de chaînes de caractères (String) qui peuvent signifier plein de choses (un numéro de téléphone, une adresse email, un numéro client, etc.). Nous aimerions qu'un objet String dispose de méthodes telephone?, email?, no_client? ... et peut-être même d'autres plus tard ! Comment faire ? Vous vous doutez bien de la réponse : avec la métaprogrammation !

Voici le code : (je vous expliquerai juste après son fonctionnement, donc pas de panique ;-) )

  #!/usr/bin/env ruby

class String
        @@string_checks = {
        'phone?' => %r{^0[\d]{2}\/[\d]{3}\.[\d]{2}\.[\d]{2}$},
            'email?' => %r{^.+@.+\.[a-z]{2,4}$},
            'no_client?' => %r{^[A-Z]{2}[\d]{4}-[\d]{4}-[\d]{4}$}
        }
        def method_missing(method_id)
                kind_of_string = method_id.to_s
                if @@string_checks.has_key?(kind_of_string)
                        if self =~ @@string_checks[kind_of_string]
                                true
                        else
                                false
                        end
                else
                        raise NoMethodError,
                                "Undefined method #{method_id} for \"#{self}\":String"
                end
        end
end

def humanize(bool)
        bool ? "Yes !" : "No !"
end

begin
        # Valid
        tel = "022/123.45.67".phone?
        email = "foo@foo.com".email?
        no_client = "CH1234-4242-4242".no_client?

        puts "Is 022/123.45.67 a valid phone number ? #{humanize(tel)}"
        puts "Is foo@foo.com a valid email address ? #{humanize(email)}"
        puts "Is CH1234-4242-4242 a valid client number ? #{humanize(no_client)}"

        puts "#################################################"

        #Invalid
        tel = "0033/123.12.12.12".phone?
        email = "www.foo.com".email?
        no_client = "42".no_client?

        puts "Is 0033/123.12.12.12 a valid phone number ? #{humanize(tel)}"
        puts "Is www.foo.com a valid email address ? #{humanize(email)}"
        puts "Is 42 a valid client number ? #{humanize(no_client)}"
end

Alors qu'est-ce que j'ai fait ?

Vous noterez tout d'abord que je définis une classe String qui est en fait une classe prédéfinie. En effet, en Ruby il est tout à fait possible de rajouter des choses dans les classes prédéfinies.

Il faut ensuite faire attention à la méthode method_missing que j'ai ajouté à la classe String. Que fait cette méthode ? Et bien si vous appelez une méthode non définie d'un objet String, Ruby va jeter un oeil dans cette méthode pour voir si on y définit quand même la méthode.  Et c'est justement là-dessus que je joue en récupérant l'id de la méthode et cherchant l'expression régulière correspondante au nom de la méthode appelée.

Après, je lève simplement une exception NoMethodFound si la méthode appelée n'est pas correct.

Finalement, je fais une méthode humanize ... qui n'a rien de spéciale :-) Elle ne fait que mettre en forme un booléan. Le reste n'est ensuite que des tests que vous pouvez vous amusez à lancer.

N'est-ce pas beau de pouvoir simplement appeler "foo@foo.com".email? pour vérifier s'il s'agit bien d'un email valide ?

Ainsi, il va gérer la méthode dynamiquement et directement au Runtime :-) De plus, si on veut par la suite ajouter d'autres vérifications pour une String, il suffit d'ajouter ce dont on a besoin dans la hash table @@string_checks.

Il y a également d'autres façons d'utiliser la métaprogrammation. On peut générer des méthodes sous forme de chaînes de caractères et utiliser la méthode class_eval pour l'évaluer. Regardons ça avec un petit exemple.

On aimerait créer une méthode attr_accessor_with_backup qui déclarerait un attribut et ses accesseurs correspondant (getter et setter) et qui garderait un backup de la valeur précédente.

 #!/usr/bin/env ruby

class Class
        # Store a backup of the previous value in attr_name_backup
        def attr_accessor_with_backup(attr_name)
          # Make sure it's a String
          attr_name = attr_name.to_s
          # Create a getter for attr_name and attr_name_backup
          attr_reader attr_name
          attr_reader attr_name+"_backup"
          # Generate setter for attr_name
          class_eval %Q{
                  def #{attr_name}=(value)
                        # backup previous value
                        @#{attr_name}_backup = #{attr_name}
                        # set the new value
                        @#{attr_name} = value
                  end
          }
        end
end

class WeNeedBackup
        attr_accessor_with_backup :foo
end

begin
        spock = WeNeedBackup.new
        spock.foo = "42"
        puts "foo => #{spock.foo} and foo_backup => #{spock.foo_backup}"
        spock.foo = "24"
        puts "foo => #{spock.foo} and foo_backup => #{spock.foo_backup}"
        spock.foo = "128"
        puts "foo => #{spock.foo} and foo_backup => #{spock.foo_backup}"
end

Cette fois on ajoute une méthode attr_accessor_with_backup dans la classe Class (classe dont toutes les classes Ruby sont des sous-classes), qui, rappelons-le, fait exactement comme la méthode prédéfinie attr_accessor en plus de stocker un backup de la valeur précédente de l'attribut.

Dans cette méthode on va alors créer des getters pour le nom de l'attribut (attr_name)  et son backup (attr_name_backup) qui contiendra la valeur précédente de attr_name.

Ensuite, il va nous falloir définir dynamiquement un setter pour l'attribut. On va donc utiliser la méthode class_eval et lui donner en argument une chaîne de caractères contenant le code de notre setter. Cette méthode s'occupera de sauvegarder la valeur courante de l'attribut dans l'attribut _backup et de mettre à jour la valeur de l'attribut.

Finalement, notre méthode attr_accessor_with_backup va générer les méthodes nécessaires directement au runtime.  N'est-ce pas monstrueusement puissant ?

Conclusion

Vous l'aurez sans doute constaté, la métaprogrammation permet de faire des tas de choses bien pratiques et peut ainsi simplifier la vie du développeur.

Le framework Rails utilise la métaprogrammation pour simplifier l'écriture du code par le développeur. Par exemple, il génère des helpers pour gérer les chemins aux ressources. Par exemple, pour accéder au controller products, il suffira d'appeler products_path ou products_url si on veut un chemin absolu. Si on veut accéder à l'action new du controller products, il suffira d'appeler new_product_path ou new_product_url. Ne vous inquiétez pas, nous reviendrons dessus beaucoup plus en détail très prochainement lorsque nous aborderons Rails.

J'espère que ce billet vous aura été utile ;-)

19déc/110

[Archlinux] Visionnez les PDF directement depuis Firefox

On va voir une petite astuce qui permet de visionner les PDF directement depuis Firefox, sans avoir à les télécharger et sans avoir besoin d'utiliser Adobe Acrobat Reader. Ca ne fonctionne que pour les systèmes GNU/Linux (What else ?©) et bien que l'intitulé du tutoriel porte le nom "Archlinux", l'astuce fonctionne en principe pour toutes les distributions; il vous faudra juste chercher le paquet spécifique à votre distribution ;-)

L'astuce est inspirée de la documentation d'Archlinux : https://wiki.archlinux.org/index.php/Firefox_Tips_and_Tweaks#Viewing_PDF.2FPS_inside_Firefox

Les prérequis sont :

  • Une distribution GNU/Linux
  • Firefox. (pour les utilisateurs de chromium, voir le "edit 2" à la fin de l'article)
  • Un lecteur PDF (par exemple Evince)

 

Allons-y !

Il faut tout d'abord installer le paquet mozplugger. Malheureusement, il ne se trouve pas dans les dépôts d'Archlinux. Vous devrez donc aller le chercher sur AUR. Si vous avez yaourt d'installé :

yaourt -S mozplugger

Sinon on y va façon geek 8-)

wget http://aur.archlinux.org/packages/mo/mozplugger/mozplugger.tar.gz

On décompresse le tout (nécessite l'utilitaire pour décompresser les tar gz, évidemment) :

tar xvzf mozplugger.tar.gz && cd mozplugger

On créé le package :

makepkg -s

Et on l'installe (le nom peut varier en fonction de la version, adaptez-le si ça ne fonctionne pas) :

sudo pacman -U mozplugger-1.14.3-1-x86_64.pkg.tar.xz

Maintenant que mozplugger est installé, il nous faut virer certains fichiers :

rm ~/.mozilla/firefox/*.default/pluginreg.dat

Editons maintenant le fichier de configuration de mozplugger (/etc/mozpluggerrc)avec notre éditeur de texte préféré (vim pour ma part ^^). Il ne faut pas oublier de le lancer en root pour avoir les droits de modification sur le fichier ;-)

Une fois le fichier ouvert, on commente la ligne (ligne 288 chez moi) suivante qui se trouve dans la partie PDF après la ligne GV() :

#repeat noisy fill exits: evince "$file"

Et on ajoute juste après :

repeat noisy swallow(evince) fill: evince "$file"

Note, vous pouvez remplacer evince par un autre lecteur PDF (comme Okular. Ca devrait normalement fonctionner.

Pour finir, on relance Firefox et le tour est joué. Il ne reste plus qu'à ouvrir un PDF pour voir si ça fonctionne bien.

J'espère que cette astuce vous aura été utile. En ce qui me concerne, je trouve vraiment pratique de pouvoir visualiser un PDF sans avoir à le télécharger (ça évite le chaos dans le dossier Downloads).

 

Edit 1 : Vous pouvez vous amusez à modifier le fichier de configuration de mozplugger pour gérer d'autres formats de fichiers (.doc/.docx, etc.). Pour la doc, c'est pas ici : http://mozplugger.mozdev.org/documentation.html

Edit 2 : Pour les utilisateurs de chromium, c'est par ici : https://aur.archlinux.org/packages.php?ID=46826  je n'ai pas testé, mais en principe ça devrait fonctionner de la même façon ;-)

18déc/1110

La technologie n’est pas seulement pour les humains !


Un lézard joue sur un smartphone - Vidéo humour

Les lézards ont eu aussi le droit de jouer :-P

Soit dit en passant, l'appli est sympa, mais on s'en lasse vite (je l'avais testée il y a quelques mois)

Taggé comme: 10 Commentaires
18déc/110

Interview de Richard Stallman

Une interview intéressante de Richard Stallman :

http://www.commentcamarche.net/news/5857618-richard-stallman-la-censure-sur-le-web-a-explose-en-france

18déc/119

Shaarli me voici !

Sebsauvage, un bloggeur que je suis, a récemment développé Shaarli une petite application web écrite en PHP et qui permet de partager des liens. J'ai récemment mis Shaarli en place sur mon serveur et vous pouvez désormais suivre mes "liens en vrac" à l'adresse suivante :

http://links.gilliek.ch/

18déc/116

Personnaliser Unity : La suite

Dans un article précédent, je vous avais donné quelques petits trucs pour personnaliser Unity sous Ubuntu 11.10. Je vous avais notamment mentionné Ubuntu Tweak comme étant un outil indispensable et qu'il fallait suivre sa sortie prochaine pour Ubuntu 11.10. Et bien on dirait qu'elle pointe le bout de son nez : La version finale d’Ubuntu Tweak 0.6 bientôt prête

J'ai aussi découvert un nouvel outil pour personnalisé certains paramètres d'Unity : MyUnity

Pour l'installer, ajoutez le dépôt de MyUnity :

sudo add-apt-repository ppa:myunity/ppa

Puis, mettez à jour la liste des paquets :

sudo apt-get update

Et finalement, installez le paquet myunity :

sudo apt-get install myunity