How to Dynamically Create a Local Variable in Ruby

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 to dynamically create local and global variables?

You cannot define a local variable with a full stop (.) character in ruby. That is not valid syntax.

(eval):2: unexpected fraction part after numeric literal
string_1.0 = "1.0"

Additionally, you cannot dynamically define local variables. There are various workarounds to sort-of achieve this, however, fundamentally I think you are asking an XY problem.

For example, have you considered using an OpenStruct, or passing this hash as locales when rendering a template, or instead dynamically setting instance variables?

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 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

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 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 }

Dynamically create Ruby variables with string interpolation

You cannot dynamically set local variables in this manner because what you are actually trying to do is set a String. Your code is interpreted as follows

"member1" = Fabricate(:user)

Which will raise a SyntaxError for unexpected = because you cannot set a String to anything.

You can however perform this operation with instance_variables like so:

4.times do |n|
instance_variable_set("@member#{n}", Fabricate(:user))
end

Then access them with @member1,@member2, etc.

To answer your second question is no send and eval have no particular use in this case

creating and passing dynamic local variables to partials

In the controller, along with the array of placeholder values, I did

@i = 0

The forms_for partial was being rendered 4 times because I created 4 answers.

4.times {  @question.answers.build}

Therefore, after each rendering of the partial, I incremented the @i instance variable and passed the incremented value into the @placeholder array @placeholder[@i] and inside the partial used the appropriate element of the array

  <%= f.fields_for :kanswers do |builder| %>
<%= render :partial => 'kanswer_fields', :locals => { :f => builder, :k => @placeholder[@i]} %>
<% @i += 1 %>

<% end %>


Related Topics



Leave a reply



Submit