I am trying to implement that when inside a worker a model that has paper trail is updated the whodunnit must be set with 'worker', I tried many things without success, the last one was to try a solution found in a github thread the problem is that I have workers with perfom without / with 1 or multiple params( I changed this to set PaperTrail.request.whodunnit = 'worker')
How can I set whodunnit before or around each perform, so that the whodunnit in the versions is saved as whodunnit: worker?
my workers look like this:
module ModuleName
module Workers
class WorkerClass
include Sidekiq::Worker
sidekiq_options queue: 'default', retry: true
def perform # here with none, 1 or more params
# update some model
end
end
end
end
The solution for me
base_worker.rb
class BaseWorker
extend ActiveModel::Callbacks
define_model_callbacks :perform
around_perform :set_paper_trail_whodunnit
def set_paper_trail_whodunnit
PaperTrail.request.whodunnit = "worker-#{self.class.name}"
yield
PaperTrail.request.whodunnit = nil
end
def perform(*args)
run_callbacks(:perform) do
perform!(*args)
end
end
def perform!(*_args)
raise 'Missing your #perform! method implementation'
end
end
workers:
module ModuleName
module Workers
class WorkerClass < ::BaseWorker
include Sidekiq::Worker
sidekiq_options queue: 'default', retry: true
def perform! # here with none, 1 or more params
# update some model
end
end
end
end
Related
I am new to Ruby on Rails and Sidekiq. I want to set this delete request to be done in Sidekiq queue and I don't know how to send it to the perform method, I am sending the Book model to the perform method
My controller Action code
def destroy
BaseWorkerJob.perform_async(Book)
end
My BaseWorkerJob class code
class BaseWorkerJob
include Sidekiq::Job
sidekiq_options retry:0
def perform(book)
# Do something
book.find(params[:id]).destroy!
sleep 15
end
end
SideKiq Error
enter image description here
ruby 3.1.2
Rails 7.0.4
You can send the model name and object id to the worker
def destroy
BaseWorkerJob.perform_async(Book.to_s, params[:id])
end
class BaseWorkerJob
include Sidekiq::Job
sidekiq_options retry: 0
def perform(klass_name, object_id)
klass_name.constantize.find(object_id).destroy!
end
end
Try it out!
class User < ApplicationRecord
def update_avatar
#some avatar image processing
end
handle_asynchronously :update_avatar, queue: :image_processing
end
I'm using gem delayed_job_active_record with default config for failed jobs as delete_failed_jobs: true. I would like to not delete jobs on queue image_processing, How can I achieve the case.
As described here, to set a per-job default for destroying failed jobs that overrides the Delayed::Worker.destroy_failed_jobs you can define a destroy_failed_jobs? method on the job
NewsletterJob = Struct.new(:text, :emails) do
def perform
emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) }
end
def destroy_failed_jobs?
false
end
end
in your case something similar to this:
class YourJob
...
def destroy_failed_jobs?
queue_name != 'image_processing'
end
end
Is it possible, when catching an error in a job, to put this job in dead?
Something like:
class MyJob < ApplicationJob
queue_as :default
sidekiq_options retry: 5
rescue_from MyError do
# This is where I have to put the job in dead.
end
def perform(document)
...
end
end
As per this question ... you cannot dynamically do this within your job. Your best option would be to set the retries to zero.
From the documentation ... Skip retries, send a failed job straight to the Dead set:
class NonRetryableWorker
include Sidekiq::Worker
sidekiq_options retry: 0
def perform(...)
end
end
I have the following snippet within my job:
before_enqueue do |job|
# do something
#car = create_car
end
before_perform do |job|
# do something
#car.update(type: 'broken')
end
but when the job is performed #car is a nil. Is it possible to pass somehow the instance variable from one callback to the second one? Even only ID would be fine. Cheers.
You would need to make this an instance variable off of job and access that way:
class Car < ActiveJob::Base
attr_accessor :car
end
then
before_enqueue do |job|
# do something
job.car = create_car
end
before_perform do |job|
# do something
job.car.update(type: 'broken')
end
Similar to what you're trying to do, I wanted to know which user enqueued a job without passing the user id to each job in perform. It doesn't look like ActiveJob lets you serialize new instance variables, so I just made use of the arguments variable:
class ApplicationJob < ActiveJob::Base
before_enqueue do |job|
# Get user_id from somewhere first
job.arguments = [user_id, *job.arguments]
end
around_perform do |job, block|
user_id = job.arguments.shift
# Store user_id somewhere
block.call
# Ensure user_id is no longer stored
# (why I'm using around_perform instead of before_perform)
end
end
However, that causes problems if you use perform_now and not just perform_later, since any job performed "now" does not pass through before_enqueue. So here's an improved approach to allow perform_now:
class ApplicationJob < ActiveJob::Base
before_enqueue do |job|
job.arguments = ['_LATER_', user_id, *job.arguments]
end
around_perform do |job, block|
if job.arguments[0] == '_LATER_'
_, user_id = job.arguments.shift(2)
store_somewhere(user_id) { block.call }
else
block.call
end
end
end
I want all the enqueue calls to default to a certain queue unless specified otherwise so it's DRY and easier to maintain. In order to specify a queue, the documentation said to define a variable #queue = X within the class. So, I tried doing the following and it didn't work, any ideas?
class ResqueJob
class << self; attr_accessor :queue end
#queue = :app
end
class ChildJob < ResqueJob
def self.perform
end
end
Resque.enqueue(ChildJob)
Resque::NoQueueError: Jobs must be placed onto a queue.
from /Library/Ruby/Gems/1.8/gems/resque-1.10.0/lib/resque/job.rb:44:in `create'
from /Library/Ruby/Gems/1.8/gems/resque-1.10.0/lib/resque.rb:206:in `enqueue'
from (irb):5
In ruby, class variables are not inherited. That is why Resque can't find your #queue variable.
You should instead define self.queue in your parent class. Resque first checks for the presence of #queue, but looks secondarily for a queue class method:
class ResqueJob
def self.queue; :app; end
end
class ChildJob < ResqueJob
def self.perform; ...; end
end
If you want to do this with a mixin, you can do it like this:
module ResqueJob
extend ActiveSupport::Concern
module ClassMethods
def queue
#queue || :interactor_operations
end
end
end
class ChildJob
include ResqueJob
def self.perfom
end
end
(if you don't have activesupport, you can also do this the classical ruby way, but I find this way easier, so well worth the weight ;) )
Try a mixin. Something like this:
module ResqueJob
def initQueue
#queue = :app
end
end
class ChildJob
extend ResqueJob
initQueue
def self.perform
end
end
Resque.enqueue(ChildJob)