Posts Tagged ruby on rails
Procesando archivos en background con Rails
Posted by Gazer in General, Programación on 08/09/2010
Procesar archivos en paneles de administración es un bajón, sobre todo cuando son cada vez más grande y cada vez más. En uno de nuestros proyectos tenemos ya unos cinco archivos CSV diferentes a procesar donde cada uno cumple alguna función específica. Algunos de estos archivos superan los 5 Mb por upload, por lo que se hizo necesario desacoplar el procesamiento del archivo del request que realiza el upload.
Para manejar procesos en background en rails hay muchas opciones buenas, algunas más complejas con colas de trabajo, workers distribuidos y otras cosas que nosotros no necesitamos. Lo importante para nosotros era encontrar alguno que permita :
- Procesar en background (obvio
) - Poder saber en que estado está el trabajo (% completado)
- Al menos un proceso en background a la vez (si hay más que espere su turno, los uploads son semanales o mensuales por lo que no hay tanto solapamiento entre tareas como para justificar múltiples workers)
Y nada más. Buscando y buscando caí con BackgroundFu que cumplía con lo necesario.
Procesando los archivos
Como todos los archivos son de texto con algún separador (algunos ‘;’, otros tabs o comas) empezamos por lo básico y crear una clase base que maneje algunos parámetros comunes.
En BackgroundFu uno puede encolar cualquier clase, por lo que nuestro worker es simplemente una clase con un método process (que también es arbitrario el nombre, podría ser cualquier otro).
Como el archivo que sube el usuario está en un directorio temporal, antes de mandar al proceso en background en necesario guardarlo en algún lugar seguro, para poder saber que al momento de procesarlo va a seguir estando, para ello encapsulamos este comportamiento en un método especial para encolar.
Nuestra completa termina siendo algo como (más adelante veremos en detalle algunas cosas) :
class FileWorker include BackgroundFu::WorkerMonitoring attr_reader :total_lines def self.enqueue(file, *args) filename = "#{RAILS_ROOT}/tmp/#{self.name}-#{Time.now.to_i}" File.open(filename, "wb") { |f| f.write(file.read) } Job.enqueue!(self.name, :'process', filename, *args).id end def process(filename, *args) @lines = 0 @total_lines = `wc -l #{filename}`.to_i record_progress(0) result = real_process(filename, *args) record_progress(100) File.unlink(filename) result end protected def line_processed! @lines += 1 update(@lines) end def update(lines) record_progress(lines, @total_lines) end end
Veamos un poco más en detalle algunas partes.
Encolado
def self.enqueue(file, *args) filename = "#{RAILS_ROOT}/tmp/#{self.name}-#{Time.now.to_i}" File.open(filename, "wb") { |f| f.write(file.read) } Job.enqueue!(self.name, :'process', filename, *args).id end
Este método es quien guarda en un lugar seguro el archivo. Utilizando el nombre de la clase actual (recuerden que este método está en una clase base, de donde luego vamos a heredar) y un timestamp por las dudas que se lleguen a hacer 2 uploads (cosa que en la práctica no pasa, pero por las dudas está).
Luego de guardar el archivo se encola el trabajo pasándole el archivo (el path completo) y cualquier otro argumento que se quiera.
Esto hará que luego el daemon de background instancia la clase ‘self.name’ y llame al método process pasandole nuestros parámetros.
Procesamiento
El método genérico de procesamiento hace uso de un método que la clase base no tiene ‘real_process’ que es el método que las clases hijas definarán para realizar el trabajo final :
def process(filename, *args) @lines = 0 @total_lines = `wc -l #{filename}`.to_i record_progress(0) result = real_process(filename, *args) record_progress(100) File.unlink(filename) result end
En este caso el método se encarga de saber cual es el total de lineas (de una manera tal vez no muy linda, pero que anda
) y borrar el archivo al terminar el proceso.
Un worker real
Para que esto tenga sentido, debemos crear un worker que haga realmente algo, entonces por ejemplo podríamos tener un worker que sume puntos a los usuarios desde un archivo :
class UserPointProcessor < FileWorker private def real_process(file) ActiveRecord::Base.transaction do FasterCSV.new(File.read(file), :col_sep => "\t").each do |row| # Hacer algo con row line_processed! end end "Podemos retornar algun valor o status de exito" end end
De esta forma podemos ahora en algún controller hacer el encolado del trabajo :
def add_points if request.post? redirect_to admin_job_path(UserPointProcessor.enqueue(params[:file])) and return end render 'file_upload' end
En este caso el template file_upload es un form con un file field para hacer el upload, sin ningún otro campo. La ruta admin_job_path nos la provee directamente BackgroundFu, generándonos también las vistas con AJAX para actualizar el estado, una linda barra de progreso que nos muestra el tiempo estimado para terminar entre otros datos útiles (o no tanto
).
Take A Photo – Demo
Posted by Gazer in Programación on 01/07/2009
Hace unos meses publiqué un plugin para tomar fotos instantáneas desde una página web, usando Flash y un plugin para Ruby on Rails.
Hoy me puse un rato a jugar y armé un demo donde se pueden tomar fotos (ojo que todo es público
) : TAP.
Las fotos se borran una vez por día y acepta un máximo de 50 fotos por día (para evitar que me llenen el disco). Tengo algunos TODOs que iré viendo de resolver en los tiempos libres (como cambiar el tamaño de la foto, recortar, aplicar efectos, etc).
Necesita Flash 9 o superior y una web cam para que tenga sentido
. Todavía sigo peleando con el Flash player de Linux y la camarita, sorry :S.
Les recomiendo verlo en Firefox, no me gasté en ver si en IE el CSS no se rompe y no creo que lo haga nunca.
Take A Photo – Fotos instantáneas desde tu web
Posted by Gazer in Programación on 02/04/2009
En la última semana estuve trabajando en agregar nuevas funcionalidades a un sitio de un cliente y entre los pedidos estaba una especie de Fotoblog para los usuarios (y bue, hay que pagar las cuentas a fin de mes
).
La cosa salió rápido, usando Paperclip que guarda las imágenes que se suben, se muestra en orden, etc, nada del otro mundo. Pero hablando con el cliente surgió la idea de hacer que el usuario se pueda tomar una foto directamente desde la web usando su webcam, así que después de decir “si, se debe poder hacer” mentalmente me salió un “doh!, que dije!“. Lo último que se dijo en esa reunión fue “Lo quiero”
.
Ya había visto juegos flash que utilizan la webcam así que empecé por ahí, para ver como sacaban un frame del video, lo cual era muy fácil. Lo siguiente era serializarla. A falta de algo mejor hice un serializador de imágenes muy pedorro, pero que anda (aunque es lento), que envía por POST la información de cada pixel.
La parte de Ruby fue fácil y decidí encapsularla en un plugin de Rails para poder reutilizarlo luego o por si a alguien más le sirve
. Además se puede integrar con Paperclip o AttachmentFu para hacer la persistencia de la imagen en donde sea.
La única parte que usa Flash es el capturador de la cámara, porque otra no quedaba, pero luego el botón para tomar la foto y los eventos se manejan todo por Javascript.
El plugin tiene varios TODOs, pero por si a alguien le sirve está en github.
Update : A falta de demo, algunas de las fotos tomadas con la aplicación de ejemplo que hice hoy durante Locos x rails.
Refactoring de “Fat Methods” – Episodio 4
Posted by Gazer in Programación on 01/12/2008
En la última entrega habíamos logrado separar las notificaciones del controlador. Una de las cosas que todavía me seguían molestando eran los métodos donde obtengo las direcciones de email, como por ejemplo :
@notifications = @match.players.select {|p| p.user.notify_new_match_comment? }.collect {|p| p.user.email}
Lo que busca esto es obtener los emails de los usuarios para notificar ante un evento dado (un nuevo partido creado, un nuevo comentario, una petición de amistad, etc). Me molesta principalmente porque se ven horribles mis controllers
.
La respuesta que motiva este episodio llegó por casualidad. Intentando solucionar un bug que tenía en uno de los plugins que uso (has_many_friends, que es feo pero cómodo para un proyecto como este de prueba) vi algo que no conocía : las asociaciones (has_many, has_one, etc) aceptan un bloque donde podemos agregar funcionalidad extra.
Entonces, por ejemplo, si yo quisiera que el ejemplo anterior pueda ser escrito como @match.players.notificables (si, el nombre no es de lo mejor, pero es a modo ilustrativo) simplemente debería agregar ese método como sigue :
class Match < ActiveRecord::Base has_many :players do def notificables collect {|p| p.user.email if !p.user.email.blank? && p.user.notify_new_match_comment? } end end end # Ejemplo de uso @notifications = @match.players.notificables
El método que agregamos trabaja directamente sobre la asociación, que en este caso es una colección de Player, por lo que el collect nos dará todos los emails de los usuarios que se anotaron al partido y tienen activa la opción de recibir notificaciones cuando alguien deja un comentario nuevo en ese partido.
Los métodos que agregamos pueden aceptar parámetros también, con lo cual podría aceptar un symbol con el tipo de permiso a verificar y tener todos los emails de los usuarios para un tipo de notificación dada. Ahora si se hace un refactor de las opciones de notificación de los usuarios tenemos centralizada la lógica en los modelos en lugar de tenerla en los controladores u observers.
Reunión Mensual de Ruby Argentina
Posted by Gazer in Yo Estuve Ahí on 22/11/2008
Hoy anunciamos la próxima reunión mensual que se va a desarrollar la semana que viene. Cualquier interesado en dar alguna charla puede dejarme un comentario.
El día 29/11 se presentará un conjunto de charlas y se lanzará el ciclo mensual de conferencias sobre Ruby y Ruby on Rails y organizado por Argentina on Rails y Ruby Argentina.
El evento se llevará a cabo entre las 11 y las 16hs, con un break para almorzar sobre las 13hs. Al momento contamos con la presencia de Luis Lavena, actual mainteiner del one-click installer de Ruby para Windows, Pedro Vistinin de Argentina on Rails. A medida que tengamos confirmación de los demás oradores la iremos comunicando.
Luis hablará del rake-compiler y draco
rake-compiler es una serie de tareas de rake que simplifican la compilacion de tanto extensiones como gemas en Ruby, cosa que hasta ahora viene bastante descuidada y caoticamente un desastre entre developers. Cositas lindas para que no tengan mas excusas de no hacer cosas para Windows…
Pedro Visintin hablará de como hacerse freelance (trabajando con rails
) y no morir en el intento.
Si estás interesado en dar alguna charla referida a Ruby o Rails, mandá un mail que veremos como proveerte un espacio en ésta o próximas ediciones del evento.
Lugar
La cita es en la Facultad de Ingeniería de la UBA, Paseo Colón 850 en el Aula 402, 4to Piso. Capital Federal
La entrada es libre y gratuita y no requiere registración previa.
Además, CaFeLug sorteará 2 libros de Ruby/Rails de la editorial O’Reilly entre los asistentes.




