24

Essentially I'm wondering if the following can be done in Ruby.

So for example:

def bar(symbol) 
  # magic code goes here, it outputs "a = 100" 
end

def foo
  a = 100 
  bar(:a) 
end

5 Answers 5

18

You have to pass foo's context to bar:

def foo
  a = 100
  bar(:a, binding)
end
def bar(sym, b)
  puts "#{sym} is #{eval(sym.to_s, b)}"
end
Sign up to request clarification or add additional context in comments.

Comments

18

There is no built-in way to get a callers binding in Ruby in 1.8.X or 1.9.X.

You can use https://github.com/banister/binding_of_caller to work around.

In MRI 2.0 you can use RubyVM::DebugInspector, see: https://github.com/banister/binding_of_caller/blob/master/lib/binding_of_caller/mri2.rb

Working sample in MRI 2.0:

require 'debug_inspector'

def bar(symbol)
  RubyVM::DebugInspector.open do |inspector|
    val = eval(symbol.to_s, inspector.frame_binding(2))
    puts "#{symbol}: #{val}"
  end
end

def foo
  a = 100
  bar(:a)
end

foo
# a: 100

3 Comments

here's an example of the fragile way to use set_trace_func to do so : stackoverflow.com/questions/1314592/…
Should include disclaimer: Please don't do this unless you are actually using it for debugging of some sort.
Why @tmandry? This is a metaprogramming technique - it is undesirable in the way that all metaprogramming is undesirable (difficult to maintain and debug) but it isn't dangerous is a new or special way.
9

Here's a easier syntax hack, using a passed in block binding:

  def loginfo &block
    what = yield.to_s
    evaled = eval(what, block.binding)
    Rails.logger.info "#{what} = #{evaled.inspect}"
  end

called like this:

  x = 1
  loginfo{ :x }

will log out:

  x = 1

Comments

6

Just FYI, here's a "hacky way". This is my (re-)implementation of well-known ppp.rb:

#!/usr/bin/ruby
#
# better ppp.rb
#

require 'continuation' if RUBY_VERSION >= '1.9.0'

def ppp(*sym)
  cc = nil
  ok = false

  set_trace_func lambda {|event, file, lineno, id, binding, klass|
    if ok
      set_trace_func nil
      cc.call(binding)
    else
      ok = event == "return"
    end
  }
  return unless bb = callcc{|c| cc = c; nil }

  sym.map{|s| v = eval(s.to_s, bb); puts "#{s.inspect} = #{v}"; v }
end

a = 1
s = "hello"
ppp :a, :s

exit 0

This currently fails with 1.9.[012] due to a bug in ruby's set_trace_func.

1 Comment

Very interesting! I've adapted it to build my own version of caller_binding. But what is ppp?
3

Check article out Variable Bindings in Ruby

class Reference
  def initialize(var_name, vars)
    @getter = eval "lambda { #{var_name} }", vars
    @setter = eval "lambda { |v| #{var_name} = v }", vars
  end
  def value
    @getter.call
  end
  def value=(new_value)
    @setter.call(new_value)
  end
end

def ref(&block)
  Reference.new(block.call, block.binding)
end

def bar(ref)
  # magic code goes here, it outputs "a = 100" 
  p ref.value
end

def foo
  a = 100 
  bar(ref{:a}) 
end

foo

3 Comments

+1 looks very interesting, I wonder if this can be done without the block though ...
I don't think it can be done without the block in ruby 1.8.x. I'm not familiar with ruby 1.9.x, so things may be different there.
In Ruby 2.1 you still need the block, but implementation of Reference can be redone using calls to Binding#local_variable_get and Binding#vars.local_variable_set. Although, the current implementation probably allows for calls to reader and writer methods in addition to local variables, whereas the new implementation wouldn't...

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.