在 method_added 中调用了未定义的自模块方法

Undefined self module method called in method_added

我正在尝试为 class 方法实现一个 hooker,比如 before_actionafter_action。问题是,如果我在模块内定义一个方法,define_method 或通常在 method_added 内使用 def do_before; puts 'do_before called'; end 则未定义。那么如何在 method_added 中调用 module method

module Hooker

  [:before, :after].each do |element|
    define_method("#{element}_action") do |name|
      puts "#{element}_action called with parameter #{name}"
    end
    define_method("do_#{element}") do
      puts "do_#{element} called"
    end
  end
  def method_added(name)
    return if @filtering # Don't add to original_ methods
    @filtering = true

    alias_method :"original_#{name}", name
    define_method name do |*args|
      do_before # undefined method `do_before' for #<Bar:0x007ff2f183c318>
      self.send("original_#{name}", *args)
      do_after # undefined method `do_after' for #<Bar:0x007ff2f183c318>
    end

    @filtering = false
  end
end

class Bar
  extend Hooker

  before_action 'foo2'
  after_action 'bar2'

  def my_func
    puts 'MyFunc called'
  end
end

Bar.new.my_func

那是因为你正在使用 - extend

adds the specified module's methods and constants to the target's metaclass

但你还需要 - include

it mixes in the specified module's methods as instance methods in the target class

# some code goes here

class Bar
  extend Hooker
  include Hooker

  before_action 'foo2'
  after_action 'bar2'

  def my_func
    puts 'MyFunc called'
  end
end

Bar.new.my_func
=> before_action called with parameter foo2
=> after_action called with parameter bar2
=> do_before called
=> MyFunc called
=> do_after called

更清楚的是将其分离到不同的模块。

这是一个替代解决方案,您无需在 class:

中显式调用 extendinclude
module Hooker
  def self.included(klass)
    klass.extend ClassMethods
  end

  module ClassMethods
    [:before, :after].each do |element|
      define_method("#{element}_action") do |name|
        puts "#{element}_action called with parameter #{name}"
      end
      define_method("do_#{element}") do
        puts "do_#{element} called"
      end
    end
  end

  def method_added(name)
    return if @filtering # Don't add to original_ methods
    @filtering = true

    alias_method :"original_#{name}", name
    define_method name do |*args|
      do_before
      self.send("original_#{name}", *args)
      do_after
    end

    @filtering = false
  end
end

class Bar
  include Hooker

  before_action 'foo2'
  after_action 'bar2'

  def my_func
    puts 'MyFunc called'
  end
end

Bar.new.my_func

这里的技巧是 Hooker 模块现在 extending though include.