«

»

May 19

Agendamento de tarefas muito fácil com com Ruby on Rails

Em um projeto pequeno, que precisava rodar em um desktop, eu precisei de um agendador de tarefas, o sistema precisava rodar em windows, linux ou mac, então utilizar o Cron para isto não era uma opção, e eu precisava de algo semelhante ao Cron, rodar algum código a cada X tempo não era o suficiente, ou pelo menos tornaria a minha vida muito mais difícil.

Eu também não queria ter que iniciar um processo separado da aplicação para cuidar disto pois eram tarefas simples, e depois de algumas pesquisas, encontrei o Rufus-Scheduler que resolve o problema que eu tinha, então resolvi escrever este post para que eu lembre dele caso precise novamente.

Sempre lembrando que aqui não serão mostrados todos do Rufus-Scheduler, apenas uma forma simples de usa-lo, se tiver dúvidas deixe um comentário que farei o melhor para esclarece-las.

Este exemplo vai começar com uma aplicação simples, um scaffold de uma entidade de nome SimpleTask com os seguintes parâmetros:

rails generate scaffold SampleTask name:string cron:string times:integer done:boolean job_id:string
rake db:migrate

A premissa para este exemplo é a seguinte:
Quando o sistema for iniciado as tarefas ainda não completas tem que ser agendadas novamente, e toda vez que eu criar uma tarefa nova esta tem que ser agendada automaticamente.

Então vamos a implementação com o Rufus-Scheduler.

  • Apagar o arquivo public/index.html
  • No arquivo routes.rb adicionar a linha ‘root :to => “sample_tasks#index”‘
  • No Gemfile adicionar a linha ‘gem “rufus-scheduler”‘
  • Criar um arquivo config/initializers/scheduler_initialization.rb com a seguinte linha: MySampleScheduler.update_schedules

No model criado adicionar o seguinte código:

class SampleTask < ActiveRecord::Base
	after_create :schedule_me
	after_destroy :unschedule_me
 
	def schedule_me
		MySampleScheduler.schedule read_attribute(:id)
	end
	def unschedule_me
		MySampleScheduler.unschedule read_attribute(:job_id)
	end
end

Criar o arquivo lib/my_sample_scheduler.rb com o seguinte conteúdo:

class MySampleScheduler
	@@scheduler = Rufus::Scheduler::PlainScheduler.start_new
	def self.update_schedules
		@@scheduler.in '1m' do #delay initialization because of problems acessing rails models during rails initialization
			jobs = @@scheduler.cron_jobs
			tasks = SampleTask.all
			tasks.each do |task|
				job = jobs[task.job_id] if task.job_id
				job.unschedule if job && (task.done || job.cron_line!=task.cron)
				if !task.done
					schedule(task)
				end
			end
		end
	end
	def self.unschedule(job_id)
		@@scheduler.unschedule(job_id)
	end
	def self.schedule(task_id)
		task = SampleTask.find task_id
		job = @@scheduler.cron task.cron do |j|
			task = SampleTask.find task_id
			if task
				puts "#{task.name} executing at #{Time.now} ---- #{task.times}"
				task.times = task.times - 1
				task.done = task.times==0
				task.save
				unschedule task.job_id if task.done
			else
				puts "Task deleted #{task_id}"
				unschedule j.job_id
			end
		end
		task.job_id = job.job_id
		task.save
	end
end

Rodar a aplicação e já deve ser possível cadastrar tarefas.
Quando a aplicação for finalizada e novamente inicializada, as tarefas pendentes terão um delay de 1 minuto para re-iniciarem, isto foi necessário porque se acessarmos os models diretamente durante a inicialização do rails, muitas coisas ruins podem acontecer.

Não se esqueça que para este exemplo funcionar você precisa ter o Rufus-Scheduler instalado. Se você seguiu todos os passos, um “bundle install” do diretório do projeto deve resolver o problema.

Se quiser o código da aplicação que eu escrevi, ele esta disponível neste endereço:http://github.com/urubatan/rails_schedule_samples

Se você já usa o GIT pode baixar o código com o comando: git clone git://github.com/urubatan/rails_schedule_samples.git