Module to wrap a class method?
Is it possible to make t开发者_C百科his work, without having to include the module at the end of the class and just include it at the top?
module VerboseJob
def self.included(job_class)
class << job_class
alias_method :original_perform, :perform
def perform(*args)
JobLogger.verbose { original_perform(*args) }
end
end
end
end
class HelloJob
include VerboseJob
def self.perform(arg1, arg2)
puts "Job invoked with #{arg1} and #{arg2}"
end
end
What I want to happen, is for HelloJob.perform
to actually invoke VerboseJob.perform
(which then calls the original method inside a block). Because the module here is included at the top of the class, this doesn't work, since perform
isn't yet defined. Moving the include
to the end does work, but is there a way that's a bit more forgiving? I like to keep all the included modules at the top of my class definitions.
I'm sort of looking for some method that is called on Module
or Class
when it has been fully loaded, instead of as it is interpreted by the runtime.
Here is a rather roundabout/hackish way of doing it that I came up with by deferring the definition of the wrapper method until the original method had been defined:
module A
def self.included(base)
base.class_eval do
def self.singleton_method_added(name)
@@ran||=false
if name==:perform && !@@ran
@@ran=true
class<<self
alias_method :original_perform, :perform
def perform(*args)
puts "Hello"
original_perform(*args)
end
end
end
end
end
end
end
class B
include A
def self.perform
puts "Foobar"
end
end
B.perform
Edit:
d11wtq simplified this to the much cleaner:
module VerboseJob
module ClassMethods
def wrap_perform!
class << self
def perform_with_verbose(*args)
JobLogger.verbose { perform_without_verbose(*args) }
end
alias_method_chain :perform, :verbose \
unless instance_method(:perform) == instance_method(:perform_with_verbose)
end
end
def singleton_method_added(name)
wrap_perform! if name == :perform
end
end
def self.included(job_class)
job_class.extend ClassMethods
job_class.wrap_perform! if job_class.respond_to?(:perform)
end
end
Assuming that you want all of your classes defined before you want perform
run, you may want to use Kernel#at_exit:
Converts block to a Proc object (and therefore binds it at the point of call) and registers it for execution when the program exits. If multiple handlers are registered, they are executed in reverse order of registration.
def do_at_exit(str1)
at_exit { print str1 }
end
at_exit { puts "cruel world" }
do_at_exit("goodbye ")
exit
produces:
goodbye cruel world
You may also want to look at how unit testing frameworks, such as Test::Unit or MiniTest, handle delaying the running of tasks.
精彩评论