2011-08-03 17 views
18

Antes de entrar en detalles voy directamente al grano: alguien ha descubierto una forma de hacer que Carrierwave guarde los archivos con sus nombres como marca de tiempo o arbitraria cadena que es única para cada archivo?CarrierWave: cree el mismo nombre de archivo único para todos los archivos versionados

De forma predeterminada, Carrierwave guarda cada archivo y sus versiones alternativas en su propio directorio (con el nombre del número de ID del modelo). No soy fanático de esto porque en vez de un directorio con 1,000, para usar un número redondo grande, archivos (en mi caso fotos) en él obtenemos un directorio con 1,000 subdirectorios cada uno con uno o dos archivos. Yuck.

Ahora, cuando se reemplaza el método de su subida por store_dir a ser algo así como lo siguiente:

def store_dir 
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}" 
end 

se termina con el comportamiento exacto que quiero. Todos los archivos (imágenes) entran en una gran carpeta feliz. No más subcarpetas que se quedan cuando el objeto se elimina.

Solo hay un problema. Colisiones de archivos. Si subes delicious_cake.jpg dos veces, el segundo sobrescribirá el primero incluso si son dos imágenes diferentes de delicioso pastel. Es claro por qué el método store_dir tiene el /#{model.id} adicional marcado al final del valor que devuelve.

Entonces, ¿qué hacer? Después de leer un poco, descubrí que en el archivo de carga generado hay una solución aparente comentada.

# Override the filename of the uploaded files: 
# Avoid using model.id or version_name here, see uploader/store.rb for details. 
# def filename 
# "something.jpg" if original_filename 
# end 

Después de un poco de búsqueda me encontré con alguien que había hecho el siguiente

def filename 
    @name ||= "#{secure_token}.#{file.extension}" if original_filename 
end 

Esto me hizo pensar, ¿por qué no hacer esto

def filename 
    @name ||= "#{(Time.now.to_i.to_s + Time.now.usec.to_s).ljust(16, '0')}#{File.extname(original_filename)}" 
end 

que es cuando las cosas se pusieron terriblemente roto. El problema con esto es que aparentemente se llama al filename para cada versión del archivo, así que terminamos con nombres de archivo como 1312335603175322.jpg y thumb_1312335603195323.jpg. Observe la pequeña diferencia? Cada nombre de archivo se basa en el momento en que se llamó al filename para esa versión en particular. Eso no funcionará en absoluto.

A continuación me cansé de usar model.created_at para la base de la marca de tiempo. Solo un problema, que devuelve nil para la primera versión ya que aún no se ha puesto en la base de datos.

Después de pensar un poco más, decidí probar lo siguiente en el controlador de mi imagen.

def create 
    if params[:picture] and params[:picture][:image] 
    params[:picture][:image].original_filename = "#{(Time.now.to_i.to_s + Time.now.usec.to_s).ljust(16, '0')}#{File.extname(params[:picture][:image].original_filename)}" 
    end 
    ... 

Esto reemplaza la propiedad original_filename antes Carrierwave incluso llega a ella por lo que es ser una marca de tiempo. Hace exactamente lo que quiero. La versión original del archivo termina con un nombre como 1312332906940106.jpg y la versión en miniatura (o cualquier otra versión) termina con un nombre como thumb_1312332906940106.jpg.

Pero, esto parece un hack horrible. Esto debería ser parte del modelo, o mejor aún, parte del cargador montado en el modelo.

Entonces, mi pregunta es, ¿hay una mejor manera de lograr esto? ¿Extrañé algo crucial con Carrierwave que hace esto fácil? ¿Hay una manera no tan obvia pero más limpia de resolver esto? El código de trabajo es bueno, pero el código de trabajo que no huele mal es mejor.

+1

Me alegra ver que alguien más aprueba poner todas las imágenes en una gran carpeta feliz. Me alejé de Paperclip debido a su subdirectorio sin sentido. Un nombre de archivo único es todo lo que necesitas. Una computadora no necesita directorios para organizarse, son muy buenos para encontrar cosas. – Starkers

Respuesta

20

Puede hacer algo como esto en su archivo uploader, y también funcionará para archivos versionados (es decir, si tiene una imagen y luego crea otras 3 versiones en miniatura del mismo archivo, todas tendrán el mismo nombre, acaba con la información de tamaño anexado al nombre):

# Set the filename for versioned files 
    def filename 
    random_token = Digest::SHA2.hexdigest("#{Time.now.utc}--#{model.id.to_s}").first(20) 
    ivar = "@#{mounted_as}_secure_token"  
    token = model.instance_variable_get(ivar) 
    token ||= model.instance_variable_set(ivar, random_token) 
    "#{model.id}_#{token}.jpg" if original_filename 
    end 

esto creará un nombre de archivo como este por ejemplo: 76_a9snx8b81js8kx81kx92.jpg, donde 76 es el ID del modelo y la otra es un poco hexagonal SHA azar.

+3

WOAH. ¡Acabas de hacer mi semana! Esto funciona a la perfección. Modifiqué lo que tenía un poquito para hacer que fuera una marca de tiempo usando el formato que ya estaba usando (porque me agrada estéticamente). Pero esto hace EXACTAMENTE lo que necesitaba y de una forma Ruby muy fácil de entender y elegante en un lugar que tiene sentido. Esto realmente REALMENTE debería estar en el léxico Carrierwave. No puedo ser la única persona que usa esta gema por lo demás fantástica que casi se ha quitado el pelo tratando de resolver esta tarea aparentemente trivial. – seaneshbaugh

+3

FYI .. parece que ya estaba en la wiki de CarrierWave :) https://github.com/jnicklas/carrierwave/wiki/How-to%3A-Create-random-and-unique-filenames-for-all-versioned -files – iwasrobbed

+2

Este código se romperá, porque filename no puede usar model.id, ya que no se establece hasta que se crea el recurso. –

1

Comprobar también la solución de wiki carrierwave disponibles ahora https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Use-a-timestamp-in-file-names

se puede incluir una marca de tiempo en los nombres de archivos reemplazando el nombre de archivo que se puede leer en documentos Carrierwave:

class PhotoUploader < CarrierWave::Uploader::Base 
    def filename 
     @name ||= "#{timestamp}-#{super}" if original_filename.present? and 
     super.present? 
    end 

    def timestamp 
    var = :"@#{mounted_as}_timestamp" 
    model.instance_variable_get(var) or model.instance_variable_set(var, Time.now.to_i) 
    end 
end 

No se olvide de memorizar las dar como resultado una variable de instancia o puede obtener diferentes marcas de tiempo escritas en la base de datos y en el almacén de archivos.

+0

Mucho mejor, esto es exactamente lo que necesitamos en una respuesta. Bien hecho, y gracias por editarlo para incluir código de ejemplo y explicaciones. – Frits

Cuestiones relacionadas