How to load a ruby file in to IRB? - ruby

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'

Related

difference between node.run_state vs global varibale in a ruby file chef

would like to know the difference and recommended approach to use between global variable vs node.run_state
test.rb
dbpassword=''
ruby_block "load_databag_secret" do
block do
secret_key = Chef::EncryptedDataBagItem.load_secret("/home/test/db_key")
db_keys = Chef::EncryptedDataBagItem.load("mydatabag", "mydatabagitem", secret_key)
end
dbpassword=db_keys['DB_PASSWORD']
node.run_state['password']=db_keys['DB_PASSWORD']
end
end
execute "Enable on hosts" do
command lazy { "echo #{node.run_state['password']} > /home/app/db.txt" }
end
template "/config/properties" do
source "properties.erb"
variables(lazy {
:db_password => { node.run_state['password'] },
})
or using node.run_state['password'] in place of global variable in this .rb file
Now execute command worked fine im able to see the password on the echoed file db.txt where as when i used lazy in template variables it outputed as empty value for db_password in template.
So a few issues, first what you have there isn't a global variable, it's a local variable. Globals in Ruby start with $. Second, you can't assign to a local variable from an enclosing scope like that in Ruby (or, indeed, in most languages). That assignment just creates a second dbpassword local variable scoped to the block. You could, however, use a mutation rather than a variable assignment (e.g. dbpassword << whatever). Third, you can't actually use lazy deeply inside the variables hash like that, it has to be at the top level. Fourth, you can straight up side-step all of this if you're only using the value you once like in that example:
template "/config/properties" do
source "properties.erb"
variables lazy {
secret_key = Chef::EncryptedDataBagItem.load_secret("/home/test/db_key")
db_keys = Chef::EncryptedDataBagItem.load("mydatabag", "mydatabagitem", secret_key)
{db_password: db_keys['DB_PASSWORD']}
}
end
Just for completeness in case others find this via Google, with real global variables the biggest difference is unit testing, the run state is tied to the converge so individual unit tests won't see each other's values which is always nice (though of course you could work around this in your code).

irb access pre-load script local variables

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.

Is there a way to force a required file to be reloaded in Ruby?

Yes, I know I can just use load instead of require. But that is not a good solution for my use case:
When the app boots, it requires a config file. Each environment has its own config. The config sets constants.
When the app boots, only one environment is required. However, during testing, it loads config files multiple times to make sure there are no syntax errors.
In the testing environment, the same config file may be loaded more than once. But I don't want to change the require to load because every time the a spec runs, it reloads the config. This should be done via require, because if the config has already been loaded, it raises already initialized constant warnings.
The cleanest solution I can see is to manually reset the require flag for the config file after any config spec.
Is there a way to do that in Ruby?
Edit: adding code.
When the app boots it calls the init file:
init.rb:
require "./config/environments/#{ ENV[ 'RACK_ENV' ]}.rb"
config/environments/test.rb:
APP_SETTING = :foo
config/environments/production.rb:
APP_SETTING = :bar
spec/models/config.rb: # It's not a model spec...
describe 'Config' do
specify do
load './config/environments/test.rb'
end
specify do
load './config/environments/production.rb'
end
Yes it can be done. You must know the path to the files that you want to reload. There is a special variable $LOADED_FEATURES which stores what has been loaded, and is used by require to decide whether to load a file when it is requested again.
Here I am assuming that the files you want to re-require all have the unique path /myapp/config/ in their name. But hopefully you can see that this would work for any rule about the path name you can code.
$LOADED_FEATURES.reject! { |path| path =~ /\/myapp\/config\// }
And that's it . . .
Some caveats:
require does not store or follow any kind of dependency tree, to know what it "should" have loaded. So you need to ensure the full chain of requires starting with the require command you run in the spec to re-load the config, and including everything you need to be loaded, is covered by the removed paths.
This will not unload class definitions or constants, but simply re-load the files. In fact that is literally what require does, it just calls load internally. So all the warning messages about re-defining constants will also need to be handled by un-defining the constants you expect to see defined in the files.
There is probably a design of your config and specs that avoids the need to do this.
if you really want to do this, here's one approach that doesn't leak into your test process. Fork a process for every config file you want to test, communicate the status back to the test process via IO.pipe and fail/succeed the test based on the result.
You can go as crazy as you want with the stuff you send down the pipe...
Here's some quick and dirty example to show you what I mean.
a config
# foo.rb
FOO = "from foo"
another config
# bar.rb
FOO = "from bar"
some faulty config
# witherror.rb
asdf
and your "test"
# yourtest.rb
def load_config(writer, config_file)
fork do
begin
require_relative config_file
writer.write "success: #{FOO}\n"
rescue
writer.write "fail: #{$!.message}\n"
end
writer.close
exit # maybe this is even enough to NOT make it run your other tests...
end
end
rd, writer = IO.pipe
load_config(writer, "foo.rb")
load_config(writer, "bar.rb")
load_config(writer, "witherror.rb")
writer.close
puts rd.read
puts rd.read
puts rd.read
puts FOO
The output is:
success: from foo
success: from bar
fail: undefined local variable or method `asdf' for main:Object
yourtest.rb:24:in `<main>': uninitialized constant FOO (NameError)
as you can see, the FOO constant doesn't leak into your test process etc.
Of course you're only through half way because there's more to it like, making sure only one process runs the test etc.
Frankly, I don't think this is a good idea, no matter what approach you chose because you'll open a can of worms and imho there's no really clean way to do this.

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}

Reading in variables from a file in Ruby

Is there a way to read in a file of environment variables?
In bash I have a file env.sh that I can use
env.sh
foo="bar"
bash file
set -a
source env.sh
This would allow me to just use foo as if I had delcared it in the ruby script.
Also is it there a way to make sure that this file is unreadable so that passwords could be stored in this file?
It sounds like you should provide a file example for the user/admin to modify for their personal environment, and then populate the environment from that, while avoiding, perhaps, having that file with the sensitive information in a repository. Note: per file security is going to be addressed by where the file is located and your operating system, and server software.
If this is the case, then you can provide a file that holds a template of the kind of things that you would require from the administrator/user of the program you are configuring.
Ruby has the ENV constant that acts like a Hash and holds the environment of the shell you are using.
As an example, there is a file called environment.rb.sample that gets shared with anyone, publicly. It has instructions and holds the template that users can modify freely, with instructions to copy the file to environment.rb. The sample file looks like this:
# environment.rb.sample
# Copy this file to environment.rb and change the name and password to your credentials
ENV['temp_user_name'] = 'Foo Bar'
ENV['temp_password'] = 'Dazz Kezz
The file is then copied to this, perhaps:
# environment.rb
ENV['temp_user_name'] = 'Joe Admin'
ENV['temp_password'] = 'Super Secure Password'
The file that loads this and uses it is just a Ruby file that is freely modified by the user/administrator of the software, and looks like this and is also shared publicly.
# load_environment
require './environment'
puts ENV['temp_user_name']
puts ENV['temp_password']
This loads the file and uses the ENV that is a globally scoped constant for the application.
The file permissions are then managed by the user/administrator of the system and secured like any other sensitive information on their system. The sensitive file should also be listed in the repository's ignore mechanism. It should never be made public.
Yes, there is, and if for some bizzare, arcane reason you must use it, it's eval:
WARNING: Never use this on a user-supplied file
And, unless you have a very, very specific need, don't use it in production code.
eval(File.read("name_of_var_file"), binding)
If what you're really trying to do is write a configuration file, use YAML. A file like this:
config.yaml:
foo: "bar"
Can be accessed like this:
require 'yaml'
conf = YAML.loads(File.read("config.yaml"))
conf['foo'] #=> 'bar'
This is secure and manageable, not to mention standard practice.
As for making the file inaccessible, that is an operating system level problem that can't be solved without information on the environment, OS, setup, etc.
The purpose of a local variable is to be used temporally within a method definition or a block. Using it outside of such environments, particularly across files defeats the purpose of it. You should not need to do it, and Ruby does not have a simple way to do it.
If you are using variables correctly, and want to share variables between files, that should be other types of variables such as instance, class, or global variables. Or, for the purpose of setting environments, you should be using constants. Among them, global variables and constants can be written in a file, loaded in a different file, and be used.
file-a.rb
$foo = 1
FOO = 2
file-b.rb
load "file-a.rb"
$foo # => 1
FOO # => 2
As for instance and class variables, they belong to a class or an instance of it, so they should be defined in such environment. And you can reopen the same class within a different file, and load it in another file.
file-a.rb
class Bar
##foo = 1
def initialize; #foo = 2 end
end
file-b.rb
load "file-a.rb"
Bar.class_variable_get("##foo") # => 1
Bar.new.instance_variable_get("#foo") # => 2

Resources