Goal: To understand the concept of binding in Ruby.

Binding is an elegant way to access the current scope (variables, methods, and self) in Ruby. Typically, you use it for building view templates and executing strings of Ruby code. The Ruby REPL also makes abundant use of binding.

The basic idea behind binding is to store the current context in an object for later use. Later, you can execute some code in the context of that binding, using eval.

A Ruby binding is an instance of the Binding class. It's an object that packages or encapsulates the current scope, allowing you to pass it around in your code.

Objects of class Binding encapsulate the execution context at some particular place in the code and retain this context for future use. The variables, methods, and value of self that can be accessed in this context are all retained. Binding objects can be created using Kernel#binding. - Ruby Docs

The Kernel#binding method returns the current binding object. Think of this binding object as a wrapper that encapsulates the current programming environment, i.e. variables, methods, and even the self object.

The most common and realistic use case for a binding object is to use it later to fill some pre-defined slots in a template, like ERB views. So you save the current context including variables in a binding, and use it somewhere else to generate the final views, just like Rails.

Evaluating Ruby Code in Different Context

The Kernel#eval method takes a string that contains Ruby code, executes it, and returns the result. The following code snippet defines a method named run that takes an argument named task.

eval "
  def run(task)
    puts 'running task: ' + task
  end
"

run "compile"

# Output
#
# running task: compile

However, you can pass a binding object as the second argument of the Kernel#eval method. This establishes the environment for the evaluation.

def some_other_context
  word = "Rails"
  return binding  # calls Kernel#binding method
end

word = "Ruby"
eval "puts 'hello ' + word"  # hello Ruby
eval "puts 'hello ' + word", some_other_context  # hello Rails

The following example illustrates how eval returns a different value for the language variable for different bindings.

language = "JavaScript"

eval("puts language", binding)  # JavaScript

def get_binding
  language = "C-Sharp"
  binding
end

new_binding = get_binding
eval("puts language", new_binding)  # C-Sharp

class Ruby
  def get_binding
    language = "Ruby"
    binding
  end
end

ruby_binding = Ruby.new.get_binding
eval("puts language", ruby_binding)  # Ruby

If you check out the source code of the IRB gem, you'll notice that it makes abundant use of the bindings.

Top-Level Binding

Ruby provides a constant called TOPLEVEL_BINDING that returns the binding of the top-level scope. Use it to access the top-level scope from anywhere in the program.

def run_ruby
  puts "running inside main"
end

class Ruby
  def run_ruby
    puts "running inside Ruby"
  end

  def exec
    eval "run_ruby", binding           # running inside Ruby
    eval "run_ruby", TOPLEVEL_BINDING  # running inside main
  end
end

Ruby.new.exec

# Output
#
# running inside Ruby
# running inside main

I hope these examples have given you a good understanding of how binding works in Ruby.

In the next lesson, we'll use binding and ERB to make our HTML views dynamic.