Ruby possède un truc cool : les classes ouvertes. Lorsque j’écris :

class String
  def foo
    "foo"
  end
end

Je ne suis pas en train de définir une nouvelle classe String, je suis simplement en train d’ouvrir la classe String existante pour lui ajouter une nouvelle méthode.

C’est très utile et très cohérent avec l’esprit orienté objet de Ruby. Mettons que j’ai besoin de cacher les voyelles d’une phrase pour un hypothétique jeu de lettre, je peux écrire un module avec une collection de méthodes utilitaires, par exemple:

module Util
  def self.hide_vowels(string)
    string.tr('aeiouy', '*')
  end
end

puts Util.hide_vowels("bonjour xavier")

Ce qui donne:

b*nj**r x*v**r

Mais on peut aussi tirer parti des classes ouvertes de cette manière:

class String
  def hide_vowels
    tr('aeiouy', '*')
  end
end

puts "bonjour xavier".hide_vowels

C’est plus élégant, à la fois dans la définition et dans l’utilisation. Seulement ce genre de code peut poser problème quand il est utilisé dans des bibliothèques, puisque une fois chargée, la nouvelle méthode est visible dans tout le code client. Parfois c’est ce qu’on veut, parfois ce ne devrait être qu’une méthode utilitaire du code tiers.

Ruby 2.0 propose le raffinement de méthode (method refinement) pour pallier à ce problème. L’idée est de limiter la portée des méthodes ajoutées ou modifiées avec le mécanisme des classes ouvertes. Voici un petit exemple:

module CoolString
  refine String do
    def hide_vowels
      tr('aeiouy', '*')
    end
  end
end

# puts "abc".hide_vowels

using CoolString
puts "abc".hide_vowels

La ligne en commentaire provoquerait une erreur (NoMethodError). Pour pouvoir utiliser la méthode hide_vowels il faut explicitement écrire using CoolString. La portée de hide_vowels s’étend du moment où on utilise la méthode using jusqu’à la fin du fichier.

Le raffinement de méthode est expérimental dans Ruby 2.0 et devrait être définitivement adopté dans Ruby 2.1. Pour aller plus loin, on peut se reporter à la documentation.

À demain.