2

I want to programmatically insert a new variable into the local Ruby namespace. For example, I want to be able to write

label = 'some_name'
# some code equivalent to
#   some_name = 3
# but using only 'label' to get the name.
puts some_name  # returns 3

What do I put in the middle here to get this done?

Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
Peter
  • 127,331
  • 53
  • 180
  • 211
  • When you say symbol, I guess you are not referring to symbols in Ruby, right (e.g. :a_symbol)? And what exactly is label for if it's not used in the later lines of your code? – mikong Aug 11 '11 at 02:57
  • @mikong: thanks, I've clarified the question. I want `some_name` to be available later on in the code. You can assume that `label` is just a mechanism for knowing what to call the variable. – Peter Aug 11 '11 at 03:03
  • At first I was thinking you should just use Ruby constants. But it seems you want to change the label arbitrarily, and your puts line can just be changed to the new name, correct? I'll write the solution in a bit, after you confirm. – mikong Aug 11 '11 at 03:17
  • 2
    You should really just use a Hash. Mucking around with eval makes your code uglier, slower less idiomatic and more error-prone. – Chuck Aug 11 '11 at 03:58
  • @Chuck: ordinarily it would be horrendous practice, yes - however, in this particular case I want to support a certain DSL for my users that requires setup like this. It's certainly not being used as a general-purpose programming technique :) – Peter Aug 11 '11 at 16:27
  • @Peter: The "normal" way to support a DSL with access to some specific state is to create an object wrapping the state (e.g. with a `def label() "Hi, I'm a label" end`) and then `instance_eval` the DSL code so that it runs in this object. – Chuck Aug 11 '11 at 17:07

3 Answers3

3

I've answered another SO question similar to this. The short answer is this, if you specifically want to create a local variable with the name of it based on the value of another variable, then there is no way to do it. It you just want to make seem as though you've created a local but it is really ruby magic, then something like @mikong's answer is one way to go.

Note that if you relax your contraint and are happy to create an instance variable instead, then you can do it.

label = 'some_name'
self.instance_variable_set("#{label}", 3)
puts @some_name

You can even dynamically define an accessor and then you can get rid of the unsightly @, but once again you will simply have a method masquerading as a local rather than a real local variable.

Community
  • 1
  • 1
skorks
  • 4,376
  • 18
  • 31
2
label = 'some_name'
eval "#{label} = 3"
puts eval "#{label}"
puts local_variables

Note that you would presumably never have an opportunity to execute...

puts some_name

...because if you knew what local variables you were going to create there would be no need to name them with run-time code. And that's good, because the interpreter will not be able to puts some_name directly because it never parsed an assignment for some_name. But it is there and it is a local, as puts local_variables is able to show.

DigitalRoss
  • 143,651
  • 25
  • 248
  • 329
  • Interesting. All four lines of your code work for me in ruby-1.8.7-p334. However if I run the same program in ruby-1.9.2-p180 then `puts local_variables` outputs only "label" and `puts eval "#{label}` raises an exception: `test.rb:3:in 'eval': undefined local variable or method 'some_name' for main:Object (NameError)`. – David Grayson Aug 11 '11 at 05:37
  • That's because in 1.9 local variables are only available within the context of the `eval` where they were defined. So you define a local within one `eval` and try to use it within another, you'll get ... well exactly what you got :). – skorks Aug 11 '11 at 14:13
  • Hmm. So it might be a good idea to either switch to creating instance variables rather than locals, or just use a more complex data structure in the first place so you don't need to dynamically create individual names. It's hard to see the benefit of creating Ruby-level names over a simple Hash key. (I try to avoid giving *"don't do that in the first place"* advice, since sometimes the motivation is academic curiosity. But in this case...) – DigitalRoss Aug 12 '11 at 05:36
2

The following is not exactly code between the 2 lines that you mentioned above:

class Example
  attr_accessor :label

  def method_missing(name, *args, &block)
    return some_processing  if name == label.to_sym
  end

  def some_processing
    3 # of course, this can be something more complicated
  end

  def test
    @label = 'some_name'
    puts some_name
  end

end

Nonetheless it seems to work with what you need. The mechanism has changed from what you gave (label is now an attribute). Also, technically, it's not a variable but a method with a dynamic name that returns what you need.

Personally, I think your requirements seem a little bit dangerous in that the "variable" name changes. I would probably not use the code in my example. I guess depending on the project requirements, I'll think of a different approach.

mikong
  • 8,270
  • 3
  • 16
  • 15