Delayed_Job: accessing job metadata and/or avoiding duplicate jobs - ruby-on-rails

i'm trying to get the run_at datetime in a custom job class. my code looks like this:
class MyCustomJob < Struct.new(:my_object)
def perform
if self.run_at == my_object.start_time
# process the job
end
end
end
i also tried a Delayed::Job.find(self) but had no luck.
thanks in advance

If you define a before method on your custom job, the worker will pass you the delayed job instance before calling perform:
class MyCustomTask
def before(job)
#job = job
end
def perform
# has access to #job object.
# You may need to call #job.reload to see in-flight changes to object in the database.
end
end

You should handle this when you create the job: priority = 0
run_time = my_object.start_time
Delayed::Job.enqueue(MyCustomJob.new(my_object), priority, run_time)
https://github.com/tobi/delayed_job/wiki
If your jobs aren't running at the expected time, you may be scheduling them for UTC:
http://www.gregbenedict.com/2009/08/19/is-delayed-job-run_at-datetime-giving-you-fits/
To check the queue for an existing job - you could do the following:
class MyCustomJob < Struct.new(:object_id)
def self.exists?(object_id)
Delayed::Job.where(['handler = ? and failed_at is null',handler(object_id)]).count(:all) > 0
end
def self.handler(object_id)
"--- !ruby/struct:MyCustomJob \nobject_id: #{object_id}\n"
end
def perform
my_object = MyObject.find(object_id)
my_object.do_stuff
end
end
Then just check for MyCustomJob.exists?(my_object.id) before queueing.
It's a little bit of a hack - edit the handler method as needed. I would modify the delayed_jobs table to have a class/object_id to make for cleaner code and more efficient table scans if your jobs table is large or if you do this with other types of jobs.
This question also looks relevant:
How to cancel scheduled job with delayed_job in Rails?

Related

How to stop a Delayed Job from running

Can I use the hooks of a delayed job to stop it before running?
If so how?
class Jobs::SomeJob < Struct.new
def before(job)
if should_not_run_job
# Do I raise an exception?
# Is there an official way to stop a Job from running?
end
end
def perform
# Working...
end
def queue_name
return "SomeJob";
end
end
Do I raise an error?
Which hook is recommended to use?
Raising an exception wouldn't work because then the retry mechanism would kick in and would retry running the job a couple of times.
Instead, I would just add a guard clause that in the first line of the perform method that returns without doing anything when the conditions is not true anymore. It depends on how your condition looks like but something like this might work for you:
def perform
return if job_should_not_run_anymore?
# Working
end
private
def job_should_not_run_anymore?
# Condition
end
When your condition is based on the job itself that is only available in a hook method like before but not in the perform method then I would store the result of the condition in an instance variable and check that variable in the perform method like this:
def before(job)
#outdated = job.run_at > 15.minutes.ago
end
def perform
return if #outdated
# Working
end

How do I create delayed_job jobs with hooks/callbacks?

I am using the most basic version of delayed_job in a Rails app. I have the max time allowed for a delayed_job set at 10 minutes. I would like to get the hooks/callbacks working so I can do something after a job stop executing at the 10 minute mark.
I have this set in my rails app:
config.active_job.queue_adapter = :delayed_job
This is how I normally queue a job:
object.delay.object_action
The hook/callback example is for a named job but the basic, getting started steps are not for a named job. So I don't think I have a named job. Here is the example given to get the callbacks working:
class ParanoidNewsletterJob < NewsletterJob
def enqueue(job)
record_stat 'newsletter_job/enqueue'
end
def perform
emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) }
end
def before(job)
record_stat 'newsletter_job/start'
end
def after(job)
record_stat 'newsletter_job/after'
end
def success(job)
record_stat 'newsletter_job/success'
end
def error(job, exception)
Airbrake.notify(exception)
end
def failure(job)
page_sysadmin_in_the_middle_of_the_night
end
end
I would love to get the after or error hooks/callbacks to fire.
Where do I put these callbacks in my Rails app to have them fire for the basic delayed_job setup? If I should be using ActiveJob callbacks where do you put those callbacks given delayed_job is being used?
You cannot use object.delay.object_action convenience syntax if you want more advanced features like callbacks. The #delay convenience method will generate a job object that works similar to this:
# something like this is already defined in delayed_job
class MethodCallerJob
def initialize(object, method, *args)
#object = object
#method = method
#args = args
end
def perform
#object.send(#method, *#args)
end
end
# `object.delay.object_action` does the below automatically for you
# instantiates a job with your object and method call
job = MethodCallerJob.new(object, :object_action, [])
Delayed::Job.enqueue(job) # enqueues it for running later
then later, in the job worker, something like the below happens:
job = Delayed::Job.find(job_id) # whatever the id turned out to be
job.invoke_job # does all the things, including calling #perform and run any hooks
job.delete # if it was successful
You have to create what the delayed_job README calls "Custom Jobs", which are just plain POROs that have #perform defined at a minimum. Then you can customize it and add all the extra methods that delayed_job uses for extra features like max_run_time, queue_name, and the ones you want to use: callbacks & hooks.
Sidenote: The above info is for using delayed_job directly. All of the above is possible using ActiveJob as well. You just have to do it the ActiveJob way by reading the documentation & guides on how, just as I've linked you to the delayed_job README, above.
You can create delayed_job hooks/callback by something like this
module Delayed
module Plugins
class TestHooks < Delayed::Plugin
callbacks do |lifecycle|
lifecycle.before(:perform) do |_worker, job|
.....
end
end
end
end
end
And need this plugin to initializer
config/initializers/delayed_job.rb
require_relative 'path_to_test_plugin'
Delayed::Worker.plugins << Delayed::Plugins::TestHooks
Similar to perform there are also hooks for success failure and error.
And similar to 'before' you can also capture the 'after' hooks.

Sidekiq Job - How can I launch a job and each time change the parameter used?

I would like to launch a job which is going to calculate the points of each user of my web-App.
Here is the problem, I would like to launch it automatically with sidekiq-scheduler.
But I have trouble to understand how I can launch my job with an argument which is going to change. I mean I have to calculate the amount of points for each user, so the argument is going to change and take different user_id.
Here is my code :
class PointsjoueurJob < ApplicationJob
queue_as :default
def perform(user_id)
#user = User.find(user_id)
#playerseason = PlayerSeason.where(user_id: #user.id)
#forecasts = Forecast.where(player_season_id: #playerseason)
points = []
#forecasts.each do |forecast|
if forecast.points_win.present? || forecast.points_lose.present?
if forecast.points_win.present?
points << forecast.points_win
else forecast.points_lose.present?
points << forecast.points_lose
end
#playerseason.update(number_of_points: points.sum)
else
end
end
end
Right now if I want to launch it, I have to go to my console then type :
PointsjoueurJob.perform_now(1)
But I want to schedule this with sidekiq-scheduler. The goal is to trigger the work everyday at 01h00 (cron: '0 1 * * *')but I don't know how to set-up the argument in order for the job to iterate trough all the users.
Thank you by advance.
Assuming that you want to recalculate all users' totals, you can create a separate 'wrapper' job, which is scheduled, that in turn enqueues the individual recalculation jobs:
class RecalcPointsJob < ApplicationJob
queue_as :default
def perform
User.find_each do |u|
PointsjoueurJob.perform_later(u.id)
end
end
end
If you are after a subset of users instead, substitute User.where() or User.find_by().
You can generate a Task and use whenever, then setup it.
on task you can write this:
rails g task test cron
namespace :test do
task :cron do
User.find_each do |u|
PointsjoueurJob.perform_async(u.id)
end
end
end
then in config/schedule.rb after install whenever
every '0 1 * * *' do
rake "test:cron"
end
then
whenever --update-crontab

Rails control Job execution

I have a job created with rails g job cleanUp.
Is where any option to check if Job is running? Something like this CleanUpJob.isRunning?
If where is no way to make it without additional gems, which will be the simplest? delayed_job?
Second thing to control Job is progress, any thoughts how to implement CleanUpJob.progress or progress_job should be my choice?
briefly:
I need to create a job with two methods (isRunning?, progress).
I don't really want additional tables if possible.
You can use Class: Sidekiq::ScheduledSet for this purpose.
Documentation here
This Class is used in Sidekiq web interface.
Example:
Save job id (jid) when set job. Then you can call it for queried instance
def is_running?
require 'sidekiq/api'
ss = Sidekiq::ScheduledSet.new
jobs = ss.select {|ret| ret.jid == self.jid}
jobs.any?
end
Or, you can set DB flag inside Job with around_perform hook.
class SomeJob < ActiveJob::Base
queue_as :some_job
around_perform do |job, block|
start_import_process_log job.arguments[0], job.arguments[1] || {}
block.call
finish_import_process_log
end
# ...
private
def start_import_process_log import_process, options={}
#some actions
end
def finish_import_process_log
end
end
In this example associated log record is created.
Or you can use before_perform/ after_perform.
In my practice I'm using creting log records on long tasks.
When I need to find and kill job as example - I'm using Sidekiq::ScheduledSet.

How to perform a background job now?

I want to execute this on background
Product.all.map { |product| product.save }
When I save the product will call a callback to create a new record in a table with the costs of products
I create a job for this, but if I execute perform_now it is not executed on background and perform_later executes long after.
I want to execute this right now but in background. I'm not sure if I can just execute this in a thread too.
I am using Delayed Job, here is the job
class UpdateProductCostsJob < ApplicationJob
queue_as :default
def perform
Product.all.map { |product| product.save }
end
end
And I want to execute every time this model is saved
class CostComposition < ApplicationRecord
before_save :update_product_costs
private
def update_product_costs
UpdateProductCostsJob.perform_now
end
end
Assuming that you are using rails 5, you can create a high priority queue and create the job under that queue. If you have only one queue, you can add the job to the queue as well as specify the time when you want to process that job.
UpdateProductCostJob.set(wait_until: Time.now + 5.minutes).perform_later
refer http://edgeguides.rubyonrails.org/active_job_basics.html#enqueue-the-job

Resources