Das Gem file_exists: File.exists? und Dir.exists? unter Ruby 3.2+
Mit Ruby 3.2 wurden File.exists? und Dir.exists? entfernt. Beide Methoden galten schon seit Jahren als veraltet, zugunsten von File.exist? und Dir.exist? (ohne «s»). Trotzdem steckten sie noch in viel älterem Code. Nach dem Upgrade bricht dieser Code mit folgender Meldung ab:
undefined method `exists?' for File:Class (NoMethodError)
Kurz nach dem Erscheinen von Ruby 3.2 habe ich darüber in File.exists? and Dir.exists? on Ruby 3.2 Workaround (Englisch) geschrieben. Dieser Beitrag ist eine aktuelle Übersicht über das kleine Gem, das daraus entstanden ist: file_exists.
Was es macht
Das Gem fügt File.exists? und Dir.exists? wieder hinzu, und zwar als Aliase von File.exist? und Dir.exist?. Mehr nicht. Die ganze Bibliothek besteht aus ein paar Zeilen:
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
Seit Version 0.2.0 wird der Alias nur angelegt, wenn die Methode tatsächlich fehlt. Auf älteren Ruby-Versionen, die exists? noch kennen, tut das Gem also nichts. Laut Gemspec läuft es ab Ruby 2.0. Es hat keine Abhängigkeiten und steht unter der MIT-Lizenz.
Warum es das Gem gibt
Die richtige Lösung ist immer, den Aufruf umzubenennen. Manchmal steht der Aufruf aber nicht in Ihrem eigenen Code, sondern in einem Gem, von dem Sie abhängen. Und dieses Gem wird nicht mehr gepflegt, oder die Korrektur ist zwar gemergt, aber noch nicht veröffentlicht. Genau das ist mir mit openssl-win-root passiert. In so einer Situation soll die Anwendung auf der neuen Ruby-Version weiterlaufen, während Sie auf die eigentliche Korrektur warten oder selbst daran arbeiten. Diese Lücke füllt file_exists.
Laut RubyGems wurde es über 760’000 Mal heruntergeladen. Ich war mit diesem Problem also offensichtlich nicht allein.
So verwenden Sie es
Fügen Sie es dem Gemfile hinzu:
bundle add file_exists
Oder installieren Sie es direkt:
gem install file_exists
Laden Sie es danach so früh wie möglich, bevor Code geladen wird, der File.exists? oder Dir.exists? aufruft:
require 'file_exists'
File.exists?("Filename.txt") # => gleiches Ergebnis wie File.exist?
Dir.exists?("Dirname") # => gleiches Ergebnis wie Dir.exist?
Die Reihenfolge ist wichtig. Ruft ein anderes Gem File.exists? bereits beim Laden auf, muss der Alias vor dessen require existieren. In einer Rails-Anwendung eignet sich dafür config/boot.rb, direkt nach require "bundler/setup".
Wann Sie besser Ihren Code anpassen
Wenn der Aufruf in Code steht, den Sie selbst kontrollieren, brauchen Sie dieses Gem nicht. Benennen Sie ihn einfach um:
- aus
File.exists?wirdFile.exist? - aus
Dir.exists?wirdDir.exist?
File.exist? und Dir.exist? funktionieren auf jeder Ruby-Version, die Sie realistischerweise einsetzen. Die Änderung ist also gefahrlos. So finden Sie die verbleibenden Aufrufe:
grep -rnE "(File|Dir)\.exists\?" --include="*.rb" .
Falls Sie RuboCop verwenden: Der Cop Lint/DeprecatedClassMethods meldet diese Aufrufe ebenfalls.
Bei Gems von Dritten prüfen Sie zuerst, ob eine neuere Version das Problem schon behebt. Falls nicht, eröffnen Sie ein Issue oder einen Pull Request beim Projekt. Setzen Sie file_exists als Überbrückung ein, bis die Korrektur veröffentlicht ist, und nicht als festen Bestandteil Ihres Projekts. Das Gem verändert Kernklassen für den ganzen Prozess, und sobald niemand mehr die alten Namen braucht, ist es eine Abhängigkeit weniger.
Eine Korrektur zu meinem alten Beitrag: Der dort gezeigte, von Hand geschriebene Monkeypatch rief exist? ursprünglich auf, ohne den Dateinamen weiterzugeben. Das habe ich inzwischen korrigiert. Wenn Sie lieber Code kopieren statt das Gem zu installieren, ist die alias_method-Variante oben die bessere Wahl: Genau diese liefert das Gem aus.