Valid XHTML     Valid CSS2    

Tuteur Ruby / Ruby Tutorial

gilles.hunault@univ-angers.fr

Attention : ce tuteur utilise Ruby 1.9 et non pas Ruby 1.8 et comme il y a quelques incompatibilités entre ces versions, il faut être prudent(e) si vous faites des copier/coller des instructions ou des programmes. Comme mes autres tuteurs, ce texte est une introduction à un langage de programmation, pas à la programmation en général. On suppose donc ici que vous savez déjà programmer, mais dans un ou plusieurs autres langages.

Il est possible d'apprendre rapidement les rudiments de Ruby avec des sites comme tryruby (anglais), Ruby en vingt minutes (français) ou Apprenez Ruby (français). D'autres liens et des vidéos sont disponibles pour la communauté française ici.

Une fois Ruby installé, l'interpréteur interactif irb permet de tester du code, des expressions, des programmes... Il peut même compléter les commandes avec la touche Tab. Voir par exemple irb shell pour plus de détails sur irb et son utilisation. Vous pouvez aussi utiliser irb sur le web sans rien installer avec le site tryruby.

Enfin, sur le site zenspider, on trouvera une "carte de référence" détaillée pour Ruby nommée Ruby QuickRef et bien sûr l'ouvrage de référence par le créateur du langage est disponible en ligne : Programming Ruby.

Table des matières

  1. Présentation de Ruby

  2. Syntaxe élémentaire

  3. Nos exemples standards

  4. Compléments pour les bases de données, le Web et XML

  5. Forces et faiblesses du langage

1. Présentation de Ruby

Le créateur de Ruby, Yukihiro « Matz » Matsumoto reconnait avoir voulu créer son langage à partir de SmallTalk, Lisp et Perl. Il voulait un langage plus lisible, plus souple. La première version date de 1995 mais la version 1.9 n'a été disponible qu'en 2009. Ruby est surtout connu à cause de son fameux framework de développement nommé Rails ou Ror (Ruby On Rails) basé sur MVC, dont nous recommandons le tutoriel long et détaillé en français à l'adresse http://french.railstutorial.org/. Il y a bien sûr une rivalité entre Python, Ruby, Php et Perl et aussi entre C (ou variantes), Java et Ruby, rivalités déplacées parce des objectifs différents requièrent sans doute des langages différents.

En Ruby, tout est objet et tout est ouvert. Ainsi, l'opérateur + peut être redéfini pour une classe particulière d'objets, les crochets d'indexation aussi, - et / aussi, etc. Certaines méthodes sont chainables et les parenthèses facultatives, sauf en cas d'ambiguité. Par exemple a.sort.reverse.first 3 est légal et interprété comme a.sort().reverse().first(3). Certaines méthodes sont définies sans objet apparent parce qu'elles sont très souvent utilisées, comme par exemple p, print et puts pour afficher des valeurs, mais ce sont en fait des raccourcis qui font appel a l'objet Kernel. Ainsi puts a est interprété comme Kernel.puts(a), ce qui permet une lecture plus aisée des programmes Ruby. Les notions d'énumération et d'itération, couplées à celles de bloc, de fonction anonyme (lambdas) et de procédure en font un langage particulièrement expressif. Le traitement des expressions régulières "à la Perl " et l'utilisation de variables globales cryptiques le rendent aussi puissant que Perl y compris en mode one-liner, tout en conférant au langage une supériorité pour la conception objets.

Ruby est réflexif : on peut explorer le langage à travers ses propres méthodes. Par exemple 2.class est interprété comme 2.class() et renvoie Fixnum, 2.class.ancestors() fournit la classe de 2 et ses super-classes, Array.methods() donne la liste des 94 méthodes de la classe Array.

2. Syntaxe élémentaire

Pour accèder aux méthodes des objets en Ruby, la notation point est utilisée et les paramètres sont mis entre parenthèses. Ainsi, on peut suivre le modèle objet.methode(paramètres) pour écrire 2.+(3) mais la syntaxe traditionnelle 2 + 3, moins "choquante", est également disponible, ce qui autorise l'écriture procédurale traditionnelle et l'écriture fonctionnelle classique aussi. Les fonctions de conversion se nomment to_i, to_f, to_a, to_s... La méthode class fournit la classe de l'objet auquel on l'applique. Ainsi "oui".class() renvoie String. Une méthode qui se termine par le caractère ? indique un test. Par exemple a.nil? renvoie vrai ou faux suivant que a est nil ou pas. Une méthode qui se termine par ! indique qu'il faut faire attention à l'utilisation de la méthode, le plus souvent parce qu'elle modifie l'objet. Par exemple b = a.sort et b = a.sort! ne réalisent pas la même chose : dans le second cas, a est de plus trié in situ.

L'affectation se fait par = et peut être parallèle ou multiple, avec des subtilités liées à la possibilité d'affecter des structures. Ainsi a,b=b,a permute les variables a et b, mais a,(*b,c),d= (1..6).to_a ne fait certainement pas ce que vous croyez ! L'affectation multiple est surtout pratique pour les retours de fonction, comme par exemple x,y=f(...) quand on sait que f renvoie deux valeurs. En plus des constantes true et false, il y a nil. Seuls false et nil peuvent être évalués comme false (pas une expression correspondant à 0 ou à une chaine vide). Il y a des délimiteurs de bloc notés do end et { } mais ils n'ont pas la même priorité d'évaluation. Les grands entiers sont gérés en précision illimitée de façon transparente. Ainsi 2.class est Fixnum mais (2**150).class est Bignum.

L'égalité se teste par ==, === mais aussi par eql? et equal? et <==>. Un test utilise la syntaxe if elsif else end mais le mot then est possible, de même que unless alors que la structure de cas utilise case when else end. Une boucle explicite est définie par les mots-clés explicite while, loop, until, (avec do, redo, retry et next possibles) mais on préfére souvent les boucles implicites définies par les itérateurs each. Les entrées et sorties se font à l'aide des instructions gets, open, print, printf, putc, puts, readline, readlines et test mais en mode interactif on aime bien utiliser aussi p car plus court, et inspect car plus détaillé.

En plus des nombres, Ruby fournit des étendues et des plages de variation comme a..b et a...b (qui va de a à b-1) et des fonctions comme upto, downto et step. Les valeurs a et b dans les étendues peuvent être des nombres, des caractères et même des chaines. Ainsi "azz".next est "baa". On consultera la ruby doc de String pour plus de détails et aussi celle de range. Ruby implémente aussi la notion de symbole comme :a (qui n'est pas exactement "a") et de tranche (slice) de structure. Au passage, on notera que << permet d'ajouter des éléments à une structure :


     $gh> irb
     
     irb(main):001:0>  a = [1,8,3]
     => [1, 8, 3]
     
     irb(main):002:0> a << 5
     => [1, 8, 3, 5]
     
     irb(main):003:0> a << 3 << 6
     => [1, 8, 3, 5, 3, 6]
     
     irb(main):004:0> b = "oui "
     => "oui "
     
     irb(main):005:0> b << "ou non "
     => "oui ou non "
     

Dans la hiérarchie standard de Ruby 1.9, il y a entre autres les tableaux (array) qui utilisent les crochets, les tables de hachages (hash) dénotés par des accolades, les ensembles (sets), les expressions régulières (regexp) délimitées par / et les symboles (symbols) préfixés par le symbole :. La définition et la gestion de structures (chaines, tableaux) est facilitée par des fonctions comme %Q, %q, %x, %W, %w et %r. A propos des expressions régulières en Ruby, on pourra consulter le site rubular qui permet de les tester en ligne.

La programmation fonctionnelle en Ruby va au-delà des simples map, filter, reduce traditionnels. Les fonctions anonymes (lambda) et le passage du controle de l'exécution à des blocs via yield en font un langage puissant mais pas toujours facile à maitriser.

Ainsi l'expression ligne.split(/,/).map() {|val| Integer(val) }; vient découper la variable ligne et renvoie les deux mots de cette ligne convertis en entiers à l'aide de map et Integer() :


     $gh> irb
     
     irb(main):001:0> ligne =" 3 , 8 "
     => " 3 , 8 "
     
     irb(main):002:0> num1, num2 = ligne.split(/,/).map() {|val| Integer(val) }
     => [3, 8]
     
     irb(main):003:0> num1
     => 3
     
     irb(main):004:0> num2
     => 8
     
     irb(main):005:0> num3, num4 = ligne.split(/,/).map { |val| Integer(val) } # plus court
     => [3, 8]
     
     irb(main):006:0> num3, num4 = ligne.split(/,/).map(&:to_i) # encore plus court
     => [3, 8]
     

Il faut connaitre et apprendre à utiliser les *ect que sont collect, detect, inject, reject, ainsi que partition pour profiter pleinement des méthodes disponibles pour les structures qui font partie de la classe Enumerable :


      $gh) > irb
     
     irb(main):001:0> require "set"
     => true
     
     irb(main):002:0> a = Set.new
     => #<Set: {}>
     
     irb(main):003:0> b = Array.new
     => []
     
     irb(main):004:0> c = Hash.new
     => {}
     
     irb(main):005:0> [a,b,c].each { |x|  puts x.class.ancestors.to_s }
     [Set, Enumerable, Object, Kernel, BasicObject]
     [Array, Enumerable, Object, Kernel, BasicObject]
     [Hash, Enumerable, Object, Kernel, BasicObject]
     
     irb(main):006:0> d  = [8,12,6,3,5,2,12,7]
     => [8, 12, 6, 3, 5, 2, 12, 7]
     
     irb(main):010:0> d.max
     => 12
     
     irb(main):007:0> d.collect { |x| x*x }
     => [64, 144, 36, 9, 25, 4, 144, 49]
     
     irb(main):008:0> d.detect { |x| x%2==1 }
     => 3
     
     irb(main):009:0> d.select { |x| x%2==1 }
     => [3, 5, 7]
     
     irb(main):010:0> d.inject { |s,x| s+x }
     => 55
     
     irb(main):011:0> d.partition { |x| x<8 }
     => [[6, 3, 5, 2, 7], [8, 12, 12]]
     
     irb(main):012:0> d.reject { |x| x%2==1 }
     => [8, 12, 6, 2, 12]
     
     

La taille d'un objet s'obtient souvent via length, size ou count qui sont sans doute des synonymes. D'ailleurs Ruby dispose d'une méthode alias qui se révèle à l'usage très pratique quand on utilise simultanément plusieurs langages de programmation. Il y a aussi alias_method.

Pour connaitre la classe des objets, on peut utiliser class, kind_of?, instance_of?, superclass et ancestors déjà utilisé dans le programme précédent. Pour connaitre les méthodes associées aux objets, il y a des fonctions comme methods, public_methods... comme dans le programme suivant nommé ar.rb qui affiche les méthodes pour la classe Array :


     #  (gH)   -_-  ar.rb  ;  TimeStamp (unix) : 24 Décembre 2012 vers 19:31
     
     def souligne(x)
       puts x
       puts "-"*x.length
     end # fin de fonction souligne
     
     souligne("les ancetres de Array")
     puts Array.ancestors.to_a
     puts
     
     pum = Array.public_methods.sort
     souligne("les " + pum.length.to_s + " methodes \"public\" de Array")
     pum.each { |x| puts x }
     puts
     
     prim = Array.private_methods.sort
     souligne("les " + prim.length.to_s + " methodes \"private\" de Array")
     prim.each { |x| puts x }
     puts
     
     prom = Array.protected_methods.sort
     souligne("les " + prom.length.to_s + " methodes \"protected\" de Array")
     prom.each { |x| puts x }
     puts
     
     im = Array.instance_methods.sort
     souligne("les " + im.length.to_s + " \"instance\" methodes  de Array")
     im.each { |x| puts x }
     puts
     
     puts("bye")
     

Voici un extrait du résultat de son exécution, le fichier complet est ar_rb.txt :


     les ancetres de Array
     ---------------------
     Array
     Enumerable
     Object
     Kernel
     BasicObject
     
     les 94 methodes "public" de Array
     ---------------------------------
     !
     !=
     !~
     <
     <=
     <=>
     ==
     ===
     =~
     >
     >=
     []
     __id__
     __send__
     allocate
     ancestors
     autoload
     autoload?
     class
     class_eval
     class_exec
     class_variable_defined?
     class_variable_get
     class_variable_set
     class_variables
     clone
     const_defined?
     const_get
     const_missing
     const_set
     constants
     define_singleton_method
     display
     dup
     enum_for
     eql?
     equal?
     extend
     freeze
     frozen?
     [...]
     
     les 83 methodes "private" de Array
     ----------------------------------
     Array
     Complex
     Float
     Integer
     Rational
     String
     __callee__
     __method__
     `
     abort
     alias_method
     at_exit
     attr
     attr_accessor
     attr_reader
     attr_writer
     binding
     block_given?
     caller
     catch
     define_method
     eval
     exec
     exit
     exit!
     extended
     [...]
     
     les 0 methodes "protected" de Array
     -----------------------------------
     
     les 152 "instance" methodes  de Array
     -------------------------------------
     !
     !=
     !~
     &
     *
     +
     -
     <<
     <=>
     ==
     ===
     =~
     []
     []=
     __id__
     __send__
     all?
     any?
     assoc
     at
     class
     clear
     clone
     collect
     collect!
     combination
     compact
     compact!
     concat
     count
     cycle
     define_singleton_method
     delete
     delete_at
     delete_if
     detect
     display
     drop
     drop_while
     dup
     each
     each_cons
     each_index
     each_slice
     each_with_index
     each_with_object
     empty?
     entries
     enum_for
     eql?
     equal?
     extend
     fetch
     fill
     find
     find_all
     find_index
     first
     flatten
     flatten!
     [...]
     
     bye
     

La programmation objets en Ruby utilise l'instruction class qui définit (ou complète) une classe. Il est possible de modifier toutes les classes de Ruby, y compris les classes de base. Le symbole @ désigne une instance d'objet alors que @@ désigne une instance de classe. On utilise self pour désigner l'objet courant. Signalons au passage que $ définit une variable globale et qu'une variable dont l'initiale est majuscule définit une constante. Il n'y a pas d'héritage multiple en Ruby mais le notion de module permet de réaliser des mixins.

La gestion des erreurs et exceptions est assurée par begin/end avec rescue raise ensure else. Il est possible de définir des auto-tests élémentaires (comme en Python) lorsqu'on exécute directement le fichier avec une construction basée sur __FILE__ et $0 ; le module test/unit permet de définir des test unitaires. Voici par exemple ce qu'on peut faire avec le fichier iota1.rb


     # # (gH)   -_-  iota1.rb  ;  TimeStamp (unix) : 08 Janvier 2015 vers 11:18
     
     def iota(n=10)
       return( (1..n).to_a )
     end # fin de fonction iota
     
     # une version objets
     
     class Integer
        def iotan
            (1..self).to_a # pas de return (hum !)
        end # fin de fonction iotan
     end # fin de class
     
     # autotest et test unitaire
     
     if __FILE__ == $0 then
     
       puts("iota(n) renvoie un tableau des nombres de 1 a n ")
       puts(" exemple : iota(5) renvoie ")
       puts( iota(5) )
     
       require 'test/unit'
     
       class TestIota < Test::Unit::TestCase
     
          def test_simple
             assert_equal([1,2,3],iota(3))
             assert_equal([1,2,3],3.iotan)
          end # fin de fonction test_simple
     
       end # fin de classe TestIota
     
     end # fin de si fichier appelé directement
     
     

     iota(n) renvoie un tableau des nombres de 1 a n 
      exemple : iota(5) renvoie 
     1
     2
     3
     4
     5
     Run options: 
     
     # Running tests:
     
     .
     
     Finished tests in 0.000198s, 5056.7366 tests/s, 10113.4732 assertions/s.
     
     1 tests, 2 assertions, 0 failures, 0 errors, 0 skips
     

Si par contre on dit que iota(5) doit renvoyer [1,2,5] ce test échoue :


     # # (gH)   -_-  iota2.rb  ;  TimeStamp (unix) : 08 Janvier 2015 vers 11:19
     
     def iota(n=10)
       return( (1..n).to_a )
     end # fin de fonction iota
     
     # une version objets
     
     class Integer
        def iotan
            (1..self).to_a # pas de return (hum !)
        end # fin de fonction iotan
     end # fin de class
     
     # autotest et test unitaire
     
     if __FILE__ == $0 then
     
       require 'test/unit'
     
       class TestIota < Test::Unit::TestCase
     
          def test_simple
             assert_equal([1,2,5],iota(5))
             assert_equal([1,2,3],3.iotan)
          end # fin de fonction test_simple
     
       end # fin de classe TestIota
     
     end # fin de si fichier appelé directement
     
     

     Run options: 
     
     # Running tests:
     
     F
     
     Finished tests in 0.000365s, 2739.2307 tests/s, 2739.2307 assertions/s.
     
       1) Failure:
     test_simple(TestIota) [iota2.rb:24]:
     <[1, 2, 5]> expected but was
     <[1, 2, 3, 4, 5]>.
     
     1 tests, 1 assertions, 1 failures, 0 errors, 0 skips
     

Signalons enfin que les modules benchmark et profile permettent respectivement d'obtenir des durées d'exécutions et des profils d'exécution afin de pouvoir optimiser le code Ruby, de même que le module pp permet de mieux afficher les structures (pp est mis pour pretty print). On trouvait sur le site RAA de nombreux modules pour Ruby (plus de 1800 début décembre 2012). Le site ayant fermé en 2013, on se rabattra désormais sur the Ruby Toolbox et sur RubyGems pour obtenir des modules, packages et "gemmes" (96318 gemmes en janvier 2015).


     @ghchu~/public_html/tuteurs|(~gH) > irb
     
     irb(main):001:0> 2.class.ancestors
     => [Fixnum, Integer, Numeric, Comparable, Object, Kernel, BasicObject]
     
     irb(main):002:0> require 'pp'
     => true
     
     irb(main):003:0> pp 2.class.ancestors
     [Fixnum,
      Integer,
      Numeric,
      Comparable,
      Object,
      PP::ObjectMixin,
      Kernel,
      BasicObject]
     => [Fixnum, Integer, Numeric, Comparable, Object, PP::ObjectMixin, Kernel, BasicObject]
     

3. Nos exemples standards

Notre premier exemple demande un nom (ou un prénom) et le convertit en majuscules après avoir affiché la date et l'heure. Pour le formatage des dates et heures, on pourra consulter la page date-time-format-ruby. Nous avons utilisé gets.chomp() pour la lecture au clavier. On aurait pu écrire aussi gets.chomp ou STDIN.gets().chomp(). On notera aussi l'utilisation de if en modification, comme en Perl.

Fichier bonjour.rb :


     require "date"
     
     print "Bonjour.\n Quel est ton nom ? "
     pren = gets.chomp()
     pren = "Bel(le) inconnu(e)" if pren.length()==0
     puts "Le " + Time.now.strftime(" %d/%m/%Y vers %H h %M") + " au revoir, " + pren.upcase() + ". "
     

Le deuxième exemple vient lire une valeur en ligne de commande. Tant que ce n'est pas un entier, on redemande une valeur. Quand c'est un entier, on affiche sa table de multiplication. Le test qui détermine si une chaine de caractères est un entier doit utiliser une fonction ou une méthode. Une première solution peut être :

Fichier tdm.rb :


     # -----------------------------------------------------
     
     def entier?(chaine)
     
         # retourne vrai si la chaine correspond a un entier
         return( chaine =~ /^\d+$/ )
     
     end # fin de fonction entier?
     
     # -----------------------------------------------------
     
     if ARGV.length==0 then
        puts " syntaxe : ruby tdm.rb NOMBRE_ENTIER "
        exit -1
     end # fin de si
     
     nb = ARGV[0]
     while ! entier?(nb)
       puts " vous avez saisi " + nb + " qui n'est pas un entier. "
       print " redonner un entier : "
       nb = STDIN.gets.chomp  # ne pas oublier STDIN ici
     end # fin de tant que
     
     puts "Table de " + nb
     nb = nb.to_i
     (1..10).each { |i|
       puts sprintf("%2d fois %d = ",i,nb) + sprintf("%5d",nb*i)
     } # find de each
     

Il est également très simple de rajouter la méthode entier? à la classe des objets chaines de caractères de façon à utiliser une approche plus objet :

Fichier tdm2.rb :


     # -----------------------------------------------------
     
     class String
     
        def entier?
            # retourne vrai si la chaine correspond a un entier
            return( self =~ /^\d+$/ )
        end # fin de methode entier?
     
     end # fin de class String
     
     # -----------------------------------------------------
     
     if ARGV.length==0 then
        puts " syntaxe : ruby tdm2.rb NOMBRE_ENTIER "
        exit -1
     end # fin de si
     
     nb = ARGV[0]
     while ! nb.entier?
       puts " vous avez saisi " + nb + " qui n'est pas un entier. "
       print " redonner un entier : "
       nb = STDIN.gets.chomp  # ne pas oublier STDIN ici
     end # fin de tant que
     
     puts "Table de " + nb
     nb = nb.to_i
     (1..10).each { |i|
       puts sprintf("%2d fois %d = ",i,nb) + sprintf("%5d",nb*i)
     } # find de each
     

Voici un exemple d'exécution :


     $gh) > ruby tdm2.rb pomme
      vous avez saisi pomme qui n'est pas un entier.
      redonner un entier : oui
      vous avez saisi oui qui n'est pas un entier.
      redonner un entier : 6
     Table de 6
      1 fois 6 =     6
      2 fois 6 =    12
      3 fois 6 =    18
      4 fois 6 =    24
      5 fois 6 =    30
      6 fois 6 =    36
      7 fois 6 =    42
      8 fois 6 =    48
      9 fois 6 =    54
     10 fois 6 =    60
     

Pour le troisième exemple, il faut lire un fichier comme notes.txt où le nom est séparé des notes par le symbole étoile puis afficher les moyennes par ordre alphabétique puis par ordre de mérite. Voici une implémentation possible ; on teste bien sûr si le fichier existe avant de tenter de le lire et on ignore les lignes vides. La documention pour la classe des fichiers est ici.


     # on teste s'il y a un argument en ligne de commande
     
     if ARGV.length==0 then
        puts " syntaxe : ruby notes.rb FICHIER_DES_NOMS_ET_NOTES "
        exit -1
     end # fin de si
     
     # s'il y a un argument, on teste s'il correspond à un fichier qui existe
     
     fn = ARGV[0]
     if ! File.exists?(fn) then
        puts "Pas de chance, le fichier "+fn+" ne semble pas exister. Stop "
     end # fin de si
     
     # si c'est le cas, on parcourt le fichier pour stocker les noms,
     # et calculer les moyennes
     
     nblig = 0  # nombre de lignes
     nbelv = 0  # nombre d'élèves
     noms  = {} # tableau associatif  ; noms = Hash.new() est ok aussi
     
     File.readlines(fn).each do |ligne|
        ligne.strip!
        nblig += 1
        next if ligne.empty?          # pas la peine de continuer si la ligne est vide
        nbelv += 1                    # arrivé ici, on est sur de ne pas avoir une ligne vide
        nom,notes = ligne.split(/\*/) # on sépare nom et notes
        nom.strip!                    # on enlève les espaces dans noms
        notes.strip!                  # on enlève les espaces dans notes
        lnotes = notes.split(/\s+/).map { | chen | Float(chen) }  # on fait un tableau de notes
        moyenne = lnotes.reduce(:+).to_f / lnotes.length          # on calcule la moyenne
        noms[nom.to_s] = moyenne                                  # on remplit le tableau associatif
     end # fin de each
     
     puts "On a vu " + nbelv.to_s + " eleves sur " + nblig.to_s + " lignes. "
     
     # affichage trié par nom
     puts("Affichage alpha")
     noms.sort.each { |n,m| puts sprintf("   %-10s %5.2f",n,m) }
     
     # affichage trié par moyenne décroissante
     puts("Par ordre de merite")
     noms.sort_by { |k,v| v }.reverse.each { |n,m| puts sprintf("   %-10s %5.2f",n,m) }
     

On notera que la construction de bloc avec File ferme automatiquement le fichier. Dans le tri par moyenne décroissante, on ne gère pas (volontairement) les ex-aequo.

4. Compléments pour les bases de données, le Web et XML

Pour accéder aux bases de données de MySql en Ruby, on peut par exemple utiliser le module nommé... mysql ! Pour une utilisation plus soutenue, sqlalchemy est une meilleure solution, notamment pour sa gestion ORM. Voici un mini-exemple d'utilisation qui montre au passage l'utilisation de begin rescue end.


     #  fichier sql.rb
     
     require "mysql"
     
     puts "Ruby, version ", RUBY_VERSION
     
     begin
          # connect to the MySQL server
          dbh = Mysql.real_connect("localhost", "anonymous", "anonymous", "statdata")
          # get server version string and display it
          puts "Server version: " + dbh.get_server_info
     rescue Mysql::Error => e
          puts "Error code: #{e.errno}"
          puts "Error message: #{e.error}"
          puts "Error SQLSTATE: #{e.sqlstate}" if e.respond_to?("sqlstate")
          # disconnect from server
     end
     
     res = dbh.query("SELECT iden,age FROM elf LIMIT 3 ")
     res.each do |row|
          printf "%s, %s\n", row[0], row[1]
     end
     
     dbh.close if dbh
     puts "ok"
     

On produit le fichier suivant, résultat de l'exécution par la simple commande ruby sql.rb > sql_rb.txt.


     Ruby, version 
     1.9.1
     Server version: 5.1.66-0ubuntu0.10.04.3
     M001, 62
     M002, 60
     M003, 31
     ok
     

Pour lire un fichier distant avec Ruby aucun souci, puisqu'il y a les modules net/http et open-uri :


     # combien y a-t-il de jours en mai, via ndjpm ?
     # si tout est OK, ce programme affiche :
     #
     #    [ndjpm] il y a 31 jours pour le mois numero 5
     #
     
     require 'net/http'
     require 'open-uri'
     
     ndm = 5
     url = "http://forge.info.univ-angers.fr/~gh/internet/ndjpm.php?m=#{ndm}"
     
     open(url) do |f|
       contenu = f.readlines
       /comporte (\d+?) jours/ =~ contenu.to_s
       nbj = $1
       puts "[ndjpm] il y a " + nbj + " jours pour le mois numero #{ndm} "
     end # fin de open
     
     

De même, avec le module socket il est très simple de disposer d'un serveur Web élémentaire. On pourra également consulter la documentation des serveurs thin et webrick. Enfin, un détour par sinatra est incontournable pour comprendre la philosophie de Ruby.

Il existe plusieurs packages pour traiter du XML en Ruby mais dont l'installation n'est pas toujours facile. Voici un exemple nommé demo_xml.rb :


     require "rexml/document"
     include REXML
     
     require "xml/xslt" # voir https://github.com/glejeune/ruby-xslt/blob/master/README.rdoc
     
     fxml     = "test3.xml"
     fxsl     = "vide.xsl"
     
     puts "Noms des personnes"
     xmldct = Document.new File.new(fxml)
     xmldct.elements.each("//personne/nom") { |n| puts n.text }
     puts
     
     puts "Les services"
     XPath.each(xmldct, "//service") { |e| puts e.text }
     puts "Noms des personnes"
     
     puts "La transformation vide"
     xslt = XML::XSLT.new
     xslt.xml = fxml
     xslt.xsl = fxsl
     resultat = xslt.serve
     puts resultat
     

Et le résultat de son exécution, si les fichiers test3.xml et vide.xsl sont accessibles :


     Noms des personnes
     Dupond
     Durand
     Dupuis
     
     Les services
     Achats
     Achats
     Courrier
     Noms des personnes
     La transformation vide
     <?xml version="1.0" encoding="ISO-8859-1"?>
     
       
         Dupond
         Achats
       
       
         Durand
         Achats
       
       
         Dupuis
         Courrier
       
     
     

5. Forces et faiblesses du langage

Un des grands intérêts de Ruby est sa syntaxe qui permet de définir facilement des langages spécifiques (DSL). Voici un mini-exemple (ventes.rb) pour des ventes dans une kermesse où toutes les ventes de consommation sont à 1 euro :


     # encoding: ISO-8859-1
     
     #####################################################
     #
     # ventes.rb
     #
     #####################################################
     
     def debut
         $ventes = 0
     end # fin de fonction debut
     
     def vente(conso)
         $ventes += 1
     end # fin de fonction vente
     
     def recette
         puts "recette courante : #$ventes euros"
     end # fin de fonction recette
     
     def fin
         puts "Il y a eu #$ventes ventes, soit une recette finale de #$ventes euros."
     end # fin de fonction fin
     
     #####################################################
     
     debut
       vente "jus d'orange"
       vente "bière"
       vente "bière"
       vente "café"
       recette
       vente "café"
       vente "café"
     fin
     
     #####################################################
     #
     # à l'exécution, ce programme affiche :
     #
     #####################################################
     
     # recette courante : 4 euros
     # Il y a eu 6 ventes, soit une recette finale de 6 euros.
     
     
     
     

Les premières versions de Ruby étaient peu ou mal documentées et les modules disponibles aussi, ce qui a fait passer Ruby pour un langage peu évolué et mal conçu.

Si Ruby 1.9 est un successeur amélioré de Perl et de SmallTalk il reste lui aussi perfectible. Ainsi les sigils $, % @ de Perl sont évités mais Ruby implémente les sigils $, @ et @@. La notion de lambda et de bloc rend Ruby très puissant et presque complètement fonctionnel mais il n'est pas possible d'appliquer directement une fonction à une structure contrairement au langage R, par exemple où on peut écrire sapply(FUN=carre,X=1:10) pour calculer les carrés des nombres de 1 à 10 (il faut recourir à une méthode) :


     $gh > irb
     
     irb(main):001:0> def auCarre(x)
     irb(main):002:1>   return(x*x)
     irb(main):003:1> end # fin de fonction auCarre
     => nil
     
     irb(main):004:0> auCarre 5
     => 25
     
     irb(main):005:0> auCarre [3,8,2]
     TypeError: can't convert Array into Integer
        from (irb):2:in `*'
        from (irb):2:in `auCarre'
        from (irb):5
        from /usr/bin/irb:12:in `<main>'
     
     irb(main):006:0> [3,8,2].map() { |i| auCarre(i) }
     => [9, 64, 4]
     
     irb(main):007:0> [3,8,2].map(&:auCarre)
     NoMethodError: private method `auCarre' called for 3:Fixnum
        from (irb):7:in `map'
        from (irb):7
        from /usr/bin/irb:12:in `<main>'
     
     irb(main):008:0> class Fixnum
     irb(main):009:1>
     irb(main):010:1*    def carre
     irb(main):011:2>      return self*self
     irb(main):012:2>    end # fin de fonction carre
     irb(main):013:1>
     irb(main):014:1* end # fin de classe Fixnum
     => nil
     
     irb(main):015:0> carre 5
     NoMethodError: undefined method `carre' for main:Object
        from (irb):15
        from /usr/bin/irb:12:in `<main>'
     
     irb(main):016:0> 5.carre
     => 25
     
     irb(main):017:0> [3,8,2].map(&:carre)
     => [9, 64, 4]
     
     irb(main):018:0> def carre(x) # def globale
     irb(main):019:1>   return(x*x)
     irb(main):020:1> end # fin de fonction carre
     => nil
     
     irb(main):021:0> carre 5
     => 25
     
     

Enfin, Ruby est souvent plus connu pour le développement web via le framework ROR (Ruby on Rails) que comme langage "indépendant". Le nombre de packages distincts (ou "gems") est actuellement sans doute un peu inférieur à celui de son «rival» Python, ce qui n'enlève pourtant rien comme à ses qualités de langage complètement objets, réflexif et ouvert... en attendant les améliorations qu'apportera Ruby 2.0.

 

 

retour gH    Retour à la page principale de   (gH)