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
Which Global Variable Is for Last Expression
How to Http Post Stream Data from Memory in Ruby
Why Is Heroku's Heroku-18 Stack Only Compatible with Puma Versions 3.7.X
Using Class Instance Variable for Mutex in Ruby
How to Efficiently Extract Repeated Elements in a Ruby Array
Rails Not Working for New Project. Showingerror " Superclass Mismatch for Class Cipher (Typeerror)"
Why Does Ruby '**' Operator Have Higher Precedence Than Unary '-'
Error: Null Value in Column "Id" Violates Not-Null Constraint
Solr or Sphinx? Which Is Better
Ruby Sequel: Array Returned by Query Is Being Returned as a String Object, Not an Array Object
How to Convert This Ruby String into an Array
Differencebetween a Constant and a Variable in Ruby
Why Do I Get "Undefined Method 'Paginate'" Error in Production
How to Read a Clients Windows Login Name Using Ruby on Rails
How to Upgrade Rvm When the Official Way Doesn't Work
Ruby- Delete a Value from Sorted (Unique) Array at O(Log N) Runtime
Ruby: Wait for All Threads Completed Using Join and Threadswait.All_Waits - What the Difference