ruby 周围的怪异现象

Weirdness with around aspect in ruby

我是新手 ruby

正在尝试写一个环绕方面。我的代码如下

我的代码如下所示

module Utils
  module Aspects

    def self.included(base)
      base.extend(self)
    end

    def around_aspect(method_name, before_proc, after_proc)

      code = %Q[
        def #{method_name} *args, &block
          #{before_proc.call}
          old_#{method_name} *args, &block
          #{after_proc.call}
        end
      ]

      class_eval %Q[
        alias_method :old_#{method_name}, :#{method_name}
      ]

      class_eval code
    end

    # def before_aspect method_name, before_proc
    #   around_aspect method_name, before_proc, ->(){}
    # end
    #
    # def after_aspect method_name, after_proc
    #   around_aspect method_name, ->(){}, after_proc
    # end
  end
end


class Test
  include Utils::Aspects

  def test
    puts 'test'
  end

  before = ->(){puts 'before'}
  after = ->(){puts 'after'}
  around_aspect :test,before,after
end

Test.new.test

问题是当我这样做时 Test.new.test 我希望它打印出来 before, test and after" 按顺序排列。但现在它打印 "before,after and test"

The problem is that when i do Test.new.test I expect it to print before, test and after" in order. But right now it prints "before,after and test"

不,不是。当调用 Test.new.test 时,它只打印 testbeforeafter 在定义包装方法时打印,即在调用 around_advice.

尝试在对 around_advice 的调用和对 Test.new.test 的调用之间放置一个 puts(并尝试多次调用 test)以观察这一点:

puts '______________________'

Test.new.test
Test.new.test

# before
# after
# ______________________
# test
# test

您只调用了一次 lambda,在定义方法时:

  code = %Q[
    def #{method_name} *args, &block
      #{before_proc.call}
#     ^^^^^^^^^^^^^^^^^^^
      old_#{method_name} *args, &block
      #{after_proc.call}
#     ^^^^^^^^^^^^^^^^^^
    end
  ]

每次调用方法时都需要调用它们:

  code = %Q[
    def #{method_name} *args, &block
      before_proc.call
      old_#{method_name} *args, &block
      after_proc.call
    end
  ]

但是,直接使用 Module#prepend 会容易得多,毕竟,这就是它的用途:

module Aspects
  refine Module do
    def around_aspect(method_name, before_proc, after_proc)
      prepend(Module.new do
        define_method(method_name) do |*args, &block|
          before_proc.()
          super(*args, &block)
          after_proc.()
        end
      end)
    end
  end
end

class Test
  using Aspects

  def test
    puts 'test'
  end

  before = -> {puts 'before'}
  after = -> {puts 'after'}
  around_aspect :test, before, after
end

只是把我的代码放在这里。这就是我最终实现我想要做的事情的方式,module.prepend 上面建议的是另一种方式

module Utils
  module Aspects

    def self.included(base)
      base.extend(self)
    end

    def around_aspect(method_name, before_proc, after_proc)

      new_method_name = Random.new_seed.to_s

      alias_method :"#{new_method_name}", :"#{method_name}"

      define_method "#{method_name}" do |*args, &block|
        before_proc.call
        send(:"#{new_method_name}", *args, &block)
        after_proc.call
      end
    end

    def before_aspect method_name, before_proc
      around_aspect method_name, before_proc, ->(){}
    end

    def after_aspect method_name, after_proc
      around_aspect method_name, ->(){}, after_proc
    end
  end
end


class Test
  include Utils::Aspects

  def test

    puts 'test'
  end

  before = ->(){puts 'before'}
  after = ->(){puts 'after'}

  before_aspect :test, before
  after_aspect :test, after
end


Test.new.test