Filtrar archivos en Ruby: una guía práctica
El filtrado eficiente de archivos es esencial para todo, desde el procesamiento de logs hasta la gestión de medios. Ruby ofrece herramientas sólidas de selección de archivos que van más allá de la simple coincidencia de patrones. Exploremos técnicas prácticas que escalan desde escenarios básicos hasta avanzados.
Requisitos
Esta guía requiere Ruby 2.5 o posterior para Dir.children. Los ejemplos
presentados usan Ruby 3.2.3 y mime-types 3.6.0. Para la detección de tipos MIME, necesitarás la gema
mime-types:
# Add to your Gemfile
gem 'mime-types', '3.6.0'
O instala la misma versión directamente:
gem install mime-types -v 3.6.0
La gema mime-types emplea un versionado semántico modificado para rastrear tanto los cambios de la API como las actualizaciones de los datos del registro. Para más detalles, consulta la documentación de mime-types.
Métodos de filtrado principales
La biblioteca estándar de Ruby ofrece varias soluciones inmediatas para filtrar archivos:
# Filter by extension
docs_dir = '/docs'
pdf_files = Dir.children(docs_dir).select do |f|
File.file?(File.join(docs_dir, f)) && File.extname(f) == '.pdf'
end
# Filter by size (1MB threshold)
large_files = Dir.glob('*').select do |f|
begin
File.file?(f) && File.size(f) > 1_000_000
rescue Errno::ENOENT, Errno::EACCES => e
warn "Error accessing #{f}: #{e.message}"
false
end
end
# Filter by modification time (last 24 hours)
recent_files = Dir.glob('*').select do |f|
begin
File.file?(f) && File.mtime(f) > (Time.now - 86400)
rescue Errno::ENOENT, Errno::EACCES => e
warn "Error accessing #{f}: #{e.message}"
false
end
end
Estos métodos usan las utilidades de la clase File para comprobaciones
rápidas sin cargar el contenido de los archivos.
Coincidencia de patrones avanzada con glob
Dir.glob de Ruby admite coincidencia de patrones al estilo UNIX con algunas
mejoras específicas de Ruby:
# Match nested Markdown files
markdown_files = Dir.glob('**/*.md').select { |f| File.file?(f) }
# Match names containing a January 2024 date (not modification times)
jan_files = Dir.glob('*').grep(/(2024-01-\d{2})/).select { |f| File.file?(f) }
# Combined size and type filter
big_images = Dir.glob('*.{jpg,png}').select do |f|
begin
File.file?(f) && File.size(f) > 500_000
rescue Errno::ENOENT, Errno::EACCES => e
warn "Error accessing #{f}: #{e.message}"
false
end
end
Usa el doble asterisco (**) para recorrer directorios de forma recursiva
y la expansión de llaves para múltiples extensiones.
Búsqueda de tipos MIME
Usa la gema mime-types para buscar el tipo de medio asociado a una extensión de
nombre de archivo:
require 'mime/types'
def media_files(dir)
Dir.children(dir).select do |f|
begin
next unless File.file?(File.join(dir, f))
mime = MIME::Types.type_for(f).first
mime&.media_type == 'image' || mime&.media_type == 'video'
rescue StandardError => e
warn "Error processing #{f}: #{e.message}"
false
end
end
end
# Usage:
visual_assets = media_files('/content/assets')
Este enfoque usa un registro de extensiones; no inspecciona el contenido de los archivos ni valida que un archivo sea seguro. Usa la inspección de contenido por separado cuando aceptes subidas no confiables. Para más detalles, consulta la documentación de la gema mime-types.
Filtrado por metadatos
Combina varios datos de metadatos para lograr una selección precisa:
def recent_documents(path)
Dir.glob("#{path}/*").select do |f|
begin
next unless File.file?(f)
ext = File.extname(f).downcase
size = File.size(f)
modified = File.mtime(f)
(ext == '.pdf' || ext == '.docx') &&
size.between?(10_000, 5_000_000) &&
modified > (Time.now - 7*86400)
rescue StandardError => e
warn "Error processing #{f}: #{e.message}"
false
end
end
end
Esto selecciona archivos PDF/DOCX modificados en la última semana que pesan entre 10 KB y 5 MB.
Consideraciones de rendimiento
Al procesar directorios grandes:
-
Evaluación perezosa: aplaza las comprobaciones de metadatos con
lazy.Dir.globsigue construyendo su array de resultados.Dir.glob('**/*').lazy .select { |f| File.file?(f) && File.size(f) > 1_000_000 } .first(10) -
Salida temprana: falla rápido con
breakcuando sea posiblelog_dir = '/logs' Dir.children(log_dir).each do |name| f = File.join(log_dir, name) begin next unless File.file?(f) && f.end_with?('.log') break if File.size(f) > 1_000_000_000 # Stop at first huge log process_log(f) rescue StandardError => e warn "Error processing #{f}: #{e.message}" end endDefine
process_log(path)para el procesamiento de tu aplicación antes de ejecutar este fragmento. -
Caché de metadatos: guarda los datos a los que accedes con frecuencia
file_cache = {} Dir.glob('*').each do |f| begin file_cache[f] = { mtime: File.mtime(f), size: File.size(f) } rescue StandardError => e warn "Error caching #{f}: #{e.message}" end end
Ejemplo listo para producción
Aquí tienes un módulo de filtrado completo con manejo de errores:
require 'mime/types'
class FileFilter
def initialize(root_dir)
@root = root_dir
end
def find_files(extensions: [], min_size: 0, max_age: Float::INFINITY)
Dir.glob(File.join(@root, '**', '*')).lazy.select do |path|
begin
next unless File.file?(path)
valid_extension = extensions.empty? || extensions.include?(File.extname(path))
valid_size = File.size(path) >= min_size
valid_age = (Time.now - File.mtime(path)) < max_age
valid_extension && valid_size && valid_age
rescue StandardError => e
warn "Error processing #{path}: #{e.message}"
false
end
end
end
end
# Usage:
filter = FileFilter.new('/user/uploads')
recent_images = filter.find_files(
extensions: ['.jpg', '.png'],
min_size: 100_000,
max_age: 3600 # 1 hour
).first(100)
Estos ejemplos ilustran formas prácticas de filtrar archivos en Ruby con un manejo de errores sólido. Si buscas soluciones de procesamiento de archivos más avanzadas, considera explorar la API de Transloadit. Visita nuestra documentación para más detalles.
Manejo de codificaciones de archivos
Por lo general, Ruby maneja automáticamente la codificación de los nombres de archivo cuando se usa UTF-8. Sin embargo, si tienes problemas con caracteres no ASCII en los nombres de archivo, crea un valor de visualización en UTF-8 de la siguiente manera:
# Convert from Ruby's known source encoding and replace invalid bytes for display
utf8_labels = Dir.glob('*').map do |f|
f.encode('UTF-8', invalid: :replace, undef: :replace).scrub
end
Conserva la ruta original para las operaciones del sistema de archivos. Los caracteres de reemplazo
pueden cambiar un nombre de archivo; estas etiquetas son solo para mostrar.
force_encoding únicamente reetiqueta los bytes y no transcodifica.
Conclusión
A lo largo de esta guía exploramos técnicas prácticas para filtrar archivos en Ruby, desde simples comprobaciones de extensión y patrones glob hasta la detección de tipos MIME y el filtrado por metadatos. Al combinar estos métodos, puedes crear pipelines eficientes de procesamiento de archivos adaptados a tus necesidades. Para soluciones a gran escala y listas para producción, considera explorar la sólida API de procesamiento de archivos que ofrece Transloadit. Visita nuestra documentación para saber más.
