在ruby中具有环绕体的古怪

在ruby中具有环绕体的古怪,ruby,metaprogramming,Ruby,Metaprogramming,我对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

我对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
我的代码如下所示

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
时,我希望它打印出来 前,测试和后“按顺序。但现在它打印“前,后和测试”

问题是,当我执行
Test.new.Test
时,我希望它打印出来 前、测试和后“按顺序”。但现在它打印“之前、之后和测试”

不,没有。调用
Test.new.Test
时,它只打印
Test
<定义包装方法时(即调用
around\u advice
时),会打印
之前
之后

尝试在调用
通知
和调用
Test.new.Test
(并尝试调用
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
  ]
每次调用方法时都需要调用它们:

  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
  ]
然而,只使用
模块#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
问题是,当我执行
Test.new.Test
时,我希望它打印出来 前,测试和后“按顺序。但现在它打印“前,后和测试”

不,不需要。当调用
Test.new.Test时,它只在定义包装方法时(即调用
环绕_advice
时)打印
Test
之前的
之后的

尝试在调用
通知
和调用
Test.new.Test
(并尝试调用
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
  ]
每次调用方法时都需要调用它们:

  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
  ]
然而,只使用
模块#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 

只是把我的代码放在这里。这就是为什么我最终实现了我想做的事情,上面建议的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 

为什么要
优化模块
而不是将
模块.new{def around_aspect…end}
直接添加到
测试.singleton_类
?!不确定
过程。()
调用表示法在这里是非常传统的。还要记住,这个包装函数的返回值现在已被破坏,需要捕获并返回调用
super
的结果。@tadman:这与OP的行为相匹配。一个环绕的建议应该能够更改返回值。但是,在OP的代码中after块无法检查返回值,因此它不能做很多事情。@JörgWMittag您是对的,对before和after的调用是在定义时间而不是在我的代码中的调用时间进行计算的,但我不知道为什么。我正在计算表达式#{before_proc.call}在我的方法定义中,由于我正在使用class_eval,该方法是当前类的一部分,所以当我在类的实例上调用该方法时,不应该执行它。不确定发生了什么on@AbdulRahman:您正在插入表达式
{before_proc.call}
转换为代码字符串。字符串插值在解析字符串时发生。并且只发生一次。该插值的结果仅为
nil
,使用
转换为
(当被插值的对象还不是字符串时,字符串插值始终使用
转换为
).
nil.to_s
只是一个空字符串,因此在生成的代码中没有建议的痕迹,唯一可以观察到的是插入字符串时的副作用(
put
)。为什么要
细化模块
而不是预加
Module.new{def around\u aspect…end}
直接到
测试。singleton_类
?!不确定
过程。()
调用表示法在这里是非常传统的。还要记住,这个包装函数的返回值现在已被破坏,需要捕获并返回调用
super
的结果。@tadman:这与OP的行为相匹配。一个环绕的建议应该能够更改返回值。但是,在OP的代码中after块无法检查返回值,因此它不能做很多事情。@JörgWMittag您是对的,对before和after的调用是在定义时间而不是在我的代码中的调用时间进行计算的,但我不知道为什么。我正在计算表达式#{before_proc.call}在我的方法定义中,由于我正在使用class_eval,该方法是当前类的一部分,所以当我在类的实例上调用该方法时,不应该执行它。不确定发生了什么on@AbdulRahman:您正在插入表达式
{before_proc.call}
转换为代码字符串。字符串插值在解析字符串时发生。并且只发生一次。该插值的结果仅为
nil
,使用
转换为
(当被插值的对象还不是字符串时,字符串插值始终使用
转换为
).
nil.to_s
只是一个空字符串,因此在生成的代码中没有通知的痕迹,唯一可以观察到的是副作用(
put
)在插入字符串时。如果使用相关的
send
call调用将其转换为
define_方法,则可以在此处完全避免
eval
。如果使用相关的
send
call将其转换为
define_方法,则可以在此处完全避免
eval
e> 召唤