How do I work around Ruby's eval and "Already initialized constant"? - ruby

I inherited maintenance on an app that uses eval() as a way to evaluate rules written in Ruby code in a rules engine. I know there are a lot of other ways to do it, but the code base so far is pretty big, and changing it to something else would be prohibitive time-wise at this point; so assume I'm stuck using eval() for the moment.
The rules as written typically call up some of the same objects from the database as each other, and the rules writer gave the variables in the rules the same names as each other. This is resulting in pages and pages of "already initialized constant" warnings in the console during development.
I'm wondering a couple things:
First, if feels like those are slowing down the execution of the program in the dev environment, and so I'm wondering if it's a big performance hit in the production environment, specifically, having those warnings pop, not eval() itself, which I know is a hit.
Second, is there any way to "namespace" the execution of each rules so that it's not defining its variables on the same scope as all the other evals in the request to avoid that warning popping all over the place? I know I could rewrite all the rules to use ||= syntax or to check if a name has already been defined, but there's quite a lot of them so I'd rather do it from the code that runs the eval()'s, if possible.
** update with example rule **
A question has a rule about when it's to be displayed to a user. For example, if the user has stated that they live in an apartment, another question might need to be shown to ask what size the apartment building is. So the second question's rule_text might look like:
UserLivesInApartment = Question.find_by_name "UserLivesInApartment"
UserLivesInApartment.answer_for(current_user)
The code that calls the eval ensures there's a current_user variable in scope prior to evaluating.

uh, eval is not perhaps the most golden of standards. You could probably fire up a drb instance and run the stuff in that instead of eval, that way you would have at least some control of what is happening and not pollute your own namespace.
http://segment7.net/projects/ruby/drb/introduction.html
Edit: added another answer for running the code in the same process:
I don't know how your rule code looks, but it might be possible to wrap a module around it:
# create a module
module RuleEngineRun1;end
# run code in module
RuleEngineRun1.module_eval("class Foo;end")
# get results
#....
# cleanup
Object.send(:remove_const, :RuleEngineRun1)
You can also create an anonymous module with Module.new { #block to be module eval'd } if you need to run code in parallel.
In later rubies you can add -W0 to run your code without printing warnings, but doing so makes possible errors go unnoticed:
$ cat foo.rb
FOO = :bar
FOO = :bar
$ ruby foo.rb
foo.rb:2: warning: already initialized constant FOO
$ ruby -W0 foo.rb
You could also run your eval inside a Kernel.silence_warnings block, but might be devastating as well if you actually run into some real problems with the eval'd code, see
Suppress Ruby warnings when running specs

Related

Setting a global value (and keeping it) within the scope of an eval

I've got a large Rails 5 app (Ruby 2.6.x at present) that makes crucial use of Kernel::eval (please don't tell me to try to refactor this out because eval is dangerous - I didn't write the original code, and this is not in the cards for any time soon).
There are a very wide variety of Ruby expressions (coming out of the db) that can be passed to eval, sometimes of great complexity, making extensive use of classes and resources of the app.
(you might want to jump straight down to BIG EDIT below)
What I want is to be able to set a global value ($global) that will be seen within the scope of the eval execution, but that will not "infect" any of the execution context outside of that. I can't try to interpolate this into the string and pass it down though method params and such, because, as I say, the code being eval'ed is complex and stacks can get very deep, and I want the value to potentially be accessed (though never modified) anywhere within.
I understand about Bindings. I have played around with setting local and instance vars in a binding, and passing this to eval, but inevitably these are not seen inside any method calls within the eval, especially if I'm inside a method of some random class (which I always am). Seems like global is the only possibility. But experimentation shows that a global set inside an eval remains in the code that calls the eval:
2.6.3 :002 > $foo
=> nil
2.6.3 :003 > eval("$foo = 12")
=> 12
2.6.3 :004 > $foo
=> 12
Although I might find some hacky way to deal with this situation, I'm sure you can see where I'd really rather not.
The Binding class offers methods to set local and instance vars dynamically within a Binding object, but nothing for globals (apparently). I've thought about something like this:
...
eval code_string, get_binding()
...
def get_binding
$global = :special_value
binding
end
but I'm really worried, with a Rails app that might be servicing lots of requests at the same time, that these settings of $global will step on each other in unpredictable ways. Related clarifying question: Is a global value in a Rails app global to the entire thing, readable and writable within the scope of all the requests whose servicing may be overlapping in time? (I'm running under Passenger, if that means anything)
So this is a fairly simple and straightforward problem when you understand it, although oddly not addressed in anything I can google about it, and I think I've written enough words. Thanks for any help or ideas to try.
BIG EDIT:
Ok, let me try to refocus this in a different way. I'm getting that the scope of a global can never, no-how, be constrained (duh, right?), but how about this strategy (similar to above):
...
eval code_string, get_binding()
...
def get_binding
luaapg = :special_value ## local used as a pseudo-global
binding
end
So, now I've got this Binding that includes the local var luaapg. I've confirmed that. I eval code_string with this Binding. When I am somewhere inside the execution of code_string, where do I find luaapg - how do I access it? If you look at pretty much every tutorial on this stuff on the web, they show you puts eval("luaapg", get_binding) and voila, the assigned value comes out! But this is too simplistic for real life. When I am in the middle of my code_string, in some method scope of some class, luaapg is not there. I had great hope that this would work, even deep down the stack:
TOPLEVEL_BINDING.local_variable_get(:luaapg)
but it doesn't (I learned about TOPLEVEL_BINDING from here - thanks to that author). So this is the new question: what does it mean to say that I have executed (eval'ed) my code_string in the context of that Binding, which contains a local variable, if I have no way to access that variable, other than with the most simpleminded code? (incidentally I played around with instance vars too - same thing). I'm still hoping there's some magic incantation...
I think you've put your finger on it in the name of the type of variable - it's global - common to all the code in the executing program. I'm not sure exactly how Passenger works but I suspect it runs several copies of your program, so it won't be common between the copies.
To get reliable shared information I think you're going to have to use your database or some sort of information cache like memcached. You choose how you save/name it there.

How is called the feature that let's ruby execute code inside classes while interpreting the file

When I have the following ruby code
#file a.rb
class A
puts '2'
end
if I execute rb a.rb I get 2 printed onto the screen. This is because of the way ruby interprets the code, but, what is the name of this behavior?
The reason it prints something is because in the course of defining class A you asked it to. Anything inside a class block is treated as regular Ruby code, it's not special, so printing, exiting, making network connections, opening files, that all works as it would anywhere else.
This is in stark contrast to things like JavaScript and C++ where that is absolutely not allowed.
Ruby, being a dynamic programming language, has a lot more latitude. This permits things like defining methods based on input from files, or pretty much anything you can imagine.
The name of this behaviour is basically "evaluation", as in when Ruby evaluates that code, that is it parses and runs it.
This allows you to do things in Ruby not possible in other languages without employing macros, pre-processor tricks, or other techniques:
class A
if (Date.today.saturday? or Date.today.sunday?)
def party!
:on
end
end
end
Where that will only define the party! method when the code is run on a weekend.

if __FILE__ == $0 break program when it's placed before any methods

I'm new to Ruby and am trying to understand a behavior that I've run into. I'm writing a class that needs some constants initialized before it can run, but when its run from another class, as it sometimes will be, I get warnings about constants already being defined. So I placed the following at the end of my file:
if __FILE__ == $0
constant_initialiation
ReviewScraper.new.getReviews($testing, $getWeekendReviews, $clearWorksheet, $getAll)
end
The constant_initialization is just a bunch of constants being set, nothing interesting. Anyway, this works great for me -- so long as its at the end of the file. If I move this up to the top and try running, I get an error: unitialized constant ReviewScraper (NameError). Almost as if it compiles sequentially for just this part of the file and isn't finding the ReviewScraper class definition when its run.
Can any Ruby geniuses explain this behavior to me? It's no big issue other than for styling purposes (I like having my list of constants up top), but it would be nice to understand what's going on here.
First off, I'll expand on my comments above as to what is going on. Secondly, I'll suggest perhaps a better way of setting your constants in code that avoids setting them multiple times.
As I mentioned, a Ruby script reads from top to bottom. So, if you try to instantiate a class before you define it, they the Ruby script won't know what it is.
cat = Cat.new # NameError
class Cat
# Code
end
cat = Cat.new # Works fine
The script first reads the line where you make a new Cat object. However, it doesn't know what a Cat is yet. Once it is done processing the code for what a Cat is, then it can create one. I used the example of talking to someone about Darth Vader but it is probably more akin to asking a construction company to build your building before you've ever handed them a blueprint. It is only after they have the blueprint that they can build a your building.
Now, in regards to initializing constants, there are a couple different things you could do. One is, you could put the initializations in a if block much like you did at the top but leave out the instantiating of the class until the end of the script. (Two if statements.) Another would be to put the constants in a module in its own file.
module Names
Dog = "Spot"
Cat = "Sparkles"
end
Now you just require that file wherever you need it. In file_one.rb you put
require_relative './modules/names_module.rb' # Or wherever it is
include Names
You put the same thing in your Review Scraper file. Here's the cool part: if you require the Names module once, it'll be brought into the code. However, if you require it a second time nothing will happen. You won't get warnings. It'll just quietly not require it a second time. One top of that, all your constants are in their own namespace.
Just a thought.

Why did I get "main.using is permitted only at toplevel" when I used a Refinement in IRB?

I tried to use a Refinement in IRB (v0.9.6, Ruby 2.3.0):
module Foo
refine Object do
def foo() "foo" end
end
end
using Foo # => RuntimeError: main.using is permitted only at toplevel
This is basically the exact setup from the documentation (which results in the same error).
What went wrong? How do I fix this?
It's either a bug or a misfeature of IRb. It is well-known that due to the pretty hackish way IRb is implemented, it does not behave correctly for all corner-cases.
The incompatibility probably everybody knows is that in Ruby, methods defined at the top-level become private instance methods of Object, whereas in IRb, they become public instance methods of Object. Another obvious behavioral difference is that in IRb, require_relative doesn't work, because it searches relative to the current file, but in IRb, there is no current file.
There are also some differences in what syntax gets accepted, I believe, and something to do with local variables and when exactly they are and aren't defined.
So, it is not inconceivable that there might also be some behavioral differences wrt. Refinements. In fact, I myself have encountered that error message, and running the exact same code outside IRb, either with ruby -e, from a file, or from a different REPL, always made it go away.

Is it possible to change Ruby's frozen object handling behaviour?

I am submitting solutions to Ruby puzzles on codewars.com and experimenting with how locked into the testing enviroment I am for one of the challenges.
I can redefine the classes used to test my solution but they are defined by the system after I submit my code. If I freeze these objects, the system cannot write over them but a RunTime error is raised when it tries to.
I'm fairly new to Ruby, so I'm not sure which parts (other than falsiness and truthiness) are impossible to override. Can I use Ruby code to force modification of frozen objects to silently fail instead of terminate the program or is that bound up in untouchable things like the assignment operator or similar?
The real answer here is that if you might want to modify an object later, you shouldn't freeze it. That's inherent in the whole concept of "freezing" an object. But since you asked, note that you can test whether an object is frozen with:
obj.frozen?
So if those pesky RuntimeErrors are getting you down, one solution is to use a guard clause like:
obj.do_something! if !obj.frozen?
If you want to make the guard clauses implicit, you can redefine the "problem" methods using a monkey patch:
class Array
# there are a couple other ways to do this
# read up on Ruby metaprogramming if you want to know
alias :__pop__ :pop
def pop
frozen? ? nil : __pop__
end
end
If you want your code to work seamlessly with any and all Ruby libraries/gems, adding behavior to built-in methods like this is probably a bad idea. In this case, I doubt it will cause any problems, but whenever you choose to start hacking on Ruby's core classes, you have to be ready for the possible consequences.

Resources