• 20 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 10/4/17

Working in Multiple Files

Log in or subscribe for free to enjoy all this course has to offer!

 

If you look at any open source Ruby projects online, you'll quickly notice that their code isn't all written in one file. In fact, programmers generally try to reduce the length of the files they are writing by splitting their code up into multiple files. This is partly because it's much easier to read and work on a program when the different parts are broken down into granular units of logic, rather than being written in one big file whose logic you have to follow in loops throughout the code. (this is sometimes called spaghetti code https://en.wikipedia.org/wiki/Spaghetti_code)

Let's take a look at one of the most popular Ruby projects, ActiveRecord.
https://github.com/rails/rails/tree/master/activerecord

Like most Ruby libraries, ActiveRecord keeps its code in a directory called lib. Let's look in there. You can click on the lib directory (https://github.com/rails/rails/tree/master/activerecord/lib) in github. In here, as I'm writing, I can see two directories, called activerecord and rails (github shows rails/generators, but that's just a shorthand to say that the rails directory contains only one child directory, "generators"), and single Ruby file: active_record.rb. If we take a peak inside the active_record directory (https://github.com/rails/rails/tree/master/activerecord/lib/active_record) we can see a lot of files!

Running a Ruby program, however, no matter how complicated, always begins with running a single file. You can see that here by navigating back up to the lib directory and opening the active_record.rb file there (https://github.com/rails/rails/blob/master/activerecord/lib/active_record.rb). You see it mostly contains require statements, something called modules, and a bunch of lines that say extend, autoload, or eager_autoload. These are all just ways of saying "go get some code from another file". Thus, active_record.rb is the file that ties together the rest of the files in the lib/active_record directory.

So, In order to split your program's logic up across multiple files, Ruby starts with the file that starts your program up, and then uses one of these ways of finding the other files in your program. The first one of these we see in active_record.rb is "require", so let's see how that works first.

Using require

We'll create two files. We'll call them "a.rb" and "b.rb":

a.rb

puts "I want to load some data from b.rb"

b.rb

B_DATA = "This is from b"

Now run a.rb:

$rubya.rb
"Iwanttoloadsomedatafromb.rb"

In order to access the data in b.rb, we'll use require. Change a.rb to look like this:

a.rb

puts "I want to load some data from b.rb"

require "b.rb"

puts B_DATA

Now run a.rb again:

$rubya.rb
Iwanttoloadsomedatafromb.rb
/Users/alegscogs/.rbenv/versions/2.3.1/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in`require':cannotloadsuchfile--b.rb(LoadError)
from/Users/alegscogs/.rbenv/versions/2.3.1/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in`require'
froma.rb:3:in`<main>'

Clearly, it doesn't work. That's because require has special rules for loading files. It looks for your files in a global variable called $LOAD_PATH.

Let's see what's in that variable. Change a.rb to look like this:

a.rb

puts "I want to load some data from b.rb"

puts "But my load path is: #{$LOAD_PATH}"

and run it again:

$ ruby a.rb

This time you should see an array output for the value of$LOAD_PATH. If you installed rbenv, you should see the path to your installed Ruby version, under the .rbenv directory in your home directory. In any case, you should see the path of your current Ruby version in here, but you probably don't see the local directory where your file is running.

You should also see at least one directory that says something about "lib/ruby/gems" and "did_you_mean". We'll learn more about this element of the$LOAD_PATHarray in the next chapter.

We can fix this by passing an option to the Ruby command when we run our file. The "-I" switch let's us pass in a directory name to add to the$LOAD_PATHvariable. In unix systems, we can specify the local path with.. Let's try that here:

$ ruby -I . a.rb

Now, if you take a look at the values in$LOAD_PATH, you should see the ., or current directory, at the beginning of the load paths. This means we can go back to requiring our file as before:

a.rb

puts "I want to load some data from b.rb"

require "b.rb"

puts B_DATA

In fact, we can also just specify that we want to look in the current directory right inside our call to require. Change a.rb again to look like this:

a.rb

puts "I want to load some data from b.rb"

require "./b.rb"

puts B_DATA

And run it again, this time without the "-I" switch:

$ ruby a.rb

Finally, since$LOAD_PATHis just a variable in Ruby, we can also just modify it using Ruby. Try this:

a.rb

puts "I want to load some data from b.rb"

$LOAD_PATH.unshift('.')
require "b.rb"

puts B_DATA

Now run a.rb *without* the "-I" switch:

$ ruby a.rb

There's one problem with referring to the local directory from inside the Ruby file, as opposed to adding it from the command line with the "-I" switch: '.' refers to the directory where you ran the Ruby process, not the directory of the Ruby file you're running. In our case, those happen to be the same thing. But what happens if you navigate to the next higher directory, and then run your file from there, specifying the file's nested path:

$cd../
$ruby./src/a.rb
Iwanttoloadsomedatafromb.rb
/Users/alegscogs/.rbenv/versions/2.3.1/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in`require':cannotloadsuchfile--b.rb(LoadError)
from/Users/alegscogs/.rbenv/versions/2.3.1/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in`require'
fromalegscogs/a.rb:4:in`<main>'

It doesn't work, because.now references the directory I'm in while issuing the "Ruby" command. This is a pretty serious limitation, since code will often be run from different places. Fortunately, there's another helpful constant variable in Ruby called__FILE__. It stores the relative path to the current file, so we can use that to add the local directory to the current Ruby file. We'll need a special object here called File as well, that has a method to expand our local filename into a directory:

a.r

puts "I want to load some data from b.rb"

local_dir = File.expand_path('../', __FILE__)
$LOAD_PATH.unshift(local_dir)
require "b.rb"

puts B_DATA
puts "P.S. Now my load path is: #{$LOAD_PATH}"

Now you can run the file again from one directory up, or anywhere, with no special switches to the Ruby command:

$ ruby src/a.rb

If you included the last line in the code example above, you can see that the$LOAD_PATHhas the right directory in it, wherever you call this file from.

requireis a method of theKernelobject

requirestands alone syntacticaly as if it were a keyword or special command in Ruby. But in fact, like almost everything you do in Ruby, it's a part of a bigger object. In this case, require is a method of the Kernel object.

What does it mean to be a method of an object?

You can think of objects as collections of two things: data, and methods

A "method", in most programming languages, means a function that is attached to an object, and internally can access that object's data and other functions.

The implicit receiver

When you call a method on an object, that object is the method's "receiver". Every method has a receiver. Methods can't just float around as things in themselves, as functions can in, for example, Javascript. When a method is called without a "." and an object before it, it's being called with an "implicit receiver".

We'll talk more about this when we get into object-oriented programming later in the class, but for now realize that, even when you write a standalone method call likerequirein Ruby that doesn't seem to be connected to anything else, it is always actually a method call on some implicit object.

Example of certificate of achievement
Example of certificate of achievement