How to Access a Variable Defined in a Ruby File I Required in Irb

How can I access a variable defined in a Ruby file I required in IRB?

While it is true that you cannot access local variables defined in required files, you can access constants, and you can access anything stored in an object that you have access to in both contexts. So, there are a few ways to share information, depending on your goals.

The most common solution is probably to define a module and put your shared value in there. Since modules are constants, you'll be able to access it in the requiring context.

# in welcome.rb
module Messages
WELCOME = "hi there"
end

# in irb
puts Messages::WELCOME # prints out "hi there"

You could also put the value inside a class, to much the same effect. Alternatively, you could just define it as a constant in the file. Since the default context is an object of class Object, referred to as main, you could also define a method, instance variable, or class variable on main. All of these approaches end up being essentially different ways of making "global variables," more or less, and may not be optimal for most purposes. On the other hand, for small projects with very well defined scopes, they may be fine.

# in welcome.rb
WELCOME = "hi constant"
@welcome = "hi instance var"
@@welcome = "hi class var"
def welcome
"hi method"
end


# in irb
# These all print out what you would expect.
puts WELCOME
puts @welcome
puts @@welcome
puts welcome

accessing variables in loaded source while in irb

like this:

def my_array
[1, 2, 3, 4, 5]
end

Why does load/require in irb not load local variables?

require/load is not the same as just copying and pasting the file into irb. You do run the file but files have their own scope in ruby. The local variable you create john is scoped to that file. That means when you define it it is available in the file but not outside it. This is a good feature to have: imagine you have a different file that creates a Dog class and assigns john = Dog.new. When you loaded that file it would change the assignment of john in your first file, breaking any code that depends on john being a person. Many Ruby programs contain hundreds of files -- you can imagine how this would be painful. It's the same thing when you have two methods

def method1
john = Dog.new
end

def method2
john = Person.new
method1()
puts john
end

We want to be able to define variables and do things with them without worrying the other code we call will change them. If you call method2 you'll see that john is still a Person. If calling other methods could change your local variables it would be very hard to reason about what was happening.

Local variables inside files are scoped only to those files just like local variables inside methods are scoped only to those methods. If you want to access them outside the file, just make them constants.

JOHN = Person.new

listing the local variables in irb

Your code is really complicated and obfuscated. First, let's clean up your code a bit so that we can see more clearly what's going on. You don't need self, since it is the default receiver anyway. And you don't need send, because you already know the name of the method you want to call. Also, Kernel#local_variables uses the current Binding anyway. Also, typically, such methods which are supposed to by called without an explicit receiver and don't actually make use of self (e.g. like puts or require) are put into Kernel. So, your code is equivalent to the following:

module Kernel
def list_vars
local_variables.map {|var| "#{var} = " + binding.local_variable_get(var).inspect}
end
end

Now we can immediately see the problem: you are reflecting on the local variables and the Binding of list_vars and not the place where it was called from! You need to pass the Binding into the method and use that instead:

module Kernel
def list_vars(binding)
binding.local_variables.map {|var| "#{var} = " + binding.local_variable_get(var).inspect}
end
end

list_vars(binding)

Or, you could make it an instance method of Binding:

class Binding
def list_vars
local_variables.map {|var| "#{var} = " + local_variable_get(var).inspect}
end
end

binding.list_vars

Ruby Object Case-Sensitive in IRB

Your trouble here is that any local variable defined in an included file will be out of scope within IRB. By defining a constant, you were able to get around this. Declaring an explicit global variable $rex or instance variable @rex are alternatives:

@rex = Dog.new
@rex.wants("play")

@rex will now be accessible through IRB...

@rex.wants "x"
# Rex wants to x.
# => nil

@John Hyland has a suggestion for declaring a constant within a module which you might also look into.


Using An Instance Variable Globally

@mudasobwa's accusation of heresy for using an instance variable globally is not without merit. Declaring @rex outside of the context of a class and importing the containing file into IRB will add that instance variable to the main object. This violates Object Oriented design principles, may be confusing, and global variables should generally be avoided.

That said, a globally scoped instance variable may be appropriate if viewed as a "fish out of water" for experimentation purposes. I prefer it to declaring a constant we intend to mutate. Instance variables may also more closely resemble resemble code you'll eventually deploy; often in rails or sinatra applications, instance variables are declared within the scope of a route handler (e.g. #get) that a view can access.


About instance variables

Classically, instance variables should pertain to the class they live within. Here's how that looks:

# dog.rb
class Dog
@@population = 0 # class variable (shared across all Dogs)

def initialize legs=4
@legs = legs # instance variable (for each Dog)
@@population += 1
end
def amputate
@legs-=1 if @legs > 0
@legs
end

# setters and getters (we could DRY this up with attr_accessor...)
def legs= x
@legs = x
end
def legs
@legs
end
def self.count
@@population
end
end

Our class describes a model of what a Dog looks like in our program. Every dog we create will have its own instance variable @legs and share a class variable @@population with all Dogs. Calling Dog.new will invoke #initialize, which sets @legs to 4, by default, and adds 1 to the total population. From irb, we could test this out:

# irb terminal
rex = Dog.new.amputate
fido = Dog.new
puts "rex has %i legs, fido has %i legs" % [rex.legs, fido.legs]
# => rex has 3 legs, fido has 4 legs
puts "We have %i total dog(s)" % Dog.count
# => We have 2 total dog(s)


Related Topics



Leave a reply



Submit