La gem file_exists : File.exists? et Dir.exists? sous Ruby 3.2+
Ruby 3.2 a supprimé File.exists? et Dir.exists?. Les deux étaient dépréciées depuis des années au profit de File.exist? et Dir.exist? (sans le « s »), mais beaucoup de code ancien les utilisait encore. Après la mise à jour, ce code échoue avec :
undefined method `exists?' for File:Class (NoMethodError)
J’en ai parlé juste après la sortie de Ruby 3.2 dans File.exists? and Dir.exists? on Ruby 3.2 Workaround (en anglais). Cet article est un état des lieux actuel de la petite gem qui en est issue : file_exists.
Ce qu’elle fait
La gem rajoute File.exists? et Dir.exists? comme alias de File.exist? et Dir.exist?. C’est tout. La bibliothèque entière tient en quelques lignes :
unless Dir.respond_to?(:exists?)
class << Dir
alias_method :exists?, :exist?
end
end
unless File.respond_to?(:exists?)
class << File
alias_method :exists?, :exist?
end
end
Depuis la version 0.2.0, l’alias n’est ajouté que si la méthode manque réellement ; la gem ne fait donc rien sur les anciennes versions de Ruby qui ont encore exists?. Le gemspec accepte Ruby 2.0 et plus récent. Elle n’a aucune dépendance et est sous licence MIT.
Pourquoi elle existe
La bonne correction consiste toujours à renommer l’appel. Mais parfois l’appel n’est pas dans votre code : il se trouve dans une gem dont vous dépendez, et cette gem n’est plus maintenue, ou la correction est fusionnée mais pas encore publiée. C’est exactement ce qui m’est arrivé avec openssl-win-root. Dans ce cas, vous voulez que votre application continue de tourner sur la nouvelle version de Ruby pendant que vous attendez la vraie correction en amont (ou que vous y travaillez). C’est ce vide que comble file_exists.
Selon RubyGems, elle a été téléchargée plus de 760 000 fois ; je n’étais donc clairement pas le seul à avoir ce problème.
Comment l’utiliser
Ajoutez-la à votre Gemfile :
bundle add file_exists
Ou installez-la directement :
gem install file_exists
Puis chargez-la le plus tôt possible, avant tout code qui appelle File.exists? ou Dir.exists? :
require 'file_exists'
File.exists?("Filename.txt") # => même résultat que File.exist?
Dir.exists?("Dirname") # => même résultat que Dir.exist?
L’ordre compte. Si une autre gem appelle File.exists? pendant son chargement, l’alias doit être en place avant ce require. Dans une application Rails, un bon endroit est config/boot.rb, juste après require "bundler/setup".
Quand corriger plutôt votre code
Si l’appel se trouve dans du code que vous maîtrisez, n’utilisez pas cette gem. Renommez-le simplement :
File.exists?devientFile.exist?Dir.exists?devientDir.exist?
File.exist? et Dir.exist? fonctionnent sur toutes les versions de Ruby que vous êtes susceptible d’utiliser ; le changement est donc sans risque. Pour trouver les appels restants :
grep -rnE "(File|Dir)\.exists\?" --include="*.rb" .
Si vous utilisez RuboCop, le cop Lint/DeprecatedClassMethods signale aussi ces appels.
Pour les gems tierces, vérifiez d’abord si une version plus récente corrige déjà le problème. Sinon, ouvrez une issue ou une pull request en amont. Utilisez file_exists comme passerelle jusqu’à la publication de cette correction, pas comme élément permanent de votre stack. Elle modifie des classes du noyau pour tout le processus, et dès que plus rien n’a besoin des anciens noms, c’est une dépendance de moins.
Une correction à mon ancien article : le monkeypatch écrit à la main qui y figurait appelait à l’origine exist? sans transmettre le nom de fichier. Je l’ai maintenant corrigé. Si vous préférez copier du code plutôt qu’installer la gem, la version avec alias_method ci-dessus est le meilleur choix : c’est ce que la gem livre réellement.