Programando "Cron Jobs" en RoR con Whenever

  • por Jesús Kahwati

En algún momento de la vida útil de nuestras aplicaciones (de rails o no) podríamos necesitar que alguna tarea sea ejecutada cada cierto tiempo, por ejemplo:

1)  Enviar un correo mensual a los usuarios del sistema con su estado de cuenta bancaria

2) Hacer un respaldo de la base de datos del sistema todos los lunes

3) Actualizar un valor dentro de la base de datos de nuestra aplicacion cada 5 minutos

Pero, ¿como hacemos esto?

Existen 2 términos que debes conocer: "Cron" y "Crontab"

Cron: Es un demonio (proceso en segundo plano) que se ejecuta desde el mismo instante en el que arranca el sistema. Comprueba si existe alguna tarea (job) para ser ejecutado de acuerdo a la hora configurada en el sistema.

Crontab: Es un archivo de texto. Por simple que parezca la definición, es así. Aunque es verdad que se trata de un archivo con contenido especial. Posee una lista con todos los scripts a ejecutar. Generalmente cada usuario del sistema posee su propio fichero Crontab. 

Entonces, ¿cómo utilizamos estos recursos en nuestra aplicacion?

Whenever es una gema de Ruby que proporciona una clara sintaxis para escribir e implementar tareas llamadas "Cron Jobs".

¿Cómo lo instalamos?

$ gem install whenever

ó

gem 'whenever', :require => false
$ bundle install

 ":require => false" significa que la gema será instalada, pero no se cargará en un proceso a menos que llame explícitamente "whenever".

Una vez instalado, ubicamos la ruta del proyecto:

$ cd /apps/mi-proyecto
$ wheneverize

Esta ultima linea de comando permite crear el archivo schedule.rb en nuestro proyecto en la siguiente ruta:

/apps/mi-proyecto/config/schedule.rb

En este archivo programaremos nuestras tareas. Hagamos un ejemplo sencillo: 

schedule.fb file

set :environment, :development              #Configuración del entorno de desarrollo
set :output, "#{path}/log/cron_log.logs"    #Ruta de la salida en un archivo llamado cron_logs.logs
env :GEM_PATH, ENV['GEM_PATH']              


every 1.hour do

    command "echo '-----Empieza la tarea-----'"      #Imprime mensaje en la salida cron_logs.logs
    runner "Project.last.increase_worm_size"         #Ejecuta método dentro del proyecto
    command "echo '-----Finaliza la tarea-----'"     #Imprime mensaje en la salida cron_logs.logs

end

Como pueden observar la sintaxis de whenever es muy intuitiva, el codigo anterior especifica que cada 1 hora se van a imprimir 2 mensajes en el archivo cron_logs.logs y y va a ejecutar el metodo del Proyecto llamado increase_worm_size encargado de agregar un "0" a la derecha del atributo worm, (simulando que un gusano crece cada minuto). Tambien podrías utilizar el siguiente codigo en caso de que quieras ver los resultados más rápido (cada 2 minutos):


every 2.minutes do

    command "echo '-----Empieza la tarea-----'"      #Imprime mensaje en la salida cron_logs.logs
    runner "Project.last.increase_worm_size"         #Ejecuta método dentro del proyecto
    command "echo '-----Finaliza la tarea-----'"     #Imprime mensaje en la salida cron_logs.logs

end

 

  def increase_worm_size 
    self.update_attributes(worm: self.name += "0")
  end

Una vez programada la tarea, escribimos en la consola:

$ whenever

Esto nos mostrará nuestro archivo schedule.rb en sintaxis de cron:

0 * * * * /bin/bash -l -c 'echo '\''Empieza la tarea'\'' >> /home/.../mi-proyecto/log/cron_log.logs 2>&1'

0 * * * * /bin/bash -l -c 'cd /home/.../mi-proyecto/ && bundle exec bin/rails runner -e development '\''Project.last.increase_worm_size()'\'' >> /home/.../mi-proyecto/log/cron_log.logs 2>&1'

0 * * * * /bin/bash -l -c 'echo '\''Finaliza la tarea'\'' >> /home/.../mi-proyecto/log/cron_log.logs 2>&1'

## [message] Above is your schedule file converted to cron syntax; your crontab file was not updated.
## [message] Run `whenever --help' for more options.

Donde 0 * * * *  significa:

# * * * * *  comando a ejecutar
# │ │ │ │ │
# │ │ │ │ │
# │ │ │ │ └───── dia de la semana(0 - 6) (Domingo=0)
# │ │ │ └────────── mes (1 - 12)
# │ │ └─────────────── dia del mes (1 - 31)
# │ └──────────────────── hora (0 - 23)
# └───────────────────────── minuto (0 - 59)

Otro ejemplo:

every :monday, :at => '2:30 am' do 
  runner "Task.do_something_great"
end
30 2 * * 1 

Ejecutar este script:

  • En el minuto 30
  • De las 2 de la noche
  • De cada día del mes
  • De cada mes
  • Sólo si es lunes

En resumen, todos los lunes a las 2:30 horas se ejecutará el script.

Siguiendo con nuestro ejemplo, luego actualizamos el archivo crontab, y le llamamos "cronExample"

$ whenever --update-crontab cronExample

Y listo, la tarea esta programada. ¿cómo sabemos si esta funcionando? Revisemos el modelo "Project" y el atributo "worm" :

En el transcurso hacia la primera hora

:worm => ">0"

En la primera hora: 

:worm => ">00"

En la octava hora: 

:worm => ">000000000"

Esto suponiendo que se requiere que cada hora un atributo llamado "worm" aumente de tamaño (agregando ceros al lado derecho del gusano) .

Ahora, ¿cómo detenemos esta tarea?

Basta con escribir en la consola:

$ whenever -c cronExample

Donde cronExample es el nombre de nuestro cron job.

Otros comando importantes son: 

Para ver las tareas existentes a través de la consola de  linux:

$ crontab -l

Para editarlas:

$ crontab -e

Ayuda

$ whenever --help

Para mas informacion puede visitar el siguiente enlace:

https://github.com/javan/whenever