Access Local Variables from a Different Binding in Ruby

Access local variables from a different binding in Ruby

It's a shame there isn't a built-in way to get the caller's binding. The block trick seems to be the usual answer to this question.

However there is another 'trick' which existed for older 1.8 Ruby versions called binding_of_caller. Looks like quix ported it to 1.9. You might want to check that out:

https://github.com/quix/binding_of_caller

metaprogramming access local variables

The local variable in initialize would be lost.

You are able to get the value fiz outside of the class, but only upon defining that class, and recording the return of the definition of the class.

return_of_class_definition = (class A ; fiz = 5 ; end) would assign the value of fiz to the variable.

You can also use binding but of course, this means changing the class, which may not be allowed for the exercise.

class A
bin = 15
$binding = binding
end

p eval 'bin', $binding

Get local variables of previous scope

This won’t fit the comment, so I would post it as an answer.

def launchREPL(scope = nil, winName = "Ruby REPL")
puts '"Starting REPL..."'

scope.eval('local_variables').each do |var|
instance_variable_set "@#{var}", scope.eval(var.to_s)
end if scope

s = ""
loop do
ret = begin
"#{s}\n> #{eval(s)}"
rescue => e
"#{s}\n> Error: #{e.message}"
end
puts ret
# s = WSApplication.input_box(ret, winName, "")
# break if s.empty?
s = "100 * @a" # remove this line and uncomment 2 above
end
end

a = 42
launchREPL(binding)

This is how your function should be written (I have just make it looking as ruby code.) The above works (currently it has no break at all, but you can see as it’s calculating 4200 infinitely.)

Injecting a local variable to a binding

You're creating country outside of the binding, and then the country inside the lambda is only valid within that scope. Why not eval it if you need to inject that into the binding as well?

Update

Try declaring the variable outside of the scope of the lambda but inside the scope of the binding:

 b.eval("country = nil; lambda {|v| country = v}").call(country)

How to change local variable value by using method?

You do not have access to those local variables inside your method.

You can play with it by passing binding but this shouldn't be used in real life:

Objects of class Binding encapsulate the execution context at some
particular place in the code and retain this context for future use.

def change(local_variable_name, binding, new_value)
binding.local_variable_set(local_variable_name, v)
end

Test:

a = 1

a #=> 1
change(:a, binding, 2) #=> 2
a #=> 2

Your case:

def swap(first, second, binding)
old_first = binding.local_variable_get(first)
old_second = binding.local_variable_get(second)

binding.local_variable_set(first, old_second)
binding.local_variable_set(second, old_first)
end

a = 1
b = 2
swap(:a, :b, binding)
a #=> 2
b #=> 1

Reference:

  • Binding#local_variable_set
  • Binding#local_variable_get

Is there a way to access a local variable defined inside a block outside the block?

binding returns a new instance every time you call it. You have to send eval to the same binding in order to access local variables that you've created earlier:

def create(code, b = binding)
width = code.each_line.map(&:length).max
code.each_line.map do |line|
'%-*s #=> %s' % [width, line.chomp, b.eval(line)]
end
end

puts create <<~'RUBY'
baz, qux = 5, 3
baz
qux
RUBY

Output:

baz, qux = 5, 3    #=> [5, 3]
baz #=> 5
qux #=> 3

Note that in the above example, binding will make the method's local variables available to the block:

create 'local_variables'
#=> ["local_variables #=> [:code, :b, :width]"]

You might want to create a more restricted evaluation context, e.g. (replicating Ruby's main)

def empty_binding
Object.allocate.instance_eval do
class << self
def to_s
'main'
end
alias inspect to_s
end
return binding
end
end

def create(code, b = empty_binding)
# ...
end

listing the local variables in irb

Your code is really complicated and obfuscated. First, let's clean up your code a bit so that we can see more clearly what's going on. You don't need self, since it is the default receiver anyway. And you don't need send, because you already know the name of the method you want to call. Also, Kernel#local_variables uses the current Binding anyway. Also, typically, such methods which are supposed to by called without an explicit receiver and don't actually make use of self (e.g. like puts or require) are put into Kernel. So, your code is equivalent to the following:

module Kernel
def list_vars
local_variables.map {|var| "#{var} = " + binding.local_variable_get(var).inspect}
end
end

Now we can immediately see the problem: you are reflecting on the local variables and the Binding of list_vars and not the place where it was called from! You need to pass the Binding into the method and use that instead:

module Kernel
def list_vars(binding)
binding.local_variables.map {|var| "#{var} = " + binding.local_variable_get(var).inspect}
end
end

list_vars(binding)

Or, you could make it an instance method of Binding:

class Binding
def list_vars
local_variables.map {|var| "#{var} = " + local_variable_get(var).inspect}
end
end

binding.list_vars

How to pass all local variables when calling a method

Local variables are there to do precisely not what you are trying to do: encapsulate reference within a method definition. And instance variables are there to do precisely what you are trying to: sharing information between different method calls on a single object. Your use of local variable goes against that.

Use instance variables, not local variables.

If you insist on referencing local variables between methods, then here is a way:

class Greeting
def hello
message = 'HELLO THERE!'
language = 'English'
say_greeting(binding)
end

def konnichiwa
message = 'KONNICHIWA!'
language = 'Japanese'
say_greeting(binding)
end

private def say_greeting b
puts b.local_variable_get(:message)
end
end

You can't make it without passing any arguments, but you can just pass a single binding argument, and refer all local variables from there.

Accessing local variables in the calling context

There is a gem binding_of_caller which can do this. Install the gem and then do

require 'binding_of_caller'

def my_method only: [], ignore: []
something('textvalue') if want('textvalue')
end

def want(value)
only = binding.of_caller(1).local_variable_get(:only)
ignore = binding.of_caller(1).local_variable_get(:ignore)
(only.empty? && ignore.empty?) || (only.any? && only.include?(value)) || (ignore.any? && !ignore.include?(value))
end

But this is a bad thing to do as it creates very high coupling. It's really not good design. If you want to do this for experimentation and playing around, fair enough, but don't do it in production.



Related Topics



Leave a reply



Submit