Binding
Binding.new, binding() The Binding object represents the current scope in Ruby. It encapsulates local variables, constants, method visibility, and self, allowing you to capture and later evaluate code in that context.
Creating Bindings
# Get current binding
b = binding
# From specific scope
def get_binding
local_var = "hello"
binding
end
b = get_binding
The two approaches above show how to obtain a binding: either call binding at the point where you want to capture the current scope, or return it from a method that sets up specific local variables. The Kernel#binding method is available everywhere and captures the caller’s scope at the exact moment it is called. Returning a binding from a method lets you encapsulate the setup logic, so the caller receives a pre-configured context without needing to know which variables were set.
Practical Examples
Eval with Binding
# Evaluate code in a specific context
x = 10
b = binding
eval("x + 5", b) # => 15 (uses x from binding)
The eval example above shows the core idea: you capture the current scope into a binding, then later evaluate code as if it were running at the capture point. The expression x + 5 resolves x from the binding’s environment, not from wherever eval is called. This lets you carry a snapshot of local state across method boundaries without passing each variable individually as an argument.
Capturing Variables
def create_closure
secret = "hidden"
binding
end
b = create_closure
puts b.local_variables # => [:secret]
# Access variable through binding
value = b.local_variable_get(:secret)
puts value # => "hidden"
The local_variable_get method reads a variable from the binding by name, which lets you extract values from a captured scope without using eval. Paired with local_variables to discover what is available, this gives you a read-only window into the captured context. It is a safer alternative to eval when you only need to inspect values rather than execute arbitrary code in the captured scope.
Passing Context
def execute_in_context(code, ctx)
eval(code, ctx)
end
name = "Ruby"
ctx = binding
execute_in_context("puts \"Hello, #{name}!\"", ctx)
Passing a binding as an argument lets you defer code evaluation to a different method while preserving the original scope. The execute_in_context helper receives both the code string and the captured context, so the string interpolation picks up name from the caller’s scope rather than from the helper’s own locals. This cleanly separates the decision of what code to run from the decision of where to run it, which is the foundation of many DSL and plugin architectures in Ruby.
Method Visibility
class MyClass
private
def secret_method
"hidden"
end
def public_binding
binding
end
end
obj = MyClass.new
b = obj.public_binding
# Can call private method through binding
eval("secret_method", b) # => "hidden"
The method visibility example is particularly instructive because it shows that a binding bypasses normal access controls. A private method that is normally unreachable from outside the class becomes callable through eval with the right binding. This is both powerful and potentially dangerous: it makes bindings useful for testing and metaprogramming but also means you should be careful about passing bindings to untrusted code.
Template Systems
class Template
def render(template, locals = {})
b = binding
locals.each { |k, v| b.local_variable_set(k, v) }
eval("\"#{template}\"", b)
end
end
t = Template.new
puts t.render("Hello, #{name}!", name: "World")
The template example above injects local variables into a binding using local_variable_set, then evaluates a string with interpolation. This is essentially how ERB and similar templating engines work under the hood: they capture the current binding, populate it with the variables the template needs, and evaluate the template code in that context, which is why template variables feel like they are just regular local variables.
Debugging
def debug_scope
puts "Local variables: #{binding.local_variables}"
puts "Self: #{binding.receiver}"
puts "File: #{binding.source_location}"
end
The debugging example uses source_location to report where the binding was created, which is invaluable when tracing through framework code or DSLs that capture scope implicitly. Knowing the file and line number of the binding’s origin helps you understand which variables are in scope without having to read through every method in the call chain.
Binding Methods
# Get local variable
binding.local_variable_get(:name)
# Set local variable
binding.local_variable_set(:name, "value")
# Check if variable exists
binding.local_variable_defined?(:name)
# Get all local variables
binding.local_variables
These four methods — local_variable_get, local_variable_set, local_variable_defined?, and local_variables — form the inspection API for bindings. They let you read and write the captured scope from outside the original method, which is the foundation for debugging tools and template engines that need to inject values into a pre-captured context before evaluation.
Source Location
# Where was this binding created?
location = binding.source_location
# => ["/path/to/file.rb", 10] [file, line]
Use Cases
- Implementing template engines
- Domain-specific languages (DSLs)
- Debugging tools
- Code evaluation in restricted contexts
- Capturing closures with state
using bindings carefully
Binding is powerful because it captures the current scope exactly as Ruby sees it, but that also means the object should be handled with care. It is a great fit for debugging, templating, and carefully controlled evaluation, yet it can make code harder to follow if it becomes the main way data moves through the program. Keeping the binding use local and explicit helps the rest of the code remain normal Ruby.
It also helps to treat a binding as a snapshot rather than a general-purpose container. The more the code depends on hidden local names or implicit scope, the harder it becomes to understand what the program needs. A clear boundary around the binding usually makes the surrounding methods simpler and more predictable.
keeping scope boundaries obvious
When a binding is passed around, the reader has to think about both the current method and the captured context. That is fine for a debugger or a small DSL, but it can become hard to trace if the object travels too far. A good rule is to create the binding as close as possible to the point where it will be used and to avoid hiding it inside layers of helper methods. That keeps the scope visible and reduces surprises.
using bindings for focused evaluation
A binding is most comfortable when it serves a single, narrow purpose such as evaluating a template or inspecting a local variable set in a console session. That keeps the object easy to explain and prevents it from becoming the hidden center of the program. If the code starts to depend on a binding for routine data flow, it is usually a sign that a plain method call or a small object would be easier to maintain.