开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜