In Ruby, is there no way to dynamically define a local variable in the current context?
It seems that Ruby's magic would provide a way, but according to Matz, this was only possible in 1.8 via eval
and only in certain contexts (i.e. irb
). As of 1.9, this behavior was taken out ("strictly forbidden"):
Matz himself weighs in here: https://www.ruby-forum.com/topic/155673#685906
I read from somewhere that now Ruby can't dynamically create local variable. Is it true or just a bug?
The local variables are created in compile time, so that local
variables that are defined in eval() cannot be accessed outside of
eval. In 1.8, irb and tryruby does line by line compilation so that
local variables are spilled from eval(), but in 1.9, it's strictly
prohibited even under line-by-line compilation.matz.
(Non-sequitur alternative here, for anyone who wants something like this but not the exact technical situation that the questioner has):
Use a hash:
local_hash = {}
my_vars.each_pair do |k,v|
local_hash[k] = v
end
puts local_hash['foo']
#=> 'baz'
How to dynamically create a local variable?
You cannot dynamically create local variables in Ruby 1.9+ (you could in Ruby 1.8 via eval
):
eval 'foo = "bar"'
foo # NameError: undefined local variable or method `foo' for main:Object
They can be used within the eval-ed code itself, though:
eval 'foo = "bar"; foo + "baz"'
#=> "barbaz"
Ruby 2.1 added local_variable_set
, but that cannot create new local variables either:
binding.local_variable_set :foo, 'bar'
foo # NameError: undefined local variable or method `foo' for main:Object
This behavior cannot be changed without modifying Ruby itself. The alternative is to instead consider storing your data within another data structure, e.g. a Hash, instead of many local variables:
hash = {}
hash[:my_var] = :foo
Note that both eval
and local_variable_set
do allow reassigning an existing local variable:
foo = nil
eval 'foo = "bar"'
foo #=> "bar"
binding.local_variable_set :foo, 'baz'
foo #=> "baz"
How do I dynamically create a local variable in Ruby?
You have to use the correct binding. In IRB for example this would work:
irb(main):001:0> eval "t=2", IRB.conf[:MAIN_CONTEXT].workspace.binding
=> 2
irb(main):002:0> local_variables
=> [:t, :_]
irb(main):003:0> eval "t"
=> 2
irb(main):004:0> t
=> 2
Dynamically set local variables in Ruby
The problem here is that the block inside each_pair has a different scope. Any local variables assigned therein will only be accessible therein. For instance, this:
args = {}
args[:a] = 1
args[:b] = 2
args.each_pair do |k,v|
key = k.to_s
eval('key = v')
eval('puts key')
end
puts a
Produces this:
1
2
undefined local variable or method `a' for main:Object (NameError)
In order to get around this, you could create a local hash, assign keys to this hash, and access them there, like so:
args = {}
args[:a] = 1
args[:b] = 2
localHash = {}
args.each_pair do |k,v|
key = k.to_s
localHash[key] = v
end
puts localHash['a']
puts localHash['b']
Of course, in this example, it's merely copying the original hash with strings for keys. I'm assuming that the actual use-case, though, is more complex.
How to create dynamic global and local variables?
Take this answer with a grain of salt because I'm no expert on Ruby's eval
, but this worked for me:
require 'json'
hash = '{"$a":5,"b":10}'
hash1 = JSON.parse(hash)
bind = binding
hash1.each do |k,v|
# Dunno what this is for and things work fine without it.
# singleton_class.send(:attr_accessor,k)
# You don't want to call `send` here, just call `eval` directly.
eval("#{k}=#{v}", bind)
end
puts $a
# => 5
puts b
# => 10
Note that I'm passing in a binding from the parent scope to eval
since you'll want to access local variables such as b
in the parent scope after you exit the loop.
Lastly, v
needed to be part of the string passed to eval
, not the second parameter passed to eval
, which, if anything, needs to be a binding. If you pass an integer it will raise an exception.
However, I should warn you that global variables should be used with restraint, and that using eval
can be dangerous, especially if you get this JSON from someone else.
Dynamic variable from each method
As other commenters suggested, you may need a hash instead of a dynamic variable:
collection = ["s", "g", "l"]
collection.each do |key|
devan[key] = ['h1', 'h2', 'h3']
devan[key].each do |s1|
puts "from #{key} we got #{s1}"
end
end
With a hash, you get "for free" an arsenal of easy to use methods, which would otherwise be more cumbersome with dynamic variables. For example:
# Add an element to the array for the 's' key:
devan['s'].push 'h4'
# Add an element to the array for each key:
devan.each_key { |k| devan[k].push 'h5' }
# Print array length for each key:
devan.each { |k,v| puts v.length }
Using loop counter as part of a variable's name in Ruby
As others have pointed out it is not possible to create local variables dynamically in Ruby, you could set up a binding as well if you're looking for another method of achieving this.
With eval
b = binding
10.times do |i|
eval("var#{i} = 'foo'", b)
end
> eval("var1", b)
=> "foo"
> eval("local_variables", b)
=> [:var9, :var8, :var7, :var6, :var5, :var4, :var3, :var2, :var1, :var0, :b, :_]
Without eval
b = binding
10.times do |i|
b.local_variable_set("var#{i}", 'foo')
end
> b.local_variable_get('var1')
=> "foo"
> b.local_variables
=> [:var9, :var8, :var7, :var6, :var5, :var4, :var3, :var2, :var1, :var0, :b, :_]
Dynamic variable from string in Ruby
Make a hash of files:
readfile = File.open("myfile.txt", 'r')
files = {
'foo' => File.open("file1.txt", 'w'),
'bar' => File.open("file2.txt", 'w')
}
for line in readfile
files[line[0..2]].write(line)
end
files.each {|k, v| v.close}
Can you dynamically initialize multiple variables on one line in ruby?
There is a way, using eval
, but you would rather not want to use it (and I would even go that far to say that it might be better not to learn it until well later).
There is simply no case when you would use that instead of plain arrays.
For your example, one should use class Range
and method map
:
(0..3).map{|i| i * 2}
#=> [0, 2, 4, 6]
You can see that this has been done without declaring any variable - even i
is alive just within the block passed to map
. It doesn't exist afterwards.
Related Topics
What Does "Shadowing" Mean in Ruby
Rails - Local Variables Versus Instance Variables
Creating Permutations from a Multi-Dimensional Array in Ruby
Ruby Gem Dependencies on Offline Server
301 Moved Permanently After S3 Uploading
Why Does Ruby String#Split Not Treat Consecutive Trailing Delimiters as Separate Entities
When Did "Assigned But Unused" Become a Warning for Ruby
Rspec --Init Not Working/ 'Mkd Ir': Invalid Argument - ./C: (Errno::Einval)
Rails 3.1 Absolute Url to an Image
Is Ruby Really an Interpreted Language If All of Its Implementations Are Compiled into Bytecode
Does Ruby Call Initialize Method Automatically
Xpath Expression for Regex-Like Matching
Detect If Application Was Started as Http Server or Not (Rake Task, Rconsole etc)
Converting a Hexadecimal Digest to Base64 in Ruby
Error: Failed to Build Gem Native Extension (Ruby Extconf.Rb): MAC Osx