Ruby for Programmers – Part 9: Making Pages With Bundler and Rack

So ends the really basic tutorial. There’s still tons more stuff to go over, like Modules and Mixins, Monkeypatching, Begin/Rescue/Ensure, but we’ll get to those basics as we come to them. If you really want to learn some deep dark mysteries, I suggest reading the Pickaxe Book. It’s a complete, unabridged, and also includes information about the standard library too.

So for now, we’re going to focus on creating a web app with Rails. There are other Ruby-based frameworks like Sinatra, we’re going to be focusing on Rails since it’s got, at least in my opinion, the right amount automagic for what we’re going to build. (Of course one could argue that Node or Java or some other language would be better. That’s not the scope of this, but to learn Rails.)

I’ve always found it better to learn Rails from the ground-up from its parts rather than a “here’s all the random things it can do!” that’s in most docs/tutorials. So to build a webpage from scratch, you must first invent the universe.

In which case, you must start with a Gemfile and Bundler. In the dayes of olde, gems were not included with Ruby, but rather a separate installation. These days, it’s included with the Ruby source as part of a standard installation. However, what’s missing is a package version manager, which is Bundler. You can install bundler as you would any other gem:

Bundler ensures that everybody is running the same version of gem as you, which is very important as APIs and functions changes, and helps ensure security updates by listing gem versions.

So for now, we’re going to create a Gemfile in our project and list Rack as a gem:

Bundler allows a bunch of configuration options in the Gemfile (which can be read in the Bundler docs). To note the current lines, the first tells Bundler to enforce a Ruby version. This is slightly helpful if you have a custom installation on a server, but especially helpful on cloud services like Heroku which otherwise wouldn’t know which Ruby version to use. (You don’t automatically want to use the latest and greatest because it might introduce bugs.) The second determines where the gems will download from (always rubygems.org). Then list the gems by name, and optionally by version. You can do the normal >, >=, <, <= tags, but ~> is special. It means that “incrementing the last number is okay, but not the others”. In this case Bundler can safely install 1.5.2, 1.5.3, 1.5.1052, but not 1.6.0. Also note that when installing on a server, Bundler will install the exact gem version you have from the Gemfile.lock file.

To install your gems just run bundle install from where your Gemfile is. This will create the Gemfile.lock which will tell servers and other programmers which version to use. If you’re missing a version, running bundle install will install it. If you wish to change a version, update the Gemfile and run bundle update rack or whatever the name of the gem to update. This will re-run the dependency check and put the new version in.

Once you have Rack installed, now it’s time to use it. While Rack can be called from a method like anything else, Rack apps typically have a “config.ru” file which is just a special file it looks for to operate.

So to create our first Rack app, just insert the following into a new config.ru file:

The first two lines tells Ruby to include rubygems, a holdover from the previously said days where gems is not included but good to do anyway. The second runs the Gemfile and includes all of its requested gems. Then we tell Rack to run a very simple app, which is already returned as an array with three elements: the HTTP status code (200 for page info, 404 for not found, etc.), the headers in a Hash, then the body of the response. Note that the body of the response has to respond to #each for metaprogramming and lazy-loading reasons we’ll go over later. Just for now, let’s make it a single element Array with a String.

To run your app, just run bundle exec rackup on the command line. (Bundle also ensures that you run the right version from the command line too, how nice!) Then just open your browser to localhost:9292 (or whichever port it chooses), and your page now loads! Nice, neat, easy simple. Except that every page is the same. Lets view what’s in the env variable:

Quit your app and restart to load new code. Reload the page to view all kinds of wonderful information:

So from here we can get the path requested and do things if we wanted to, like this:

It’s not pretty, but it does either show front page if you go to http://localhost:9292/ and a 404 error if you go to http://localhost:9292/asdf.

So right here off the bat, it’s going to show lots of things that are different if you are coming from a generic PHP side of things. PHP is generally a server plugin that operates based on which file is being requested. Even if you’re doing mod_rewrite stuff, it still generally just requests a file. Any changes to the file is fine, as the script is run on each page load.

Here though, it’s an app in the background that accepts a real HTTP connection. The app has to be running and any changes requires a restart of the app. It’s more of a tradeoff though: Large apps can keep it’s compiled code in memory and respond quicker vs. parsing scripting on each page load. For Ruby, since the code parsing for large apps is fairly slow, the server model is used.

Not all is lost, you can simply put your app behind a reverse proxy (Apache instructions, Nginx instructions). Now using straight Rackup and WEBrick (Ruby’s included web server in ruby) is not a good idea on the deploy-side. There’s better options for those that run your own servers, like Unicorn, Puma, Passenger, the latter available in both open-source and enterprise editions, and Heroku, Docker, and AWS Beanstalk on the cloud-side. We’ll be mostly focusing on app building rather than deployment for the most part.