在 ruby 中,如何将原始源代码加载到 Proc 的实例?(将 `before` `after` 钩子添加到 class 方法)

In ruby, how to load raw source code to an instance of Proc?(add `before` `after` hook to class methods)

通常,我们可以用 Proc 个对象来做到这一点:

[15] pry(main)> pr = -> { puts "test message" }                                                                                                                                                                                                                          
=> #<Proc:0x000000057b8a90@(pry):12 (lambda)>
[16] pry(main)> pr.call                                                                                                                                                                                                                                                  
test message
=> nil
[17] pry(main)> define_method("test_method", pr)                                                                                                                                                                                                                         
=> :test_method
[18] pry(main)> test_method                                                                                                                                                                                                                                              
test message

但是,如果我有原始代码字符串并想将其加载到过程中怎么办?下面是假代码:

raw_code = "puts 'test message'"
pr = -> { load(raw_code) }   # how to define the `load` method to get this working?
pr.call   # => test message

define_method("test_method", pr}
test_method  # => test message

实际上,我最初的问题是如何编写这样的方法钩子:

class TestClass
  def test_method
    puts url
  end
  def test_method_a
    puts url
  end
  before :test_method, :test_method_a do
    url = __method__.split("_").join("/")
  end
end

TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"

我的问题比较复杂,这只是一个简单的例子来说明关键问题。

简短版本:load 文件 加载代码。如果你想 运行 代码,你已经在一个字符串中,你可以使用 eval,或者它的朋友之一,class_evalinstance_eval.

如果您最终使用 eval,但是,您需要非常小心,以免您不小心 运行 代码删除您的文件、安装恶意软件或其他任何东西。

更长的版本:对于 load 的工作版本(在 ruby 2.2.3 中),您需要将 TestClass class 放入文件中:

class TestClass
  def test_method
    puts "OHAI"
  end
end

让我们将此 class 保存在名为 "test_class.rb" 的文件中。

有了这个,下面的应该就可以了:

pr = -> { load(File.join(__dir__, "test_class.rb")) }
pr.call
TestClass.new.test_method

这不会解决您的 "original problem",但它应该能让您更好地理解 load 的工作原理。

how to define the load method to get this working?

通过拼写 eval:

raw_code = "puts 'test message'"
pr = -> { eval(raw_code) }   # how to define the `load` method to get this working?
pr.call   # => test message

define_method("test_method", pr)
test_method  # => test message

--output:--
test message
test message

Actually, my original problem is how to write a method hook...

class TestClass
  def initialize
    @url = %q{__method__.to_s.split("_").join("/")}
  end

  def test_method
    puts(eval @url)
  end

  def test_method_a
    puts(eval @url)
  end
end

TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"

--output:--
test/method
test/method/a

Actually, my original problem is how to write a method hook like this:

Module TestClass
  def test_method
    puts url
  end

问题是 url 永远不能引用 def 之外的值。 def 切断了 def 之外局部变量的可见性。

class TestClass

  def test_method
    puts @url
  end

  def test_method_a
    puts @url
  end

  def self.before(*method_names, &block)
    method_names.each do |method_name|
      alias_method "first_#{method_name}", method_name
      define_method(method_name, block) #This changes __method__ inside the block from nil to method_name

      alias_method "second_#{method_name}", method_name
      define_method(method_name) do
        send "second_#{method_name}"  #This method executes: @url = __method__.to_s.split(...
        send "first_#{method_name}"   #This method executes: puts @url
      end
    end
  end

  before :test_method, :test_method_a do
    @url = __method__.to_s.split("_").join("/")
  end
end

TestClass.new.test_method # => "test/method"
TestClass.new.test_method_a # => "test/method/a"

--output:--
test/method
test/method/a

阅读回复后,我自己的解决方案和更多详细信息显示在此处:

也许有很多人不明白为什么我的问题 method hookload raw source code to Proc instance 有关系。

而且,我现在已经解决了我的问题。 那么,让我详细解释一下整个事情:

1,我原来的问题来自于我需要在模块的某些方法的头部提取一堆重复的代码,就像这样:

module TestClass
  extend self

  def test_method
    url = __method__.to_s.split("_").join("/")  # duplicated code,need to extract
    puts url
  end

  def test_method_a
    url = __method__.to_s.split("_").join("/")  # duplicated code, need to extract
    puts url
  end

2、然后想了很多,想出一个办法,就是获取test_methodtest_method_a的源码,修改源码加入url = __method__.to_s.split("_").join("/") 在它的开头,然后用新代码重新定义方法。

3,经过大量破解,我使用 eval 失败,然后,post 在这里寻求帮助。

4、看完回复,确定eval绝对是我想要的

5、终于成功了,代码如下:

 module TestClass
  extend self

  def test_method
    puts url
  end

  def test_method_a
    puts url
  end

  [ :test_method, :test_method_a ].each do |name|
    method_source_code_ar = instance_method(name).source.split("\n")

    method_source_code_ar.insert(1, 'url = __method__.to_s.split("_").join("/")')

    method_source_code = method_source_code_ar[1..-2].join("\n")

    define_method(name, -> { eval(method_source_code) })
  end

end

TestClass.test_method  # => test/method
TestClass.test_method_a  # => test/method/a

6、更简洁的代码版本展示:

module TestClass
  extend self

  def test_method
    puts url
  end

  def test_method_a
    puts url
  end

  [ :test_method, :test_method_a ].each do |name|
    method_source_code_ar = instance_method(name).source.split("\n")

    method_source_code_ar.insert(1, 'url = __method__.to_s.split("_").join("/")')

    method_source_code = method_source_code_ar.join("\n")

    eval(method_source_code)
  end

end

7,作为eval,对我来说,我认为理解它的关键是,不要像你在写模式那样思考,而是好像代码是逐行运行直到到达 eval 行代码,到那时,您希望代码做什么?当然,只是 eval method_source_code 字符串。