17

I'm rather new to Ruby, and so far, figuring out how to use "binding" objects is one of the biggest pain points for me. If I'm reading the documentation correctly, they're almost entirely opaque. To access the scope inside the binding object, you have to have a string of Ruby code and eval it using the binding.

Maybe I'm just a purist from a different school, but I'm allergic to string-based 'eval' constructs, generally speaking. Is there any way to do any of the following, securely and in the general case, given a binding object:

  1. List the identifiers in scope in the context the binding represents, or retrieve a hash of the contents.
  2. Set the value of a local variable in the binding equal to that of some local variable in an external context. Ideally, this should work generally, even if the value is an object reference, file handle, or some other complex entity.
  3. (extension 2:) Given a hash, set locals in the binding for each entry.
  4. Better yet, given a hash build a binding with only the basic language constructs and the names in the hash in scope.

Basically, I want to know which of those is possible and how to accomplish the ones that are. I imagine that the solutions for each will be fairly closely related, which is why I'm putting all of this in a single question.

Alternatively, is there any way to eval code that's already been parsed in the context of a binding, similar to Perl's eval BLOCK syntax?

1
  • 1
    this question needs more @Jörg W Mittag :P Commented Jun 22, 2010 at 2:54

4 Answers 4

9

On searching more, I found an answer to at least part of my question:

Based on: http://wikis.onestepback.org/index.cgi/Tech/Ruby/RubyBindings.rdoc/style/print

The rest is from experimentation after Jim Shubert's helpful pointers.

  1. This can be accomplished by eval-ing local_variables, instance_variables and global_variables inside the binding.
  2. You can do something as described below, given var_name, new_val, my_binding (syntax may be imperfect or improvable, feel free to suggest in comments. Also, I couldn't get the code formatting to work inside the list, suggestions for how to do that will also be implemented.)
  3. You can straightforwardly take (2) and loop the hash to do this.
  4. See the second code block below. The idea is to start with TOPLEVEL_BINDING, which I believe normally just includes the global variables.

This does involve using string eval. However, no variable values are ever expanded into the strings involved, so it should be fairly safe if used as described, and should work to 'pass in' complex variable values.

Also note that it's always possible to do eval var_name, my_binding to get a variable's value. Note that in all of these uses it's vital that the variable's name be safe to eval, so it should ideally not come from any kind of user input at all.

Setting a variable inside a binding given var_name, new_val, my_binding:

# the assignment to nil in the eval coerces the variable into existence at the outer scope
setter = eval "#{var_name} = nil; lambda { |v| #{var_name} = v }", my_binding
setter.call(new_val)

Building a "bespoke" binding:

my_binding = eval "lambda { binding }", TOPLEVEL_BINDING # build a nearly-empty binding
# set_in_binding is based on the above snippet
vars_to_include.each { |var_name, new_val| set_in_binding(var_name, new_val, my_binding) }
Sign up to request clarification or add additional context in comments.

1 Comment

Accepting this, but if anyone comes along who knows how to avoid the use of string eval for this stuff, please contribute your knowledge.
3

Walter, you should be able to interact directly with the binding. I haven't worked much with bindings before, but I ran a couple of things in irb:

jim@linux-g64g:~> irb
irb(main):001:0> eval "self", TOPLEVEL_BINDING
=> main
irb(main):002:0> eval "instance_variables", TOPLEVEL_BINDING
=> []
irb(main):003:0> eval "methods", TOPLEVEL_BINDING
=> ["irb_kill", "inspect", "chws", "install_alias_method", ....

I also have Metaprogramming Ruby which doesn't talk a whole lot about binding. However, if you pick this up, at the end of page 144 it says

In a sense, you can see Binding objects as a "purer" form of closures than blocks, because these objects contain a scope but don't contain code.

And, on the opposite page, it suggests tinkering with irb's code (removing the last two args to the eval call) to see how it uses bindings:

// ctwc/irb/workspace.rb
eval(statements, @binding) #, file, line)

And... I was going to suggest passing the lambda, but I see you just answered that, so I'll leave the irb tinkering as a suggestion for further research.

2 Comments

Thanks, the instance_variables construct is new to me, that gives me some more jumping off points. The instruction to check out irb is also helpful. I've upvoted this but not accepted it, since it doesn't directly answer the questions as asked.
Walter, thanks. I hope I at least gave you some ideas to work with and find the answer. I'm sure what you've asked is possible, considering that irb works directly with bindings (irb is a ruby class in itself).
2

Could you explain what exactly you're trying to do? Please provide some code showing how you want it to work. There might be a better and safer way to accomplish what you want.

I'm going to take a shot at guessing your typical use-case. Given a Hash: {:a => 11, :b => 22}

You want a minimal, relatively-isolated execution environment where you can access the values of the hash as local-variables. (I'm not exactly sure why you'd need them to be locals, except maybe if you're writing a DSL, or if you have already-written code that you don't want to adapt to access the Hash.)

Here's my attempt. For simplicity, I'm assuming you only use symbols as Hash keys.

class MyBinding
  def initialize(varhash); @vars=varhash; end
  def method_missing(methname, *args)
    meth_s = methname.to_s
    if meth_s =~ /=\z/
      @vars[meth_s.sub(/=\z/, '').to_sym] = args.first
    else
      @vars[methname]
    end
  end
  def eval(&block)
    instance_eval &block
  end
end

Sample Usage:

hash = {:a => 11, :b => 22}
mb = MyBinding.new hash
puts mb.eval { a + b }
# setting values is not as natural:
mb.eval { self.a = 33 }
puts mb.eval { a + b }

Some caveats:

1) I didn't raise a NameError if the variable didn't exist, but a simple replacement fixes that:

def initialize(varhash)
  @vars = Hash.new {|h,k| raise NameError, "undefined local variable or method `#{k}'" }
  @vars.update(varhash)
end

2) The normal scoping rules are such that if a local exists whose name is the same as a method, the local takes precedence, unless you explicitly do a method call, like a(). The class above has the opposite behavior; the method takes precedence. To get "normal" behavior, you'll need to hide all (or most) of the standard methods, like #object_id. ActiveSupport provides a BlankSlate class for this purpose; it only keeps 3 methods:

 __send__, instance_eval, __id__

. To use it, just make MyBinding inherit from BlankSlate. Besides these 3 methods, you also won't be able to have locals named "eval" or "method_missing".

3) Setting a local is not as natural, because it needs "self" to receive the method call. Not sure if there's a workaround for this.

4) The eval block can mess with the @vars hash.

5) If you have a real local var in the scope where you call mb.eval, with the same name as one of the hash keys, the real local will take precedence... this is probably the biggest downside because subtle bugs can creep in.

After realizing the downsides to my technique, I'm recommending using a Hash directly to keep a set of variables, unless I see a reason otherwise. But if you still want to use the native eval, you can improve safety by using Regexps to avoid code injection, and "locally" setting $SAFE to be higher for the eval call by using a Proc, like so:

proc { $SAFE = 1; eval "do_some_stuff" }.call  # returns the value of eval call

Comments

0

Here's some code for a Hash-based solution.

class ScopedHash
  def initialize(varhash)
    # You can use an OpenStruct instead of a Hash, but the you lose the NameError feature.
    # OpenStructs also don't have the ability to list their members unless you call a protected method
    @vars = Hash.new {|h,k| raise NameError, "undefined local variable or method `#{k}'" }
    @vars.update(varhash)
  end
  def eval
    yield @vars
  end
end

if __FILE__ == $0
  # sample usage
  hash = {:a => 11, :b => 22}
  sh = ScopedHash.new hash
  puts sh.eval {|v| v[:a] + v[:b] }
  sh.eval {|v| v[:a] = 33 }
  puts sh.eval {|v| v[:a] + v[:b] }
  sh.eval{|v| v[:c] }  # raises NameError
end

So, in place of using locals, you'd just access the yielded Hash. I think that there are very few reasons where one would be forced to manipulate a Binding object; there are usually cleaner ways to accomplish the task.

3 Comments

Regardless of the advisability of manipulating Bindings, that was the topic of this question. This suggestion doesn't answer the question asked. -1
Granted, it's off topic. I'm trying to be helpful, and still want to be - can you provide a use-case? Or perhaps your goal is to just experiment specifically with Binding as a language construct and don't care about anything that doesn't use Bindings? If so, just say so and I'll try to think of a better solution with Bindings.
The question is more of the latter. I think I came up with it while experimenting with templates, but my real reason for asking it was to learn how well the language handles introspecting Binding objects. Yeah, that was it -- I wanted to create a controlled namespace for template invocation with only the values from a particular hash set, without having to change the way the template was written so that it went through a hash.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.