包装类方法的模块?

发布于 2024-12-01 12:06:20 字数 731 浏览 0 评论 0原文

是否可以完成这项工作,而不必将模块包含在类的末尾,而只需将其包含在顶部?

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

我想要发生的是让 HelloJob.perform 实际调用 VerboseJob.perform (然后调用块内的原始方法)。因为这里的模块包含在类的顶部,所以这不起作用,因为 perform 尚未定义。将 include 移到最后确实可行,但是有没有一种更宽容的方法?我喜欢将所有包含的模块保留在类定义的顶部。

我正在寻找一些在完全加载时在 ModuleClass 上调用的方法,而不是由运行时解释的方法。

Is it possible to make this 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

不离久伴 2024-12-08 12:06:20

这是我想出的一种相当迂回/黑客的方法,将包装方法的定义推迟到原始方法被定义为止:

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

编辑:

d11wtq 将其简化为更清晰的:

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

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
久而酒知 2024-12-08 12:06:20

假设您希望在运行 perform 之前定义所有类,您可能需要使用 内核#at_exit

将块转换为 Proc 对象(因此将其绑定在该点
调用)并注册它以便在程序退出时执行。如果
注册了多个处理程序,它们以相反的顺序执行
注册。

   def do_at_exit(str1)
     at_exit { print str1 }
   end
   at_exit { puts "cruel world" }
   do_at_exit("goodbye ")
   exit

产生:

再见残酷的世界

您可能还想了解单元测试框架(例如 Test::Unit 或 MiniTest)如何处理任务的延迟运行。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文