Search code examples
rubyrack

Understanding ruby TOPLEVEL_BINDING


I understand that TOPLEVEL_BINDING is the Binding object for main. The following code confirms it:

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

I got confused while looking at the source for rack. The problem was in understanding this code in the file 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

The new_from_string method is passed the contents of a config.ru file which will be something like

run DemoApp::Application

Here it seems like TOPLEVEL_BINDING is referring to a Builder object, since the method run is defined for Builder but not for Object. However the earlier experiment establishes that TOPLEVEL_BINDING refers to main's binding. I do not understand how run method is working here. Please help me in understanding this code.


Solution

  • TOPLEVEL_BINDING is the top level binding.

    That method is passing the string "run ..." into Builder.new { run ... }

    Builder.new then does an instance_eval (https://github.com/rack/rack/blob/df1506b0825a096514fcb3821563bf9e8fd52743/lib/rack/builder.rb#L53-L55) on the block, thereby giving the code inside the block direct access to the instance's methods.

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

    run is an instance method of the Builder class, defined here -> https://github.com/rack/rack/blob/df1506b0825a096514fcb3821563bf9e8fd52743/lib/rack/builder.rb#L103-L105

    def run(app)
      @run = app
    end
    

    In short, "run DemoApp::Application" becomes:

    Rack::Builder.new {
      run DemoApp::Application
    }.to_app
    

    Edit: A simple example to illustrate the point:

    class Builder
      def initialize(&block)
        instance_eval(&block)
      end
    
      def run(what)
        puts "Running #{what}"
      end
    end
    
    TOPLEVEL_BINDING.eval "Builder.new { run 10 }"
    

    prints

    Running 10