My classmate Anders and I have been working on a simple forecast app (github, rubygems) for a couple of weeks now. It started as a basic command line utility, and in addition to having some neat functionality, it was a great way to explore some of the things we had learned about in the last month: command-line interfaces, interface-independent object models, simple API calls, building gems and binaries, git workflows, and pair programming. We’ve started to get into building simple web apps, so I decided to port our project over to Sinatra.
Here’s the end product: http://simple-forecast.herokuapp.com/
(A side note: before I started, this seemed like the coolest idea in the world, and one that would be sure to impress people. When I was done, though, it turned out to be a lot of headache to reproduce functionality that Google implements much better already. Honestly the more I think about it the dumber my web app seems. Even so, it was a great exercise in using Sinatra, and the groundwork I’ve laid may even prove useful to making a much more interesting, database-backed Simple Forecast web app in the future.)
1. Sinatra filters
Sinatra has built in hooks that you can use to tap into the moment before a request and the moment after a request. You do it using
after like this:
The instance variables that you create in this block are accessible in your views. This helps to DRY up your code. In this project, I needed to generate an
@forecast variable with every request, so that I could pass the data on to the template. Instead of writing it in every route, I used the
before filter to generate this variable.
2. Acccesing the request object
Sinatra allows you to access the HTTP request very easily. It’s always in a variable called
request. It looks like this:
Through some simple hash-like manipulation I can grab any piece of info about the request and use it in my route:
request.env["REMOTE_ADDR"] gets me the client IP address. It’s not very Ruby-like, but it gets the job done and will have standard formatting with each HTTP request. With Simple Forecast, I’ve also got the Geocoder gem running, so I can do some other really cool things with the request object:
request.location gives me the city, country, latitude, longitude and other information, all accessible through other simple methods.
In my case, I actually ended up wanting to use some of the request info inside a
before filter, where
request had not been built yet. Fortunately, it’s easy enough to recreate, like this:
3. Deploying to Heroku is easy
There’s basically one simple change when you deploy to Heroku: the Procfile. This is a file you need to add to your project that boots your app on the Heroku server. There are a few other straightforward steps, and Heroku has great documentation that walks you through each one so the whole process is painless. For my Sinatra app, they provided me this single line of code to put into the Procfile:
As long as you have a git repo set up and the Heroku toolbelt installed, you just need to run
heroku create next. The final step is pushing up to Heroku just like you would with any other git repo —
git push heroku master.
In my case, I had created an app using Heroku’s dashboard interface already, so I needed to set my git remote to point there instead of to the address that was provided in the creation process. It was super easy to make this change.
4. Debugging a remote server is hard (and testing is important)
Debugging a remote server is hard because you don’t get to see quite as well how things blow up — you can’t use
pry and you don’t get those detailed error pages out of the box. It’s also hard because pushing fixes is more time-consuming than on a local server. I ran into two issues. The first was a 500 Internal Server Error — when you see this, it most likely means something is wrong with your code. In my case, it was roughly equivalent to a Ruby error message, telling me that something in my Ruby code wasn’t correct. The second issue I got was a kind of generic “application doesn’t work” page, which I quickly realized meant that I had left a
binding.pry in my production code.
I’ll admit that when I saw the 500 Internal Server Error I panicked a little bit, because I had no idea where to start. It was even more dire because I was trying to add functionality that couldn’t be properly tested in the browser with a local server (grabbing the client IP address). Essentially, I couldn’t be sure if a fix worked until I pushed up the new code to Heroku and tried accessing the live site again. After thinking about it for a little while, I realized that this must be caused by some problem in my code — more than likely, something was trying to call a method on a nil object.
There are some tools. Heroku provides access to the server logs through
heroku logs. You get output much like your local server logs:
Here you can see that I was getting an undefined method error for something to do with a
Rack::Request. These aren’t quite the detailed error pages you get with Rack and Sinatra but they were sufficient to get me started down the right path.
On this same note, it’s also hard to tell when your app is working correctly. At one point, I wasn’t getting errors, but I was grabbing data for the wrong location and it wasn’t immediately clear that I was getting the wrong data. I eventually sorted it out, but I’m sure that if I had written some acceptance tests, I could have avoided a lot of this. (Each day it becomes clearer and clearer why formal testing is so important.)
An upcoming post will go into detail about the process of turning Simple Forecast into a simple Sinatra app.
Make yourself useful.