ActiveJob: Accessible instance variables between callbacks - ruby-on-rails

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

Related

Is there a way to, not delete a job for specific queue even delete_failed_jobs: true globally?

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

How to call `super` in a block

I have a block of code. It was:
class User < ActiveRecord::Base
def configuration_with_cache
Rails.cache.fetch("user_#{id}_configuration") do
configuration_without_cache
end
end
alias_method_chain :configuration, :cache
end
I want to remove the notorious alias_method_chain, so I decided to refactor it. Here is my version:
class User < ActiveRecord::Base
def configuration
Rails.cache.fetch("#{id}_agency_configuration") do
super
end
end
end
But it doesn't work. The super enters a new scope. How can I make it work? I got TypeError: can't cast Class, and I misunderstood it.
To start off, calling super in blocks does behave the way you want. Must be your console is in a corrupted state (or something).
class User
def professional?
Rails.cache.fetch("user_professional") do
puts 'running super'
super
end
end
end
User.new.professional?
# >> running super
# => false
User.new.professional?
# => false
Next, this looks like something Module#prepend was made to help with.
module Cacher
def with_rails_cache(method)
mod = Module.new do
define_method method do
cache_key = "my_cache_for_#{method}"
Rails.cache.fetch(cache_key) do
puts "filling the cache"
super()
end
end
end
prepend mod
end
end
class User
extend Cacher
with_rails_cache :professional?
end
User.new.professional?
# >> filling the cache
# => false
User.new.professional?
# => false
you can user Super in block.
please see this, any issues let me know.
Calling it as just 'super' will pass the block.
super(*args, &block)' will as well.

Passing a block to a dynamically created method

I'm creating a module that extends the functionality of an ActiveRecord model.
Here's my initial setup.
My class:
class MyClass < ActiveRecord::Base
is_my_modiable
end
And Module:
module MyMod
def self.is_my_modiable
class_eval do
def new_method
self.mod = true
self.save!
end
end
end
end
ActiveRecord::Base(extend,MyMod)
What I would like to do now is extend the functionality of the new_method by passing in a block. Something like this:
class MyClass < ActiveRecord::Base
is_my_modiable do
self.something_special
end
end
module MyMod
def self.is_my_modiable
class_eval do
def new_method
yield if block_given?
self.mod = true
self.save!
end
end
end
end
This doesn't work though, and it makes sense. In the class_eval, the new_method isn't being executed, just defined, and thus the yield statement wouldn't get executed until the method actually gets called.
I've tried to assign the block to a class variable within the class_eval, and then call that class variable within the method, but the block was being called on all is_my_modiable models, even if they didn't pass a block into the method.
I might just override the method to get the same effect, but I'm hoping there is a more elegant way.
If I understood you correctly, you can solve this by saving passed block to an instance variable on class object and then evaling that in instance methods.
bl.call won't do here, because it will execute in the original context (that of a class) and you need to execute it in scope of this current instance.
module MyMod
def is_my_modiable(&block)
class_eval do
#stored_block = block # back up block
def new_method
bl = self.class.instance_variable_get(:#stored_block) # get from class and execute
instance_eval(&bl) if bl
self.mod = true
self.save!
end
end
end
end
class MyClass
extend MyMod
is_my_modiable do
puts "in my modiable block"
self.something_special
end
def something_special
puts "in something special"
end
attr_accessor :mod
def save!; end
end
MyClass.new.new_method
# >> in my modiable block
# >> in something special
You can do this by assigning the block as a method parameter:
module MyMod
def self.is_my_modiable
class_eval do
def new_method(&block)
block.call if block
self.mod = true
self.save!
end
end
end
end

How to specify a default queue to use for all jobs with Resque in Rails?

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)

Add save callback to a single ActiveRecord instance, is it possible?

Is it possible to add a callback to a single ActiveRecord instance? As a further constraint this is to go on a library so I don't have control over the class (except to monkey-patch it).
This is more or less what I want to do:
def do_something_creazy
message = Message.new
message.on_save_call :do_even_more_crazy_stuff
end
def do_even_more_crazy_stuff(message)
puts "Message #{message} has been saved! Hallelujah!"
end
You could do something like that by adding a callback to the object right after creating it and like you said, monkey-patching the default AR before_save method:
def do_something_ballsy
msg = Message.new
def msg.before_save(msg)
puts "Message #{msg} is saved."
# Calls before_save defined in the model
super
end
end
For something like this you can always define your own crazy handlers:
class Something < ActiveRecord::Base
before_save :run_before_save_callbacks
def before_save(&block)
#before_save_callbacks ||= [ ]
#before_save_callbacks << block
end
protected
def run_before_save_callbacks
return unless #before_save_callbacks
#before_save_callbacks.each do |callback|
callback.call
end
end
end
This could be made more generic, or an ActiveRecord::Base extension, whatever suits your problem scope. Using it should be easy:
something = Something.new
something.before_save do
Rails.logger.warn("I'm saving!")
end
I wanted to use this approach in my own project to be able to inject additional actions into the 'save' action of a model from my controller layer. I took Tadman's answer a stage further and created a module that can be injected into active model classes:
module InstanceCallbacks
extend ActiveSupport::Concern
CALLBACKS = [:before_validation, :after_validation, :before_save, :before_create, :after_create, :after_save, :after_commit]
included do
CALLBACKS.each do |callback|
class_eval <<-RUBY, __FILE__, __LINE__
#{callback} :run_#{callback}_instance_callbacks
def run_#{callback}_instance_callbacks
return unless #instance_#{callback}_callbacks
#instance_#{callback}_callbacks.each do |callback|
callback.call
end
end
def #{callback}(&callback)
#instance_#{callback}_callbacks ||= []
#instance_#{callback}_callbacks << callback
end
RUBY
end
end
end
This allows you to inject a full set of instance callbacks into any model just by including the module. In this case:
class Message
include InstanceCallbacks
end
And then you can do things like:
m = Message.new
m.after_save do
puts "In after_save callback"
end
m.save!
To add to bobthabuilda's answer - instead of defining the method on the objects metaclass, extend the object with a module:
def do_something_ballsy
callback = Module.new do
def before_save(msg)
puts "Message #{msg} is saved."
# Calls before_save defined in the model
super
end
end
msg = Message.new
msg.extend(callback)
end
This way, you can define multiple callbacks, and they will be executed in the opposite order you added them.
The following will allow you to use an ordinary before_save construction, i.e. calling it on the class, only in this case, you call it on the instance's metaclass so that no other instances of Message shall be affected. (Tested in Ruby 1.9, Rails 3.13)
msg = Message.new
class << msg
before_save -> { puts "Message #{self} is saved" } # Here, `self` is the msg instance
end
Message.before_save # Calling this with no args will ensure that it gets added to the callbacks chain (but only for your instance)
Test it thus:
msg.save # will run the before_save callback above
Message.new.save # will NOT run the before_save callback above

Resources