Ruby : encapsuler un bloc de code dans un objet
Par Bounga le dimanche, 7 janvier 2007, 20:29 - Documentations - Lien permanent
En Ruby, il est possible d'encapsuler un morceau de code dans un objet. Grâce à cela, il vous est possible d'utiliser ce même morceau de code à plusieurs endroits ou encore d'exécuter un morceau de code donné en fonction du contexte courant. En somme, toute la puissance des blocs embarqués dans un objet.
Cette possibilité est offerte par les objets de type Proc. Nous essayerons de voir dans ce billet comment mettre à profit l'utilisation des Proc pour simplifier votre code et le rendre un peu plus dynamique.
Comment créer un Proc ?
Rien de plus simple :
proc = Proc.new { |x| puts "Le paramètre est #{x}" }
proc.call('test') # => Le paramètre est test
On a donc ici créé une nouvelle instance de la classe Proc en utilisant la méthode new à laquelle on a passé un bloc de code qui sera utilisé lors de l'appel à la méthode call. Nous avons, au début de notre bloc, définie une variable qui pourra être affectée lors de l'appel et utilisée par notre bloc.
Il est également possible de créer des objets Proc de façon automatique dans les appels de méthode :
def methode_avec_bloc(x, &block)
puts block.class
x.times { |i| block[i, i*i] }
end
methode_avec_bloc(4) { |n, c| puts "Le carré de #{n} est #{c}" }
# => Proc
# => Le carré de 0 est 0
# => Le carré de 1 est 1
# => Le carré de 2 est 4
# => Le carré de 3 est 9
On a donc définie une méthode qui prend deux paramètres, un entier et un bloc a exécuter. Notre méthode affiche la classe de notre bloc pour s'assurer que c'est bien un Proc qu'on manipule puis renvoit un entier et son carré au bloc de code x fois. Lorsque nous appelons notre méthode, nous lui passons en premier paramètre le nombre d'itérations à faire puis un bloc (manipulant deux paramètres) qui se charge de l'affichage.
Oui mais si je stocke déjà mon code dans un objet Proc ?
Il est également possible de passer directement un objet Proc à une méthode attendant un bloc en précédant son nom de variable par un & :
proc = Proc.new { |n| puts n }
3.times(&proc)
# => 0
# => 1
# => 2
Oui mais où sont les exemples utiles ?
Vous êtes exigeants en plus ! Bon d'accord … Disons que vous écrivez une application en Rails et qu'il vous faut pouvoir trier une liste d'articles par titre, auteur, identifiant décroissant ou par statut (publié ou non). L'utilisation des Proc pourrait bien vous simplifier la vie.
Vous pourriez par exemple utiliser le morceau de code suivant pour éviter les duplications :
def sort
order = params[:order] || "id"
proc = case order
when "author" then lambda { |a| a.user.name.downcase }
when "status", "title" then lambda { |a| a.send(order) }
when "id" then lambda { |a| -a.id }
end
@articles = Article.find(:all).sort_by &proc
end
Notre méthode sort définie une variable order qui contient le paramètre de tri (récupéré dans un formulaire), si aucun paramètre n'a été donné, l'id sera utilisé par défaut. Ensuite la variable proc se voit affecter l'un des trois lambda (un équivalent de Proc) en fonction de l'ordre demandé. Dans le cas où le paramètre de tri est l'auteur, on utilise le champ name en minuscules, pour le status et le titre, on utilise directement le champ en question, sans traitement. C'est pour cela qu'on utilise send qui nous évite de la duplication en appelant la méthode de façon dynamique. Si on fait un tri sur l'identifiant, il nous suffit de faire appel au champ en question en prenant soin de le trier en ordre inverse à l'aide du "-".
Une fois la fonction de tri choisie, on recherche tous les articles disponibles puis on les trie en passant notre Proc à la méthode sort_by qui attend un bloc.
Conclusion
Ecrire ce genre de code n'a rien de spectaculaire mais vous permet d'écrire du code plus concis et plus évolutif. Les Proc peuvent être d'une aide très précieuse et vous permettront dans de nombreux cas de limiter le nombre de méthodes à écrire pour des actions quasiment identiques où seule une ou deux opérations de traitement varient.