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.