Building Simple Web Apps with Ruby, Part 3

Here I want to focus more on the particulars of constructing a Sinatra web application. Up to now you basically have something that can serve up static or dynamic web pages. It’s time to get an application structure in place. This in turn will let us deploy our web application.

Sinatra Application Types

Let’s take a step back for a second. There are two primary approaches to building Sinatra applications: classic and modular.

What I have done so far is a classic application. Classic applications are often single-file, stand-alone apps that are run directly from the command line or with a minimal rackup file. (If you don’t know what a rackup file is, don’t worry. We’ll get there.)

In our script file, the get and post methods are defined at the top level. What this means is that the classic mode will add methods to the Object class of Ruby. Depending on your level of Ruby knowledge, you may see that as a problem. In fact, that could be a problem if you want to ship your application as a gem because you would essentially be polluting the generic Ruby namespace. Another consideration is that you cannot have multiple classic applications running in one Ruby process. What’s nice, though, is that with classic mode you can create very simple, but working Sinatra apps in a single file with the minimal amount of code. Further, you could use classic mode to create Sinatra apps at runtime, entirely in code, from within another application.

Let’s try something. Modify your get route so it looks like this:

Then, after running your app if it is not running already, go to http://localhost:9292. You will see some text returned. Note here that, as I had indicated in the second post of this series, the last part of the route action block is what gets returned. You see that in action here. Even though there is an ERB page available, since the string is last, that is what gets returned.

The reason I had you do this is the output, when you visit http://localhost:9292 shows that all method calls in the routing blocks are actually sent to an instance of the class Sinatra::Application. This means that the routes are passing blocks to instances of Sinatra::Application for evaluation. Where exactly do these route methods live? Change your string to this:

Apparently the superclass of Sinatra::Application is Sinatra::Base.

And that brings us to the modular approach. In this mode you explicitly subclass Sinatra and build your application within that scope. What this means is that modular Sinatra applications are defined within their own namespace and class. These apps inherit from Sinatra::Base as opposed to Sinatra::Application. These applications are often bundled as libraries and used as components within a larger Rack-based system.

Requiring Sinatra as we did so far basically puts all of the Sinatra::Base methods into the global namespace and sets you up with a lot of convenient defaults. Those defaults come from Sinatra::Application. This classic way of doing things is a simple way of putting together a simple web app. It’s also an inappropriate way of creating Sinatra apps that are meant to play nice in a shared space, for example multi-app environments or with Rack extensions that are written in Sinatra.

So let’s convert the classic to modular. Change your app.rb file so that it looks like the following:

What we’ve done here is create a module (Project). That acts as our namespace. Within that module is a class (TestApp) that subclasses Sinatra::Base. Because we are now using Sinatra within a subclassed context, there is a line to register the Sinatra::Reloader module. That wasn’t needed for classic apps but it is for modular apps.

The get and post methods are the same we have been using. (I cleaned up some of the changes we made to test things out.) At the bottom you see a standard code structure that tells how to execute the contents of the script if the file is being called directly from the command line (as opposed to the script being required by another script). Now you can just run the following at the command line:

app.rb

You don’t need to specify the port at the command line anymore because you are doing that already in the script.

Getting Ready to Deploy

This is obviously a very simple start, but — it’s enough to deploy! This is another area I found I wanted to get out of the way before I started too much in-depth development. So let’s get our application ready for deployment. First we’ll create a Gemfile in the project directory:

If you know Ruby you probably know about Gemfiles. This file is just indicating what gems should be used when this project is executed. I’m specifically saying that the version of Sinatra to use should be 1.3.3 since I know everything I’m doing works on that. Now make sure to get the gems you need by running this at the command line:

bundle install

Later you can just type ‘bundle update’ if you want to check for updated gems. This will create a Gemfile.lock file in your project directory. With that file in place, let’s now run our app as a local isolated environment to make sure that all the gems that the app depends on are in the gemfile:

bundle exec ruby app.rb

I actually struggled with what that command was doing for a long time. I now realize it’s providing the operating context that is specified in the Gemfile. This matters because you may have odd multiple gem versions on your machine that give the appearance of working on your development configuration but do not work on a production configuration when the actual gem versions are used.

Assuming your app started up, let’s now allow our application to run as a Rack app. Create a config.ru in your project directory:

Now you can run your app as a Rack app with the following command:

bundle exec rackup config.ru

Great. But what does it all mean? Rack is basically an interface between different web servers and your framework and/or application. Rack is a gem that provides a collection of utilities and helper classes that make it easy to to develop Rack applications by including basic implementations of HTTP request and response, cookie handling, and sessions.

The rackup command we just used converts the supplied rack config file (config.ru) to an instance of Rack::Builder. Rack::Builder provides a DSL that lets you glues various Rack middleware and applications together and convert them into a single rack application. Don’t get too hung up on the details, but just to provide context, this is a rough idea of how this works:

The run method on a Rack::Builder instance specifies the Rack application you’re wrapping with Rack::Builder. In terms of how this works with rackup, here’s a way to think about it:

Again, don’t spend too much time parsing that. It’s not fully in context. But essentially rackup converts the supplied rack config file to an instance of Rack::Builder. Then rackup supplies rack_app to the web server for execution.

What you really need to get out of this is that Rack uses configuration files with the extension .ru, that instruct Rack::Builder what middleware it should use and in which order. Basically, the config.ru file tells Rack how your application should be run. Since our application is really simple, there’s not much to tell it: we just require our app file and run our Project::TestApp class.

The Heroku Environment

Okay, so now let’s get into the web environment type stuff. I want to deploy my app to Heroku. So first let’s talk about the stack. A stack is a complete deployment environment including the base operating system, the language runtime and any associated libraries. With Heroku, the stack on which you can provision new applications is (currently) called Cedar. Specifically, “Celadon Cedar” is Heroku’s default runtime stack. Cedar is what is referred to as a polyglot platform, meaning basically it can handle different languages. So you can host a Java application on Heroku just as you can a Ruby application.

Let’s get some other terms out of the way. A “dyno” is the basic unit of composition on Heroku. For practical purposes, you can think of a dyno as representing your application. A little more detail is that a dyno is a lightweight container that runs a single user-specified command. That command is what starts your application.

The “dyno manifold” is a distributed execution environment for your application’s dyno. You won’t need to worry about this. I bring it up because of another term: slug. “Slugs” are compressed and pre-packaged copies of your application optimized for distribution across the dyno manifold. So when you push your application to Heroku, your code is received by the slug compiler which transforms your code repository into a slug. Your slug is based on your code and language runtimes.

So what all that means is that you can think of a dyno as a virtualized Unix environment container. This container can run any command that makes sense given the default environment in that container. That default environment is what is supplied as part of the Cedar stack as well as your slug.

Whew! The good news is that really you don’t have to know any of that. But I’m one of those people that has to know what I’m deploying to.

Processes and Procfiles

Let’s also talk about the process you require for your application to run. There is a mechanism called Procfile for declaring what commands are run by your application’s dyno on the Heroku platform. A Procfile is a text file that is placed in the root of your application. That file lists the process types in your application. Each process type is essentially a command that is executed when a process of that process type is executed. All the language and frameworks on the Cedar stack declare a web process type, which starts the application server.

You’ve already seen that you can run your app as:

bundle exec ruby app.rb

So that command can then go in a Procfile. First, create a file called Procfile in your project directory and put this in it:

web: bundle exec ruby app.rb -p $PORT

However, if you are deploying as a Rack app, you’ll use this:

web: bundle exec rackup config.ru -p $PORT

I recommend going with the second one there — the rackup version. Again, note that those were the commands we put in from the command line. They’re just now being used in the context of the Procfile to say that a web process will begin when that command is run. One thing to note is that you must put in that “-p $PORT” exactly as is. If you do not do this, you will get the following error: “Error R11 (Bad bind) -> Process bound to port 9292, should be 10791” or something very much like it.

What that line in the Procfile specifically does is declare a single process type, web, and the command needed to run it. The name “web” is important here. It declares that this process type will be attached to the HTTP routing stack of Heroku, and receive web traffic when deployed. If you didn’t use “web” then Heroku would have no idea what process to start to run your application.

Remember that foreman gem you installed from the first post? Foreman will start all of the processes associated with your app and display the standard output (stdout) and errors (stderr) of each process. Basically, Foreman is a command-line tool for running Procfile-backed apps. You can now start your application locally using Foreman like this:

foreman start

Okay, but why do this? It’s important when developing and debugging an application that the local development environment is executed in the same manner as the remote environment. This ensures that incompatibilities and hard to find bugs are caught before deploying to production and treats the application as a holistic unit instead of a series of individual commands working independently. That’s essentially what foreman is helping you determine.

Note that Foreman defaults to using a port of 5000. You could change the port referenced in the Procfile to say “-p 9292” or whatever you want. That would force Foreman to use that port. However, I don’t do that. I instead use the “-p $PORT” syntax and I do that because when you then deploy your application to Heroku, the $PORT will be set by Heroku when the application is started.

You now have the three major components of your app in place: dependencies in a Gemfile, process types in a Procfile, and your application source in app.rb.

Version It Up with Git

We can now git-ize our fledgling application. You will need Git to do just about anything with Ruby when you push to some environment. If you don’t already have Git installed, I recommend doing so. There are directions all over the web for various operating systems and configurations. Once you have Git installed and working, run these commands:

git init
git add .
git commit -m "Initial commit."

You can store your code repository on GitHub if you want, although it isn’t a requirement for deploying to Heroku. Now you have to create an app on Heroku. To create an app on the Cedar stack you use the heroku create command:

heroku create project-app

You’ll obviously want to use your own app name there. What that does is give you an application URL for your application. For me, for example, the URL was http://symbiont-app.herokuapp.com/. Once you’ve done this with your own project, then you can deploy your code:

git push heroku master

And with that you’ve deployed your code to Heroku, and specified the process types in a Procfile. That’s about all you need to do. You can visit the app:

heroku open

That’s the equivalent of going to the app’s URL in a browser.

Essentially you have instructed Heroku to execute a process type (web) and Heroku does this by running the associated command (from your Procfile) in a dyno. You can check the state of your app’s dynos. The following command lists the running dynos of your application:

heroku ps

In the sample app used here, I included Thin in my Gemfile. Sinatra will use Thin to run when it’s available in the environment. If you don’t specify another web server, Rack will use Mongrel, and if that’s not available, it will fall back on the generic WEBrick.

You can check the log of your running Rack application by doing this:

heroku logs

That will come in really handy if you are finding that your application is not starting up correctly.

You can also see what version of Ruby is being used by Heroku. Run one of the following commands:

heroku run 'ruby -v'
heroku run "ruby -e 'puts RUBY_VERSION'"

When I did this I found that Heroku was using Ruby 1.9.2 and I wanted to be using 1.9.3. If you want to use a different version, you can specify that in your Gemfile. I added this line to that file:

I then simply had to redeploy to Heroku to see this take effect.

With that, this seems like a good place to stop for this post. You now have a way to deploy your application to Heroku via Git. You have the basis of how to create some simple routes to handle basic HTML. While we are using ERB as our template, you have seen that this can return basic HTML. But you can also practice putting some Ruby logic in there. In the next post I’ll refocus a bit on some more specifics of generating both static and dynamic content with Sinatra applications.

Share

This article was written by Jeff Nyman

Anything I put here is an approximation of the truth. You're getting a particular view of myself ... and it's the view I'm choosing to present to you. If you've never met me before in person, please realize I'm not the same in person as I am in writing. That's because I can only put part of myself down into words. If you have met me before in person then I'd ask you to consider that the view you've formed that way and the view you come to by reading what I say here may, in fact, both be true. I'd advise that you not automatically discard either viewpoint when they conflict or accept either as truth when they agree.

One thought on “Building Simple Web Apps with Ruby, Part 3”

  1. Followed, completed and deployed to Heroku which feels great

    Lots for me to start digging into to understand exactly what I have done but this is a great start, many thanks for writing these posts and looking forward to the next steps

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.