Skip to content

Instantly share code, notes, and snippets.

@JoshCheek
Last active June 3, 2018 02:24
Show Gist options
  • Save JoshCheek/add98df43634e7a79c764c90692956ca to your computer and use it in GitHub Desktop.
Save JoshCheek/add98df43634e7a79c764c90692956ca to your computer and use it in GitHub Desktop.
How to get a binding for ERB
require 'erb'
class Num
def initialize(num)
@n = num
end
end
# okay, some object we want to evaluate our ERB within,
# to do that we need a binding for it
num = Num.new 2
# we're in main, so even though we call `binding` on `num`
# it's a binding to our current scope... sure feels like a bug
num.send(:binding).receiver # => main
# you can get a binding off the obj with something like this:
num.instance_eval { binding }.receiver # => #<Num:0x00007fa8ba0c5188 @n=2>
# but note that we call `binding` in a block there, and blocks are closures,
# so that binding has access to our local variables.
num.instance_eval { binding }.local_variables # => [:num, :b]
# NOTE:
# it sees "b", which is defined below, due to implementation details...
# Ruby does allocate space for the var, like JS hoisting, which is why
# we can already see it, but it will error if you try to access it
# before initializing it, unlike JS hoisting
# soooo, we need a pristine binding, which methods have
# (methods in Ruby are not closures), so we'll go into a method
# to get a pristine outter binding and use our trick from there.
# but how do we get the object, if we can't use locals?
# we store it as an ivar (you could also pass it via an implicit block)
class GetBinding
def initialize(obj)
@obj = obj
end
def call
@obj.instance_eval { binding }
end
end
# there, now we have a pristine binding on the right object
b = GetBinding.new(num).call
b.receiver # => #<Num:0x00007fa8ba0c5188 @n=2>
b.local_variables # => []
# if setting locals is something you want to do, as a feature of your lib:
b.local_variable_set :l, 3
b.local_variables # => [:l]
# and now, we can finally pass it to `ERB` *whew*
# (note that IDK if anything is changed by omitting the rest of the init args)
ERB.new('1 <%= @n %> <%= l %> 4').result(b)
# => "1 2 3 4"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment