Giacomo1968
Giacomo1968

Reputation: 26066

How to access and assign a string value as a string name dynamically in Ruby

In PHP I can dynamically access a variable by setting something like $variable_name = foo and echo $$variable_name which would then echo the value of foo. But I am unclear on how to do this in Ruby. In PHP I could do something like this; assign variable names in an array and iterate through them:

# Set string values.
$value_one = 'a one';
$value_two = 'and a two';
$value_three = 'and a three';

# Set array of variable names.
$process_items_array = array('value_one', 'value_two', 'value_three');

# Roll through the values.
foreach ($process_items_array as $value) {
  $$value = do_something($$value) . '<br />';
}

# A simple function for example’s sake.
function do_something ($value = null) {
  return strtoupper($value);
}

But in Ruby, what would be the equivalent? For example I have tried this and none of the items work as expected; note that is this psuedo-code as noted by the use of self. references in a non-class structure:

# Set string values.
value_one = 'a one';
value_two = 'and a two';
value_three = 'and a three';

# Set array of variable names.
process_items_array = ['value_one', 'value_two', 'value_three']

# Roll through the values.
process_items_array.each { |value|
  self.send("#{value}").to_sym = do_something value.try(self.send("#{value}").to_sym)
}

# A simple function for example’s sake.
def do_something value
  value = value.try(:strip)
  value = nil if value.blank?
  value.upcase
end

Note my attempted use of instance_variable_get, [send][2] and to_sym; I’m basically hoping something will work but nothing seems to work. What should I be doing instead?

Upvotes: 0

Views: 425

Answers (3)

J&#246;rg W Mittag
J&#246;rg W Mittag

Reputation: 369458

You can use Kernel#binding to get the current Binding object and then Binding#local_variable_get and Binding#local_variable_set to get and set the local variables:

# Set string values.
value_one = 'a one';
value_two = 'and a two';
value_three = 'and a three';

# Set array of variable names.
process_items_array = ['value_one', 'value_two', 'value_three']

# Roll through the values.
process_items_array.each { |value|
  binding.local_variable_set(value.to_sym, do_something(binding.local_variable_get(value).to_sym)
}

# A simple function for example’s sake.
def do_something value
  value = value.try(:strip)
  value = nil if value.blank?
  value.upcase
end

# Show that it works:
p value_one, value_two, value_three
# "A ONE"
# "AND A TWO"
# "AND A THREE"

However, that code is a bit unidiomatic:

  • You wouldn't use both semicolons and newlines as an expression separator, only one
  • You would use Symbols for representing the variable names, not Strings
  • You would use do/end instead of {/} for a multiline side-effecting block
  • You wouldn't state the type of the object referenced by a variable in the variable name (e.g. foo_array)

You would also have to move the definition of do_something up before it is used, otherwise you get a NoMethodError.

And for easier reproducibility, I removed the dependency on the active_support, so that people trying to test this don't have to install an extra gem which isn't even needed to reproduce the solution.

This would be a more idiomatic version of the code:

# Set string values.
value_one =   'a one'
value_two =   'and a two'
value_three = 'and a three'

# Set array of variable names.
items_to_process = %i[value_one value_two value_three]

# A simple method for example’s sake.
def do_something(value)
  value.upcase
end

b = binding

# Roll through the values.
items_to_process.each do |var|
  b.local_variable_set(var, do_something(b.local_variable_get(var)))
end

# Show that it works:
p value_one, value_two, value_three
# "A ONE"
# "AND A TWO"
# "AND A THREE"

Upvotes: -1

jag
jag

Reputation: 908

A more ruby way (so without the "dangerous" eval)

# Set string values.
value_one = 'a one';
value_two = 'and a two';
value_three = 'and a three';

# Set array of variable names.
process_items_array = ['value_one', 'value_two', 'value_three']

# Roll through the values.
process_items_array.each do |variable_name|

  # Output value
  puts binding.local_variable_get(variable_name.to_sym)

  # Reassign variable
  binding.local_variable_set(variable_name.to_sym, 'foo')
end

puts value_one, value_two, value_three
# foo
# foo
# foo

Upvotes: 1

Alex Ponomarev
Alex Ponomarev

Reputation: 945

You can do that with eval:

# Set string values.
value_one = 'a one';
value_two = 'and a two';
value_three = 'and a three';

# Set array of variable names.
process_items_array = ['value_one', 'value_two', 'value_three']

# Roll through the values.
process_items_array.each do |variable_name|

  # Output value
  puts eval(variable_name)

  # Reassign variable
  new_value = 'foo'
  eval("#{variable_name} = new_value")
end

As it was said in the comments, you might want to store your data within a Hash. Also, code where you need to dynamically create or read variables smells really bad.

Upvotes: 2

Related Questions