Quoi de neuf dans Ruby 2.3 ?

Un an a passé depuis la sortie de Ruby 2.2. C’est maintenant au tour de Ruby 2.3 de montrer le bout de son nez. Heureusement, pour les fêtes, cette nouvelle version n’arrive pas les mains vides !

Nouveaux opérateurs, nouvelles méthodes et un nouveau pragma pour apporter un peu de givre durant cet hiver bien chaud.

Opérateurs et literals

Présente depuis un certain temps dans Ruby on Rails, la méthode Object#try! devrait bientôt devenir obsolète. En effet, Ruby se voit pousser un nouvel opérateur : &..

Ce dernier permet de chainer directement des appels de méthodes sans risquer une explosion due à un objet valant nil en cours de route.

# Ruby 2.2
article && article.author && article.author.name

# Rails
article.try!(:author).try!(:name)

# Ruby 2.3
article&.author&.name

Malgré cette ressemblance avec try!, quelques subtiles différences sont à prendre en compte si vous souhaitez mettre votre code à jour.

&. oblige à passer un nom de méthode :

# try!
article.try! do |art|
  # appelé si article n'est pas nil
end

# &.
article&. do |art|
  # erreur de syntaxe
end

Les arguments de la fonction passée sont évalués uniquement si cette fonction est appelée :

# try!
# hottest_tag() est toujours évalué, qu'article soit nil ou non
article.try!(:has_tag?, hottest_tag())

# &.
# hottest_tag() n'est évalué que lorsque article existe et répond à has_tag?
article&.has_tag?(hottest_tag())

L’assignation de valeurs est possible via cet opérateur :

# try!
article.try!(:upvotes=, article.try!(:upvotes).to_i + 1)

# &.
article&.upvotes += 1

Comparaison de Hash

La classe Hash se voit dotée d’une capacité de comparaison. Il devient possible de déterminer si un Hash est inclus dans un autre :

{ a: 1, b: 2, c: 3 }  >   { a: 1, b: 2 }       # => true
{ a: 1, b: 2, c: 3 }  >   { a: 1, b: 2, c: 3 } # => false
{ a: 1, b: 2, c: 3 }  >   { a: 1, b: 2, d: 3 } # => false

{ a: 1, b: 2, c: 3 }  >=  { a: 1, b: 2 }       # => true
{ a: 1, b: 2, c: 3 }  >=  { a: 1, b: 2, c: 3 } # => true
{ a: 1, b: 2, c: 3 }  >=  { a: 1, b: 2, d: 3 } # => false

{ a: 1, b: 2, c: 3 }  <   { a: 1, b: 2 }       # => false
{ a: 1, b: 2, c: 3 }  <   { a: 1, b: 2, c: 3 } # => false

{ a: 1, b: 2, c: 3 }  <=  { a: 1, b: 2 }       # => false
{ a: 1, b: 2, c: 3 }  <=  { a: 1, b: 2, c: 3 } # => true

Des heredocs propres avec <<~

Également présente dans Rails depuis un moment, la méthode String#strip_heredoc se voit détrônée au profit d’une nouvelle syntaxe pour les heredocs : <<~.

Jusqu’à maintenant, il n’y avait que quelques solutions pour gérer les chaines de caractères multilignes de façon simple. La syntaxe suivante utilise ce que l’on appelle les here documents ou heredocs :

class Person
  def say_hello
    message = <<END
Hello,

Delighted to meet you!
END
    puts message
  end

  def say_hello_again
    message = <<-END
      Hello again,

      Delighted to meet you again!
    END
    puts message
  end
end

bob = Person.new

bob.say_hello
# >> Hello,
# >>
# >> Delighted to meet you!

bob.say_hello_again
# >>       Hello again,
# >>
# >>       Delighted to meet you again!

Le premier exemple fonctionne correctement mais le code est plutôt laid. Le deuxième exemple se présente mieux mais, malheureusement, le texte en sortie contient les espaces utilisés pour aligner notre texte dans le code.

C’est à ce besoin que répondait strip_heredoc :

class Person
  def say_hello_again
    message = <<-END
      Hello again,

      Delighted to meet you again!
    END
    puts message.strip_heredoc
  end
end

bob = Person.new
bob.say_hello_again
# >> Hello again,
# >>
# >> Delighted to meet you again!

Avec Ruby 2.3, plus besoin de faire appel à l’ami ActiveSupport ou de créer sa propre version de strip_heredoc. Il nous suffit de remplacer <<- par <<~ et le tour est joué :

class Person
  def say_hello_again
    message = <<~END
      Hello again,

      Delighted to meet you again!
    END
    puts message
  end
end

bob = Person.new
bob.say_hello_again
# >> Hello again,
# >>
# >> Delighted to meet you again!

Des chaines de caractères « gelées »

Il est maintenant possible d’indiquer que l’on souhaite « geler » les chaines de caractères par défaut.

Dans un programme Ruby classique, il est tout à fait possible de modifier une chaine de caractère :

str = "Bob"
str << " is cool"
str # => "Bob is cool"

On peut cependant empêcher cela en appelant freeze sur notre chaine :

str = "Bob".freeze
str << " is cool"
# ~> can't modify frozen String (RuntimeError)

Empêcher la modification d’une chaine de caractères a plusieurs cas d’usage. Cela permet de sécuriser une chaine passée à une autre partie du code, de rendre une constante vraiment constante ou tout simplement de réduire le nombre d’allocations mémoire et ainsi améliorer les performances.

Grâce à Ruby 2.3, il devient possible de faire en sorte que toutes les chaines soient rendues immuables par défaut.

Cela peut se faire par le biais d’un commentaire magique :

#frozen-string-literal: true

str = "Bob"
str << " is cool"
# ~> can't modify frozen String (RuntimeError)

Ou par l’utilisation d’un flag passé en ligne de commande :

# Pour activer l'option et geler les chaines
ruby --enable=frozen-string-literal my_script.rb

# Pour desactiver l'option et permettre la modification deschaines
ruby --disable=frozen-string-literal my_script.rb

Le message d’erreur informant qu’une tentative de modification de chaine gelée a eu lieu indique seulement où cette tentative a eu lieu. Il n’indique cependant pas l’origine de la chaine elle-même.

Pour aider avec ce problème, l’utilisation du flag --debug ou de --debug=frozen-string-literal permet d’obtenir plus d’informations.

Un peu d’aide n’est pas de refus

À l’instar de bigdecimal ou rake, la gem did_you_mean est maintenant installée par défaut avec Ruby. Celle-ci ajoute un peu de contenu aux messages d’erreur afin d’aider les développeurs à comprendre d’où peut venir l’erreur en question :

"hello world".uppcase
# ~> undefined method `uppcase' for "hello world":String (NoMethodError)
# ~> Did you mean?  upcase
# ~>                upcase!

Une hotte pleine de nouvelles méthodes !

Ruby 2.3 vient avec son lot de nouvelles méthodes, chacune utile dans certains contextes. Certaines méthodes sont cependant utiles dans une majorité de cas.

Array#dig et Hash#dig

Récupérer une valeur au fin fond d’un tableau est généralement une tâche un peu périlleuse et résulte le plus souvent en un code quelque peu barbar :

arr = [1, [2, 3, [4]]]

arr[1][2][0] # => 4

arr[1][3][0]
# ~> undefined method `[]' for nil:NilClass (NoMethodError)

arr[1] && arr[1][3] && arr[1][3][0] # => nil

Grâce à Array#dig il est maintenant possible de fouiller un tableau avec un peu moins d’appréhension :

arr = [1, [2, 3, [4]]]

arr.dig(1, 2, 0) # => 4
arr.dig(1, 3, 0) # => nil

Attention ! Petit piège à éviter : dans le cas où l’un des éléments de la chaine n’est pas un tableau, l’explosion reste possible :

arr = [1, [2, 3, [4]]]

arr.dig(1, 1) # => 3

arr.dig(1, 1, 0)
# ~> `dig': Fixnum does not have #dig method (TypeError)

De la même façon, la méthode dig sur un Hash permet de récupérer une valeur imbriquée dans celui-ci :

hash = { article: { author: { name: "Bob" } } }

hash.dig(:article, :author, :name) # => "Bob"
hash.dig(:article, :writer, :name) # => nil

Même piège que pour Array#dig, prudence dans le cas où une valeur n’est pas un Hash.

Par ailleurs, il est tout à fait possible de mixer l’utilisation de ces deux méthodes :

hash = { article: { tags: ['ruby', 'cool'] } }

hash.dig(:article, :tags, 1) # => "cool"

À noter que cette méthode a également été ajoutée à Struct.

Un bout de Hash

Si l’on souhaite récupérer une liste de valeurs dans un hash à partir de leur clés, on se retrouve souvent à jouer avec des appels à map ou d’autres pirouettes.

La nouvelle méthode Hash#fetch_values a été ajoutée spécialement pour ce besoin :

hash = { coffee: "black", milk: "white", tomato: "red" }

hash.fetch_values(:coffee, :tomato) # => ["black", "red"]
hash.fetch_values(:coffee, :banana)
# ~> `fetch_values': key not found: :banana (KeyError)

Tout comme Hash#fetch il est possible de passer un block pour le cas où la clé n’est pas trouvée :

hash = { coffee: "black", milk: "white", tomato: "red" }

hash.fetch_values(:coffee, :banana) { |key| "unknown #{key}" }
# => ["black", "unknown banana"]

Savoir rester positif

On peut maintenant interroger un nombre pour savoir s’il est positif ou négatif :

1.positive?  # => true
-1.positive? # => false

1.negative?  # => false
-1.negative? # => true

[1, 2, 3].all?(&:positive?)  # => true
[1, -2, 3].all?(&:positive?) # => false

Et encore plein d’autres choses !

Optimisations, correctifs, nouvelles méthodes, …

Ruby 2.3 ne s’arrête pas là et apporte bon nombre de fonctionnalités à un ensemble de classes ou modules comme IO, Enumerable (en particulier #grep_v), etc.

Pour en savoir plus, consultez les NEWS ou le Changelog.

Et vous ? Quelles sont vos nouveautés favorites ?

Publié le 27 décembre 2015

Notre vision des choses vous correspond ? Vous avez envie de travailler avec nous ?