- Recognize common Ruby application conventions
- Understand how to set up a
bin
file to run a Ruby application - Understand how to set up an environment file to load things when a Ruby application starts
Most Ruby applications we'll be dealing with in this phase will use the command line. Don't be fooled though — command-line apps may seem simple and less fancy than a fully developed computer program with a graphical user interface, but CLI apps can be just as robust and powerful, if not more so.
For the most part, Ruby CLI apps tend to start the same way — typing ruby
followed by a relative path to a Ruby file. In a complex application, though,
which file should be run? In addition to this, when we run the file, how do we
ensure all our necessary files are required?
There are two common conventions we're going to look at in this lesson that
address these questions: using a "run" file, and setting up the environment in
environment.rb
.
In the previous lesson, we had Ruby code in two separate files, lib/garden.rb
and lib/plant.rb
. We were able to require plant.rb
from inside garden.rb
.
This allowed us to run ruby lib/garden.rb
without error. Doing it this way
works, but is a little sloppy. The lib/garden.rb
file should only contain the
Garden
class. It doesn't make sense to also make it the file that runs
everything.
Running an application is its own process and deserves its own file. This way, we have a single place to start the application every time.
Sticking to conventions, a run file has already been created in this example,
bin/run
, that has the following code:
#!/usr/bin/env ruby
require_relative '../lib/garden'
require_relative '../lib/plant'
lawn = Garden.new(name: 'Front Lawn')
basil = Plant.new(name: 'Basil')
basil.garden = lawn
cucumber = Plant.new(name: 'Cucumber')
cucumber.garden = lawn
p lawn.plants
With the run file set up, we can run ruby bin/run
and see the run
file in action. Let's step through this file briefly.
The very first line of bin/run
can be broadly ignored for our purposes. It is
known as a shebang and is used to indicate what interpreter should be used
to read the code in this file. On Unix systems, with the proper configuration,
this file can run without having to write ruby
before it in the command line.
Don't worry if this does not work for you, though. We can still run the file
using ruby bin/run
. The shebang is just an optional line that makes this file
more functional.
Notice that the file doesn't have the .rb
extension. This file is designed
to be an executable and the lack of file extension indicates this. Some runner
files will still have the .rb
extension — they may not be meant as an
executable file, but are still designed to be the point of entry into the
application.
Run files act in some way to start an application. Before that, though, they
typically need to load up any necessary files. We can see this in
bin/run
. In the first lines of recognizable Ruby code, we require
both our local files located in lib
:
require_relative '../lib/garden'
require_relative '../lib/plant'
With Garden
and Plant
loaded, we can proceed to run our very simple
application — relating a few Plant
instances to a Garden
instance.
lawn = Garden.new(name: 'Front Lawn')
basil = Plant.new(name: 'Basil')
basil.garden = lawn
cucumber = Plant.new(name: 'Cucumber')
cucumber.garden = lawn
p lawn.plants
In this particular example, the run file does a few different things to show how the setup works. Commonly, though, run files can have very little in them. A run file may load up the necessary support files, then just call a class to handle the logic of running the actual application.
Whenever we run our simple application, we load the two files in lib
. If we
wanted to add a third class in a new file, once created, we'd have to add a
third require_relative
for it in the run file. Seems easy enough so far.
With more complex applications, there may be multiple places where we need to
load the same application files. For example, if we added tests to this
application, we would load files like lib/garden.rb
and lib/plant.rb
into
our test files. That means we would need a second set of require_relative
statements somewhere in the tests. Now, it isn't terribly difficult to
maintain two sets of the same code, but why should we repeat ourselves? Better
if we can avoid doing that.
The common solution to this is to create one file that has all the
require_relative
statements. Then, in the run file, we just require that
file. If we had tests, we could also just require that file. If we add more
classes, we only need to modify a single file, and any place in our application
requiring that file will automatically receive the updates!
In Ruby frameworks like Rails, this file is called environment.rb
and is
located in the config
folder. We'll follow this convention — a file with this
name already exists, all we need to do is copy the require_relative
statements
from bin/run
to config/environment.rb
, then replace them,
requiring config/environment.rb
in our run file.
For config/environment.rb
, that would be:
require_relative '../lib/garden'
require_relative '../lib/plant'
And for bin/run
:
#!/usr/bin/env ruby
require_relative '../config/environment'
lawn = Garden.new(name: 'Front Lawn')
basil = Plant.new(name: 'Basil')
basil.garden = lawn
cucumber = Plant.new(name: 'Cucumber')
cucumber.garden = lawn
p lawn.plants
In addition to requiring all necessary files, config/environment.rb
is also a
common place for configuring application settings. This is an ideal location,
for instance, for configuring access to a database that an application can use.
To recap, in this lesson, we looked at two components commonly found in Ruby
applications. The first is the run file, something that acts as the starting
point of the application. The second is the environment.rb
file. Typically loaded
when an application is started or tests are run, this file loads any required
application files and handles any configuration that has to happen every time
the application starts.
Combining the two, we have a multi-file application that is starting to show
some complexity! With our set up, we can add whatever files we want to lib
,
require those files in config/environment.rb
, and they will be loaded every
time we run bin/run
.