La gem file_exists: File.exists? e Dir.exists? su Ruby 3.2+
Ruby 3.2 ha rimosso File.exists? e Dir.exists?. Entrambi erano deprecati da anni a favore di File.exist? e Dir.exist? (senza la “s”), ma molto codice meno recente li usava ancora. Dopo l’aggiornamento quel codice fallisce con:
undefined method `exists?' for File:Class (NoMethodError)
Ne ho scritto subito dopo l’uscita di Ruby 3.2 in File.exists? and Dir.exists? on Ruby 3.2 Workaround (in inglese). Questo articolo è una panoramica aggiornata della piccola gem nata da lì: file_exists.
Cosa fa
La gem aggiunge di nuovo File.exists? e Dir.exists? come alias di File.exist? e Dir.exist?. Tutto qui. L’intera libreria è di poche righe:
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
Dalla versione 0.2.0 l’alias viene aggiunto solo se il metodo manca davvero, quindi la gem non fa nulla sulle versioni più vecchie di Ruby che hanno ancora exists?. Il gemspec ammette Ruby 2.0 e successivi. Non ha dipendenze ed è sotto licenza MIT.
Perché esiste
La soluzione giusta è sempre rinominare la chiamata. A volte però la chiamata non è nel vostro codice: si trova in una gem da cui dipendete, e quella gem non è più mantenuta, oppure la correzione è stata integrata ma non ancora pubblicata. È esattamente ciò che mi è successo con openssl-win-root. In una situazione simile volete che l’applicazione continui a girare sulla nuova versione di Ruby mentre aspettate la correzione vera e propria a monte (o ci lavorate voi stessi). È questa la lacuna che colma file_exists.
Secondo RubyGems è stata scaricata più di 760’000 volte, quindi non ero certo l’unico con questo problema.
Come usarla
Aggiungetela al Gemfile:
bundle add file_exists
Oppure installatela direttamente:
gem install file_exists
Poi caricatela con require il prima possibile, prima di qualsiasi codice che chiami File.exists? o Dir.exists?:
require 'file_exists'
File.exists?("Filename.txt") # => stesso risultato di File.exist?
Dir.exists?("Dirname") # => stesso risultato di Dir.exist?
L’ordine conta. Se un’altra gem chiama File.exists? mentre viene caricata, l’alias deve esistere prima di quel require. In un’app Rails un buon punto è config/boot.rb, subito dopo require "bundler/setup".
Quando conviene correggere il codice
Se la chiamata si trova in codice che controllate voi, non usate questa gem. Rinominatela e basta:
File.exists?diventaFile.exist?Dir.exists?diventaDir.exist?
File.exist? e Dir.exist? funzionano su tutte le versioni di Ruby che probabilmente utilizzate, quindi la modifica è sicura. Per trovare le chiamate rimaste:
grep -rnE "(File|Dir)\.exists\?" --include="*.rb" .
Se usate RuboCop, anche il cop Lint/DeprecatedClassMethods segnala queste chiamate.
Per le gem di terzi verificate prima se una versione più recente risolve già il problema. In caso contrario aprite una issue o una pull request a monte. Usate file_exists come ponte fino alla pubblicazione della correzione, non come parte permanente del vostro stack. Modifica le classi core per l’intero processo e, quando nulla ha più bisogno dei vecchi nomi, è una dipendenza in meno da rimuovere.
Una correzione al mio vecchio articolo: il monkeypatch scritto a mano che vi mostravo originariamente chiamava exist? senza passare il nome del file. Ora l’ho corretto. Se preferite copiare qualcosa invece di installare la gem, la versione con alias_method qui sopra è la scelta migliore: è ciò che la gem distribuisce davvero.