Is it possible to add instance-level callbacks to rails models?
class MyModel << ActiveRecord::Base
def some_stuff(...)
(...)
self.foo = "bar"
after_save { MyWorker.perform_async(self.id) } # Needs to see MyModel.find(id).foo == "bar", but this is not accepted.
end
end
3
Wound up with this:
module InstanceHooks
HOOKS = [
:before_validation,
:after_validation,
:before_save,
:after_save,
:after_commit,
:after_destroy_commit,
:after_update_commit,
:after_save_commit,
:after_rollback
]
def self.ivar_name(hook)
"@#{hook}_hooks"
end
def self.ivar_name_once(hook)
"@#{hook}_hooks_once"
end
def self.define_hook(hook)
ivar = self.ivar_name(hook)
define_method(hook) do |&block|
instance_variable_set(ivar, instance_variable_get(ivar) || [] + [block])
end
ivar_once = self.ivar_name_once(hook)
define_method("#{hook}_once".to_sym) do |&block|
instance_variable_set(ivar_once, instance_variable_get(ivar_once) || [] + [block])
end
end
HOOKS.each { |hook| define_hook(hook) }
def self.included(base)
HOOKS.each do |hook|
ivar = self.ivar_name(hook)
ivar_once = self.ivar_name_once(hook)
base.class_eval do
method(hook).call do |*args, **kwargs, &block|
hooks = instance_variable_get(ivar) || []
once_hooks = instance_variable_get(ivar_once) || []
(hooks + once_hooks).each do |hook|
hook.call(*args, **kwargs, &block)
end
instance_variable_set(ivar_once, [])
end
end
end
end
end
2
You can accomplish this with a normal callback which looks for a special flag on the instance.
class MyModel << ActiveRecord::Base
after_save do
MyWorker.perform_async(self.id) if @perform_my_worker_after_save
end
def some_stuff
self.foo = "bar"
@perform_my_worker_after_save = true
end
end
1