Mike Knepper

Chairman of the Board

February 14, 2014

This week I used Sinatra to add a web interface to my Ruby bank. Sinatra is a DSL for building web applications with Ruby, and in a way can be thought of as a “web framework,” though it is far more lightweight and flexible than Rails.

In the Wee Small Hours of the Morning

My first challenge in adding Sinatra to my app was wondering simply where to put everything, or even at first, anything. My bank has followed a pretty typical pure Ruby structure–a lib directory for my classes and modules, a spec directory for my tests, and a Rakefile and README in the root directory. I’ve enjoyed how lightweight the app has stayed, using only what I needed for running in a command line interface, and one of my two biggest goals in adding Sinatra (assuming a base requirement of it, you know, working) was to keep my app limber, flexible, and well-organized. However, I couldn’t help but think of the default Rails structure generated by rails new, with it’s dozens of preset directories for seemingly every possible aspect of a web app, and worry about my app sprawling out of control in an attempt to recreate everything needed for a web interface.

As with so many things, the key was to just start small. Forget about any data specific to the app–my first step was to put a file somewhere in the app directory that would generate a “Hello world!” page on the web. I decided to just put a file in the root directory named “sinit.rb” which would be responsible for initializing a Sinatra server. The file looked like this:

require 'sinatra'

get '/' do
  "Hello, world!"

…yes I’m serious. That’s all it takes. Don’t believe me? RTFM! OK so great, I could basically copy and paste the first example from the Sinatra docs. It seems like nothing, but in fact, this small step was one of the most powerful for me. It’s always satisfying to see something generate on the screen the way you expect and want (particularly in a web browser for some reason), and to be able to do that with such a concise amount of code was comforting. I realized this wouldn’t require a terrible amount of configuration and directories all over the place if I didn’t want it to.

I’ve Got the World on a String

Things started moving quite quickly after this first step. How about an index of all the bank’s customers? I decided to borrow a few Rails conventions I like by creating a route “/people” for the people index. I needed a more advanced layout than just a string output, so I created an ERB view file, and I needed to grab all the people from the repository. The result?

require 'sinatra'

get '/' do
  "Hello, world!"

get '/people' do
  @people = People.all
  erb :people_index

<h1>People index</h1>
  <% @people.each do |person| %>
    <li><%= person.full_name %></li>
  <% end %>

Can you guess what happened? An error! The file doesn’t know what People.all means. Well, this is actually easily solved–just load all the files into “sinit.rb” so that it understands everything in my app.

Dir["lib/*.rb"].each { |file| load file }

Now “sinit.rb” is aware of everything in the lib directory, and the people index page loads successfully.

I quickly realized that my “sinit.rb” file would quickly overflow with all the various routes I would need for my web interface, so my first refactor was to create a “controllers” directory with files that would be responsible for the various routes related to different models. Again, very clearly poaching from Rails here, but if it works, it works, and I think it’s an elegant way to organize those responsibilities. Similarly, I created subdirectories in the “views” directory for each model, so instead of returning erb :people_index, I returned erb :'people/index'.

Suddenly I had very organically created an organized file structure that I was very comfortable with given my Rails background, but wasn’t overflowing with unnecessary stuff. I added a whole bunch of indexes and show pages without any trouble, and with the help of the Sinatra docs (seriously, RTFM!), I was able to add some fancy things like before filters and helper methods. Because I only have two helper methods and one before filter, they’re still just living in “sinit.rb”–not the best place for them long term, but I haven’t felt any urgency to move them somewhere else quite yet so I’m letting them be.

Just One of Those Things

I mentioned having two big goals in implementing Sinatra. I believe I’ve done a good a job of keeping the app well-organized and lightweight, avoiding adding unnecessary bloat. The second goal was to change as little of the existing code as possible throughout the whole process of adding the web interface. As I’ve described in past blog posts, I’ve been paying close attention to separating concerns in my app, making sure things only have one responsibility and can be extended without requiring modification. This week’s task of implementing Sinatra was a perfect example of why I’ve worked so hard on all that. The core data and functionality of the bank should not depend on how an end user is interacting with it, whether in the command line, on the web, or through some other interface (say, a mobile or desktop app).

I’m quite excited to say that I succeeded in my second goal as well. I did add one method to my Presenter class in order to make an API for my app–I had an existing method for converting an object into JSON for storage in a Riak bucket, but I didn’t have any way to convert a collection of objects. Damn! Other than that, though, the entire implementation of Sinatra was accomplished without modifying any of my existing code.

The Best is Yet to Come

Integrating Sinatra into the bank app this week has been a great experience. I like how lightweight it is and that it allows the developer to organize everything however he or she chooses. So many decisions in programming involve choosing the right tool for the job. I still like Rails and will undoubtedly continue using it for various projects, but I’m excited to now have some experience with Sinatra so that I can make a decision about the web framework component of future apps, rather than relying exclusively on Rails.