Closing a $global in a Binding - out of the question?

Hi… I’ll try to be brief. I have a programming problem that I would like to solve by closing a global variable into a Binding. I’ve wracked my brain for other ways to do this, but it seems this is the only way. I have a legacy Rails app that keeps thousands of Ruby code fragments in the database, and these are executed with eval. The code fragments are complex, and I want to be able to access a global value no matter where (scope-wise) I invoke it within the codebase. Because I don’t know ahead of time where I will some day want accesses to happen, I think the value needs to be global, but as should be obvious, I don’t want to let this global exist for my entire app - that would be chaos - totally infeasible. I want to pass it in, and restrict it to just the eval execution.

This doesn’t seem possible at all based on the work and research I’ve done up till now. Can someone confirm this conclusion? Is it something worth putting in a request for future support for? Thanks.

You can assign a global variable in rails.
You can evaluate it from the database rows and store it globally.
You can also store it in a limited scope.

You can create as many binding as you want:

irb
irb(main):001:0> a, b = binding, binding
=> [#<Binding:0x00005651d0efd008>, #<Binding:0x00005651d0efcfe0>]

irb(main):002:0> a.eval('x = 50') # => 50
irb(main):003:0> x # NameError                                            
irb(main):004:0> a.eval('x') # => 50
irb(main):005:0> b.eval('x') # NameError                                

You can store the bindings in an array:

> $bindings = []

> $bindings << binding.tap { |x| x.eval('a = 100') }
> $bindings << binding.tap { |x| x.eval('a = 500') }
> $bindings << binding.tap { |x| x.eval('b = "hello"') }

> $bindings[0].eval('a')  # => 100
> $bindings[1].eval('a') # => 500
> $bindings[2].eval('b') # => "hello"

If you want to uniquely eval and store values from database, you might want to use hashmaps, refer the item’s id, and then the evaluated values. Like the array $bindings, the $bindings_hash can also be a global variable. You can put that inside application.rb. Do note that you can also save it as JSON to prevent it from getting lost during exit.

But I’ll not prefer putting bindings like that, I can just eval the snipped and store it inside a hashmap directly where the id maps to the value returned.

To be honest, I didn’t get your question right, but I hope this might help…

SouravGoswami, thanks for trying, but you did miss the point of my question :grinning: I suppose I should have given a code sample. What I want to do is to put a global variable inside a binding, in such a way that it is not also scoped outside the binding. In other words, like this:

irb(main):001:0> a = binding
irb(main):002:0> a.eval('$x = 50')
irb(main):003:0> a.eval('$x') # => 50
irb(main):004:0> $x # => nil

The above is what I want, but is not reality. In reality, the last line is

irb(main):004:0> $x # => 50

In other words, it seems that at line 002, the assignment was not confined to the binding, as I wanted, but was simply evaluated in the common global scope of the entire app. Another possible way that Ruby could offer me what I want is something like this:

irb(main):002:0> a.global_variable_set('$x', 50)
irb(main):003:0> $x # => nil

but this global_variable_set method is not available, possibly because of fundamentals of the Ruby interpreter that I don’t understand. What I’m looking for is some expert to tell me (a) if what I want can never and will never be possible, and maybe (b) if there’s another way to simulate (“hack”) the result that I want in my code (pls see OP), just using cool Ruby metaprogramming (but I’m not holding my breath for another way).

Do you must use global variables?

The purpose of defining global variables is to make them global. There are no scopes for them. Or more accurately, the scope is really really wide. Whenever you assign them, they are set on the top level context, and can be called from anywhere in the code - even if that’s wrapped inside a binding.

The only one way to not let global variables pollute the outer scope is to use fork. Forks create a forked copy of the ruby process - a child process. Forking is slow but good for parallelize your code and also act as such wrappers you mentioned. But they also become zombies unless you wait for them. Parent process can share global variables with children, but it can’t be vice-versa. You need to write to a file to save the value.

The method you suggested, global_variable_set isn’t helpful here in this case of binding. It will be the same thing in the toplevel as well, but it isn’t available [*1].

So I suggest you using either instance variables or just local variables.


*1 [Offtopic] The dynamic global variable name is only available from the Ruby C Extension though:

https://silverhammermba.github.io/emberb/c/#global-variables

1 Like

Because of the way my application works, I believe that I definitely need to use a global variable. The ideal would be, as I said, to have a special means of adding a global into a Binding, and then calling eval on that binding, so that only the eval’ed code can see the global var. Thanks for the fork idea. I did not realize that forking my app might allow me to set a global, but keep it confined to just the child process. I don’t really think this is realistic for me, though, because I think it raises other serious issues for a Rails app, and might cause more trouble than it is worth, but I’ll keep it in mind and might find a way to make it work.