irb access pre-load script local variables - ruby

i'm using irb -I . -r script.rb to require a script before starting an interactive session. functions defined in the global scope are available but variables aren't unless they are declared with #
is there a way to access local variables defined in the global context of a script or a better way to do this
script.rb:
def func() "..." end
a = "str"
#b = 1
then after irb starts:
irb(main):001:0> a
NameError: undefined local variable or method `a' for main:Object
from (irb):1
from /usr/bin/irb:11:in `<main>'
irb(main):002:0> #b
=> 1
irb(main):003:0> func
=> "..."
i'm assuming that the script's contents are executed as if defined in a function (eg: main in C-type languages) and so the variables at the global context are local variables that are not accessible outside that scope
so do most people use # variables when writing their scripts?
the use-case is narrow in scope (script development) and the solution is trivial (search-replace any variable with #variable), but I'm learning the semantics of the language and I'm curious about this. Can't the execution context be exposed and merged into the current context somehow?

No. You can't access local variables from a different scope. That's the whole point of local variables: they are local to the scope they are defined in.
In this case, a is local to the script body of script.rb.

Maybe binding.irb is what you are looking for.
script.rb:
def func() "..." end
a = "str"
#b = 1
binding.irb
From the documentation:
Opens an IRB session where binding.irb is called which allows for
interactive debugging. You can call any methods or variables available
in the current scope, and mutate state if you need to.

Related

Include another script in Ruby script

Let's say I have the following scripts.
script1.rb
x=1
script2.rb
# Something like: import script1
y=2
puts x+y
Is it possible to "merge" these scripts, i.e. to somehow "import" script1.rb into script2.rb so that running script2.rb would print "3"?
I have already tried statements like require_relative, load but the result is:
b.rb:4:in <main>': undefined local variable or methodx' for
main:Object (NameError)
yes:
require_relative 'yourOtherFile'
You don't need to tack .rb on the end. The "relative" part in require_relative means "look in the same folder I am in for the other file"
If you want to access one variable from file A in file B then you have to make it a global ($x = 5). This will work but isn't a great way to organize your code (because it makes for an environment where the global scope is polluted with lots of different variables.
You should organize your code into classes:
class Planet
##x = 0
def self.doStuffToX
#xx += 4
end
def self.getX
##x
end
end
and you can require this file, then use Plant.getX and Plant.doStuffToX to access and change x.
Yes, using require_relative, but it will only work for global variables and constants. This is a good thing.
Any constants or globals within the loaded source file will be available in the calling program’s global namespace. However, local variables will not be propagated to the loading environment.
script1.rb
X = 1
script2.rb
require_relative 'script1.rb'
y = 2
puts X + y
To access a constant this is fine, nothing can change it.
But if you want to alter x then it has to be a global variable $x and that's not good. Globals are to be avoided because it's hard to control how they are used or modified. You should instead redesign the code in script1.rb to be a class.

Using require to use file with hash

I am trying to refer to a hash written in another file, and tried:
require './filewithhash' #this file has hash
puts name_hash['somename'] #just trying access by the index
and got undefined local variable or method error.
The problem is that when requiring file in ruby, local variables are out of scope. Therefore any local variables defined in required file will be not available later on.
Solutions:
Make your program object oriented and add this hash as a field in some class
Make your variable constant (like this: NAME_HASH)
Make your variable global (like this: $name_hash)
Make your variable an instance variable (like this: #name_hash)
If program is very simple, I would choose option 3. However if it's suppose to grow, 1 is your best choice.
Requires in Ruby don't just inline the file like in PHP; each file has its own scope. That is, the code in the file will execute in a scope that means that all local variables are all constrained to that file's scope, and will not be visible outside of it.
In order to execute a file in the current scope, you're going to have to eval it with the current binding:
file = "file_with_hash.rb"
File.open(file) {|f| eval f.read, binding, file }
This is a bad idea and you shouldn't do it unless you know what you're doing and why. Instead, you should expose that hash as a member of a constant (which transcends the file scope):
# foo.rb
module Foo
PROGRAMMER_WORDS = {foo: 1, bar: 2, baz: 3}
...
end
# another file
require 'foo'
Foo::PROGRAMMER_WORDS # => {:foo=>1, :bar=>2, :baz=>3}

How to load a ruby file in to IRB?

I have a file: options.rb
I open IRB and type:
require './options.rb'
#=> true
Try to call a variable in the options file such as key (yes this variable is there and the file is saved)
NameError: undefined local variable or method `key' for main:Object
from (irb):2
Why is this not working? By the way also tried to load the file as: irb -r ./options.rb
UPDATE
Also tried to do load './options.rb' which does return #=> true but this also does not work.
From the require docs:
Any constants or globals within the loaded source file will be available in the calling program’s global namespace. However, local variables will not be propagated to the loading environment.
So if in options.rb you have something like:
key = something
(i.e. key is a local in the file) then it will not be available in irb. If you make it a global (e.g. $key = 'something') or a constant (e.g. KEY = 'something') it should be available.
If you do not like global variables (as matt suggested) you might also make it an instance variable of the object irb is running on (an instance of Object available through self as ruby always has an object it is operating on) you may also assign
#key='value'
in your file which will give you access to #key in your irb-session afterwards. This will work with either require as with load, but require will only load the file if it has not already done so while load will always execute the code in the file and thus will end up overwriting the contents of your variable if it has been changed in the mean time.
Ruby is a interpreted language, so for the interpreter to notice your declarations you need to actually 'run' them, the corresponding command in irb is
load './options.rb'

What's the point of an interactive Ruby subshell?

Starting up an interactive Ruby shell in the Terminal ('irb'), one can continue to open up irb subshells endlessly. What's the point of this?
So far I've seen three usefull things irb subsessions can do for you:
undefine local variables
change self of an irb session
irb is a part of a great set of tools
undefine local variables
The nested irb starts a new subsession in which all local variables (not classes, modules etc.) are not defined any more.
irb(main):001:0> a = 1
#=> 1
irb(main):002:0> irb
irb#1(main):001:0> a
NameError: undefined local variable or method `a' for main:Object from (irb#1):1
change self for an irb session
irb(main):001:0> self
#=> main
irb(main):002:0> irb "Hello World"
irb#1(Hello World):001:0> self
#=> "Hello World"
irb#1(Hello World):002:0> length
#=> 11
Note: This is also known as "change binding" of an irb session.
By the way: It's possible to change the binding without opening a subsession (cb, irb_change-binding both do that for you). But it's more convenient to get back to the old binding with subsession.
The best thing is, that irb is just one of a useful set of commands
irb: start a new subsession
jobs: list subsessions
fg: switch to a subsession
kill: kill a subsession
See this insteresting SO answer for details.

access scope of 'required' files to get variables

If I require 'helper_file' in a program and there are constants and variables declared in that required file is there a way to access those variables and constants?
require 'helper_file'
...some nice code
x = ConstantFromRequireFile
You use require to load a library into your Ruby program. It will return true if successful.
So you have a file example.rb:
require 'library.rb'
# Some code
x = CONSTANTFROMREQUIREFILE
puts x # "Hello World"
method_from_required_file # "I'm a method from a required file."
and a file library.rb:
CONSTANTFROMREQUIREFILE = "Hello World"
def method_from_required_file
puts "I'm a method from a required file."
end
As you can see you access the constant and the method as you would access a constant and a method from the same file.
You can read more about require here: What is the difference between include and require in Ruby? and here: Kernal Module in Ruby
constants, global variables, instance variables and class variables defined in the top level scope in a required file will all be available in the scope of the requiring file, but local variables will not. What exactly is your problem?

Resources