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}
Related
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).
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.
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'
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
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?