Published 2025-09-05.
Last modified 2025-09-16.
Time to read: 5 minutes.
ruby
collection.
Ruby ERB
expands Ruby expressions embedded in HTML or markup such that the encapsulated expression is replaced by its value.
ERB ultimately calls eval
, with some wrapping for safety and binding control.
Nugem
allows ERB expressions within templates,
and creates an internal ArbitraryContextBinding
instance to render templates.
This allows variable and method references in ERB templates to be resolved when
the render
method is called.
Nugem
uses ERB templates to create every type of text file that a Ruby Gem needs.
This includes Ruby code, RSpec tests, markdown files, .gitignore
, and more.
ERB templates support expressions that contain:
- References to
self
- Local, instance, and global variable names
- Local and instance public method names
- Computation using the above
An ERB template consists of a String
expression enclosed in single or double quotes, or provides as a here doc.
object_id
Every Ruby object has a unique identifier.
$ x = Object.new => #<Object:0x00007fcd79d5c9a8>
irb(main):002> x.object_id => 7160
If you find two variables with the same unique identifier,
then they are aliases, or mirrors, for each other.
This information could be helpful as you explore how Ruby Binding
s work.
Bindings
For ERB to work, a mechanism must exist that can look up the value of a variable or obtain a method body from its name.
This mechanism is called a binding.
In Ruby, a Binding
instance is an object that encapsulates the execution context at a specific point in the code.
Each Binding
instance maintains an execution environment,
sometimes referred to as the execution state.
Each Binding
instance in Ruby encapsulates the execution state at the point where it was created.
This includes the current values of local variables, the value of self
, method visibility,
and the context for constant and method lookup.
This execution environment can later be used to evaluate code with eval
.
ERB is not the only thing that uses bindings; debuggers, the Ruby REPL, and code generators also use bindings.
The 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.
Because the top-level scope never contains local variables,
TOPLEVEL_BINDING
does not contain local variables either.
self and main
When Ruby starts, it sets self
to an instance of Object
.
That instance is not bound to a constant or variable named main
.
The string "main"
is just what you see if you print self
:
$ irb irb(main):001> TOPLEVEL_BINDING.eval 'self' => main
irb(main):002> puts TOPLEVEL_BINDING.eval 'main' (eval):1:in `': undefined local variable or method `main' for main:Object (NameError)
main ^^^^ from (irb):18:in `eval' from (irb):18:in `' from /home/mslinn/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/irb-1.15.2/exe/irb:9:in ` ' from /home/mslinn/.rbenv/versions/3.2.2/lib/ruby/site_ruby/3.2.0/rubygems.rb:319:in `load' from /home/mslinn/.rbenv/versions/3.2.2/lib/ruby/site_ruby/3.2.0/rubygems.rb:319:in `activate_and_load_bin_path' from /home/mslinn/.rbenv/versions/3.2.2/bin/irb:25:in ` '
If a binding
is created at the top-level,
then binding’s self.to_s
returns the string "main"
.
self
is an instance of Object
.
There is no separate main
class or module in Ruby;
this output just indicates that the binding currently provides a particular execution context.
No standalone documentation page for main
exists.
Note that IRB starts with TOPLEVEL_BINDING
;
the binding
tends to accumulate things as code is executed.
$ irb irb(main):001> self => main
irb(main):002> self.class => Object
There is no global variable or constant called main
.
The String "main"
is just the default output of to_s
for that particular Object
instance.
TOPLEVEL_BINDING Is Not Empty
The top-level binding is not empty:
$ irb irb(main):001> puts TOPLEVEL_BINDING.eval 'self.public_methods' to_s inspect conf pretty_print_cycle pretty_print pretty_print_inspect pretty_print_instance_variables hash singleton_class dup itself methods singleton_methods protected_methods private_methods public_methods instance_variables instance_variable_get instance_variable_set instance_variable_defined? remove_instance_variable instance_of? kind_of? is_a? display pretty_inspect public_send extend clone <=> class === !~ frozen? then tap nil? yield_self eql? respond_to? method public_method singleton_method define_singleton_method freeze object_id send to_enum enum_for ! equal? __id__ __send__ == != instance_eval instance_exec => nil
main Methods
When Ruby finishes initializing, main
has the mixture of all the public methods of
Object
, Kernel
, and BasicObject
.
-
From
Kernel
:load
,puts
,print
,p
,gets
,raise
,rand
,sleep
, andrequire
. -
From
Object
:object_id
,is_a?
,class
,tap
, andpublic_send
. -
From
BasicObject
:==
,!=
,!
,__send__
, andequal?
.
The full list can be obtained like this:
$ ruby -e 'puts TOPLEVEL_BINDING.eval("self.methods.sort")' ! != !~ <=> == === __id__ __send__ class clone define_singleton_method display dup enum_for eql? equal? extend freeze frozen? hash inspect instance_eval instance_exec instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method methods nil? object_id private_methods protected_methods public_method public_methods public_send remove_instance_variable respond_to? send singleton_class singleton_method singleton_methods tap then to_enum to_s yield_self
Note that the above incantation could have been written without self
,
since it is always implied if a receiver is not explicitly specified:
$ ruby -e 'puts TOPLEVEL_BINDING.eval("methods.sort")'
binding and self
In Ruby, every Binding
instance contains a self
object.
A Binding
object captures the execution context at a specific point in the code,
including the current value of self
,
local variables, instance variables, class variables, global variables,
and all methods.
The following code accesses the self
of a Binding
with Binding#receiver
:
$ irb irb(main):001> x = Object.new => #<Object:0x00007fcd79d5c9a8>
irb(main):002> b = x.instance_eval { binding } => #<Binding:0x00007f973f4d5138>
irb(main):003> puts b.receiver.public_methods.sort ! != !~ <=> == === __id__ __send__ class clone define_singleton_method display dup enum_for eql? equal? extend freeze frozen? hash inspect instance_eval instance_exec instance_of? instance_variable_defined? instance_variable_get instance_variable_set instance_variables is_a? itself kind_of? method methods nil? object_id pretty_inspect pretty_print pretty_print_cycle pretty_print_inspect pretty_print_instance_variables private_methods protected_methods public_method public_methods public_send remove_instance_variable respond_to? send singleton_class singleton_method singleton_methods tap then to_enum to_s yield_self
You can also evaluate self
within the Binding
:
irb(main):003> puts binding.eval 'self' => main
irb(main):004> binding.eval 'self.singleton_methods' => [:to_s, :inspect, :conf]
irb(main):005> binding.eval 'self.local_variables' => [:b, :x, :_]
irb(main):006> binding.eval 'self.instance_variables' => [] irb(main):007> puts binding.eval 'self.global_variables' $-p $-l $-a $@ $; $-F $-I $: $" $LOAD_PATH $LOADED_FEATURES $-v $VERBOSE $-W $-w $-d $DEBUG $PROGRAM_NAME $0 $& $` $' $+ $= $? $$ $stdin $stdout $> $stderr $DEBUG_RDOC $_ $~ $! $/ $, $\ $-0 $< $. $FILENAME $-i $* => nil %}
my_binding.
is not equivalent to my_binding.
or
my_binding.
.
my_binding.
returns an array of the names of local variables available in the context captured by the binding.
my_binding.
calls the local_variables
method on the object that is self
in the binding.
This usually returns the local variables in the current scope of that object, which is typically not the same as those
in the binding unless you are in the same context.
For example:
$ irb irb(main):001> x = 42 => 42
irb(main):002> b = binding => #<Binding:0x000073a8689ec918>
irb(main):003> b.local_variables => [:b, :x, :_]
irb(main):004> b.receiver.send 'local_variables' => [:b, :x, :_]
Leaky Top-Level Definitions
The top-level binding context is the outermost scope, and is not encapsulated within any class or module.
The hello
method shown below is defined at the top-level binding context / outermost scope.
Its method definition exists separately in the top-level binding context,
distinct from all other entries.
def hello
'hi'
end
p main.method(:hello) # => #<s;Method: Object#hello>
As you can see, hello
is not shown as a method of main
,
it is an instance method of Object
,
available everywhere because every object inherits from Object
.
Promiscuous top-level definitions such as the leakage from the main
instance to the Object
definition are said to leak globally.
All top-level definitions leak, including methods, constants, local variables, and instance variables.
The precise mechanism varies for each type of definition, but the result is the same:
definitions propagating through Object
to everything inheriting from Object
.
To prevent accidental leakage, which would pollute the global namespace,
wrap your definitions within a module or class instead of defining them at the top level.
Variable and Method Resolution
Here’s how variable and method resolution works:
- If a name matches a local variable in the binding, the value is returned.
-
Ruby next tries to resolve instance variables and method references on the current
self
object. Instance variables are only accessible if the binding was created inside an object where they exist. Methods are accessible if the object in the binding responds to them. - Ruby then looks up constants through the normal constant lookup rules.
-
If no suitable binding is found, ERB falls back to
TOPLEVEL_BINDING
.
Interrogating a Binding
To list methods and variables in a Ruby binding you can use two different syntaxes:
$ irb irb(main):001> my_binding.local_variables # Preferred => [:my_binding, :_]
irb(main):003> my_binding.eval 'local_variables' => [:my_binding, :_]
my_binding.instance_variables
The binding and the object it is attached to are distinct.
This means they have separate methods and variables.
You can examine the public methods of a Binding
instance like this:
my_binding.public_methods
To see the methods of the object the binding is attached to,
first access binding.receiver
(this is self
within the Binding
instance):
my_binding .receiver .public_methods .sort
Example ERB
The following simple example shows a template String
being
interpreted according to the local binding
.
1: require 'date' 2: require 'erb' 3: 4: name = 'Mike' 5: age = ((Date.today - Date.civil(1956, 10, 15)) / 365).to_i 6: template = '<%= name %> is <%= age %> years old.' 7: 8: erb = ERB.new template 9: puts erb.result binding
The above code contains two ERB expressions in the template on line 6.
Both ERB expressions implicitly use the binding
to look up the
value of the given variable name or method name.
When I ran the above, the output was:
Mike is 68 years old.
The remainder of this article is a work in progress.
Purpose of custom_binding
It is a good practice to evaluate ERBs in a different portion of a code base than where the computation might be performed. For example, Model-View-Controller architectures are based on this style of programming. However, this means that the variables and methods that the ERB needs to evaluate are defined in different binding contexts than where they are used.
custom_binding
to the rescue!
It allows you to define a virtual binding context consisting of a collections of objects, modules, and a given binding.
Method and variable references are resolved by iterating through the entire virtual binding context until the desired reference is found.
ArbitraryContextBinding#
invokes ERB#
with the appropriate binding.
The custom_binding
RSpec tests
show how to call ArbitraryContextBinding#
,
which in turn invokes ERB#
with the appropriate binding
object.
Here is the
GitHub’s online editor
for the custom_binding
project.
Usage
Good Examples
Some of the RSpec tests are shown below as regular code to simplify the examples.
This code is provided in the
playground
directory of the custom_binding
Git project.
This Ruby source file defines modules and objects that the ERB will reference. Notice that every class and variable is defined within a module; none are top-level definitions.
require_relative '../lib/acb_class' module TestHelpers def self.version = '9.9.9' def self.helper = 'helper called' def self.greet(name) = "Hello, #{name}!" def self.with_block yield 'block arg' end end module OtherHelpers def self.helper = 'other helper' end module DefineStuff # include CustomBinding repository_class = Struct.new(:user_name) project_class = Struct.new(:title) @repository = repository_class.new('alice') @project = project_class.new('cool app') obj1 = Struct.new(:foo).new('foo from obj1') obj2 = Struct.new(:bar).new('bar from obj2') obj3 = Struct.new(:foo).new('foo from obj3') @acb_all = CustomBinding.new( objects: [obj1, obj2, obj3], modules: [TestHelpers], base_binding: binding ) def self.acb_all @acb_all end end
The following Ruby code uses the above definitions to expand a template.
Note that DefineStuff.
calls ERB#render
.
require_relative 'define_stuff'
module GoodExample
template = 'User: <%= @repository.user_name %>, Project: <%= @project.title %>'
puts DefineStuff.acb_all.render template # Displays 'User: alice, Project: cool app'
end
Bad Example
This is an example of how NOT to write Ruby code:
require_relative 'define_stuff' # This is an example of defining a top-level object: acb_all = CustomBinding.new( objects: [], modules: [Blah, TestHelpers] ) # Do not define top-level objects in Ruby unless you have a license ;) # Look at usage_good.rb for a positive example template = 'User: <%= @repository.user_name %>, Project: <%= @project.title %>' puts acb_all.render template # Displays 'User: alice, Project: cool app'