2

How to define variables in main binding of plain ruby file?

I have tried TOPLEVEL_BINDING, but its dont share variables to main scope

#!/usr/bin/env ruby
# ....

5.times do |i|
  src = %(
    reader#{i} = library.create_reader "name"
    book#{i}   = library.create_book   "title"
  )
  TOPLEVEL_BINDING.eval(src)
end

3.times { reader0.take(book0) } # error
5.times { reader1.take(book1) }
1.times { reader2.take(book2) }
0.times { reader3.take(book3) }
1.times { reader4.take(book4) }

puts book0.title 
11
  • 3
    I wonder why you want to do that? Wouldn't it be easier to use an array or a hash to store the objects and read from it? Commented Nov 11, 2016 at 21:22
  • 1
    Since Ruby v1.8 it has not been possible to create local variables dynamically. (It can be done in v1.8 with eval.) Commented Nov 11, 2016 at 21:22
  • @spickermann want my code to resemble rspec) Commented Nov 11, 2016 at 21:28
  • @CarySwoveland so theres no way to do this with local variables? Commented Nov 11, 2016 at 21:29
  • 2
    What you're trying to do is easily possible in other language, and at one time was considered an OK practice, however these days it's frowned on. You're generating variables that are "invisible" unless you analyze the code closely. That leads to bugs and very hard to maintain code. Ruby has other ways of doing it, and in some situations they're acceptable, but you should try to not do this until you're very familiar with when it's OK and when it's not. Commented Nov 11, 2016 at 22:05

1 Answer 1

1

You could define instance variables dynamically :

5.times do |i|
  instance_variable_set(:"@reader#{i}", "library_name#{i}")
  instance_variable_set(:"@book#{i}", "book_title#{i}")
end

puts @reader1
puts @book1
puts @book4

# => library_name1
#    book_title1
#    book_title4

Another possibility would be to use method_missing to fake local variables, while using instance variables as cache :

def create_variable_or_use_cache(name, &block)
  name = "@#{name}"
  instance_variable_get(name) || instance_variable_set(name, block.yield)
end

def method_missing(sym,*p)
  if sym=~/^reader(\d+)$/ then
    create_variable_or_use_cache(sym){ "Create reader#{$1} here" }
  elsif sym=~/^book(\d+)$/ then
    create_variable_or_use_cache(sym){ "Create book#{$1} here" }
  else
    super
  end
end

puts reader1
puts reader1
puts book3
wrong_method

# => 
# Create reader1 here
# Create reader1 here
# Create book3 here
# binding.rb:13:in `method_missing': undefined local variable or method   `wrong_method' for main:Object (NameError)

It's an interesting Ruby exercise, I'm not sure you should use it though.

Sign up to request clarification or add additional context in comments.

1 Comment

I don't think it's possible without explicitely using binding. The question is also if you should do it ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.