Understanding Rails by Building a Ruby Web Framework From Scratch

I’ve been teaching a part time Back End Web Development course at General Assembly which focuses on building web applications using Ruby and Rails. I very purposely say “Ruby and Rails” instead of “Ruby on Rails” because understanding the Ruby language will allow you to understand any Ruby web framework. One of the biggest challenges in teaching the course is explaining all of the “magic” that Rails uses to do its job. Having written a custom MVC framework on top of Sinatra many times before I understand the broad strokes of that magic but to really understand Rails and even the more basic Sinatra I think you need to go deeper and start with Rack and a basic knowledge of Ruby fundamentals. That’s why I’ve been building a Ruby web framework on top of Rack for the last few days. If you want to understand Rails then let’s check out Rack and build a basic web framework.

What is Rack

Rack is a Rubygem and a specification to allow Ruby code to interface with web servers. In the old days (and even today) if you wanted any language to run on your web server and speak HTTP you’d use CGI, Common Gateway Interface. Using CGI you could make just about any language from C to Perl, Python, or anything else run on a server and respond to HTTP requests. CGI got this reputation for being slow and so some languages sought to replace CGI with something more suited to them. Python has WSGI, PHP (a language built specifically for web development) had FastCGI and PHP-FPM, and now Ruby has Rack. These days just about every Ruby web framework including Rails uses Rack as its interface. But enough about the history of Rack. Let’s talk about what makes it so useful.

Rack has a very simple API for handling requests and serving responses. A Rack application is nothing more than a Ruby class or proc that responds to a call method. The call method takes an environment hash (containing HTTP headers and other env vars) and responds with an array with 3 elements:

  • The HTTP response code as an integer (the usual 200, 404, 500, etc. response codes)
  • A hash of response headers (like the Content-Type and such)
  • The response body which must respond to each (so either a hash or array)

A simple Rack app

Based on the spec above, a simple Rack application would look like this. The Rack website has a simple example using a Proc object so mine will be different and more like something you’d use:

class MyApp
  def call(env)
    [200, {'Content-Type' => 'text/html'}, ['My response body']]

The above is a working Rack compatible application. That’s all you need! And all Rack apps start from there and build up functionality like Routers and request parsers on top of them. So with that taken care of you need to be able to run the application. To do that you need a Rackup file which is a Ruby file with a .ru extension. So assuming we have a file myapp.rb in the root folder of a project, we’d add a config.ru file to that same project and run the app like this:

require './myapp.rb'

run MyApp.new

Now you can go to localhost:9292 and view the response. Rack will use the built-in Webrick web server but you can customize this along with the port and other options by specifying some command line flags or adding them in a special shebang line in the Rackup file. But so far this isn’t very useful. We’d want dynamic responses.

Building on functionality

So at this point we have a Rack compatible class that will respond to HTTP requests but it’ll be the same response over and over. We need to add features like routing and response methods for different content types and statuses. I’m currently building a Ruby web framework from scratch called Savannah to add these features. Its a great learning experience and I encourage you to fork the code and use it yourself or contribute if you like. At the moment I’m looking to build a simple Sinatra style framework for my own projects but I’m also hoping that when I finish it’ll be useful enough for others as well.

Features to be included:

  • Routing: I really like the simplicity of Sinatra applications but I also like how Rails handles routing. So I want to combine the two and have a DSL for routing and have my controller methods be mapped to URLs
  • All the other features Sinatra has

Remember, this is a learning exercise and not an attempt to dethrone Rails as the go-to Ruby web framework.

Know Rack, Know everything

Once you understand Rack you’ll be a far more effective Ruby web app developer. I encourage you to build a Rack app yourself just for the experience. You’ll find that suddenly all the magic of Rails doesn’t look like magic anymore. It’s just very logical Ruby code that’s been hidden from you in a gem.

Web development

« Use a custom domain and SSL certificate with Amazon Cloudfront How to structure Bookshelf.js models »