理解Ruby的TOPLEVEL_BINDING

4

我了解TOPLEVEL_BINDING是主要绑定对象。以下代码证实了这一点:

def name
  :outer
end

module Test
  class Binder
    def self.name
      :inner
    end

    def self.test_it
      eval 'name', TOPLEVEL_BINDING
    end
  end
end

p Test::Binder.test_it # => :outer

我在查看Rack的源代码时感到困惑。问题出在理解文件lib/rack/builder.rb中的这段代码上。

def self.new_from_string(builder_script, file="(rackup)")
  eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
    TOPLEVEL_BINDING, file, 0
end

def run(app)
end

新的`from_string`方法需要传递一个`config.ru`文件的内容,该文件类似于:
run DemoApp::Application

在这里,TOPLEVEL_BINDING似乎是指一个Builder对象,因为方法“run”是为Builder而不是Object定义的。然而,早期的实验表明TOPLEVEL_BINDING指的是main的binding。我不理解run方法在这里是如何工作的。请帮助我理解这段代码。
2个回答

2

TOPLEVEL_BINDING 顶层绑定。

那个方法将字符串"run ..."传递到 Builder.new { run ... }

然后 Builder.new 对该块执行实例评估 (https://github.com/rack/rack/blob/df1506b0825a096514fcb3821563bf9e8fd52743/lib/rack/builder.rb#L53-L55),从而使块内的代码直接访问实例方法。

def initialize(default_app = nil,&block)
  @use, @map, @run, @warmup = [], nil, default_app, nil
  instance_eval(&block) if block_given?
end

run 是 Builder 类的一个实例方法,在这里定义 -> https://github.com/rack/rack/blob/df1506b0825a096514fcb3821563bf9e8fd52743/lib/rack/builder.rb#L103-L105

def run(app)
  @run = app
end

简而言之,"run DemoApp::Application" 变成了:
Rack::Builder.new {
  run DemoApp::Application
}.to_app

编辑:以下是一个简单的例子来说明这个问题:

class Builder
  def initialize(&block)
    instance_eval(&block)
  end

  def run(what)
    puts "Running #{what}"
  end
end

TOPLEVEL_BINDING.eval "Builder.new { run 10 }"

打印
Running 10

问题是你无论是否使用TOPLEVEL_BINDING.eval,都会得到“Running 10”。看起来rack开发人员试图为应用程序提供干净的环境,但如果有人问我否则可能出什么问题......我没有答案。你呢? - x-yuri

0

TOPLEVEL_BINDING 可以让你访问在第一个文件被执行时的上下文

a.rb:

a = 1
p TOPLEVEL_BINDING.local_variables  #=> [:a]
require_relative 'b'

b.rb:

b = 1
p TOPLEVEL_BINDING.local_variables  #=> [:a]

$ ruby a.rb

Rack 开发人员显然试图实现的是将构建器行/脚本与从 Rack::Builder.new_from_string 访问的所有内容(局部变量、方法等)隔离开来。

还有一个注释 这些天

    # Evaluate the given +builder_script+ string in the context of
    # a Rack::Builder block, returning a Rack application.
    def self.new_from_string(builder_script, file = "(rackup)")
      # We want to build a variant of TOPLEVEL_BINDING with self as a Rack::Builder instance.
      # We cannot use instance_eval(String) as that would resolve constants differently.
      binding, builder = TOPLEVEL_BINDING.eval('Rack::Builder.new.instance_eval { [binding, self] }')
      eval builder_script, binding, file

      return builder.to_app
    end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接