The Devver Blog

A Boulder startup improving the way developers work.

Posts Tagged ‘Sinatra

Making Rack::Reloader work with Sinatra

According to the Sinatra FAQ, source reloading was taken out of Sinatra in version 0.9.2 due to “excess complexity” (in my opinion, that’s a great idea, because it’s not a feature that needs to be in minimal a web framework like Sinatra). Also, according to the FAQ, Rack::Reloader (included in Rack) can be added to a Sinatra application to do source reloading, so I decided to try it out.

Setting up Rack::Reloader is easy:

require 'sinatra'
require 'rack'

configure :development do
  use Rack::Reloader
end

get "/hello" do
  "hi!"
end
$ ruby hello.rb
== Sinatra/0.9.4 has taken the stage on 4567 for development with backup from Thin
>> Thin web server (v1.2.4 codename Flaming Astroboy)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:4567, CTRL+C to stop
[on another terminal]
$ curl http://localhost:4567/hello
hi!

If you add another route, you can access it without restarting Sinatra:

get "/goodbye" do
  "bye!"
end
$ curl http://localhost:4567/goodbye
bye!

But what happens when you change the contents of a route?

get "/hello" do
  "greetings!"
end
$ curl http://localhost:4567/hello
hi!

You still get the old value! What is going on here?

Rack::Reloader simply looks at all files that have been required and, if they have changed on disk, re-requires them. So each Sinatra route is re-evaluated when a reload happens.

However, identical Sinatra routes do NOT override each other. Rather, the first route that is evaluated is used (more precisely, all routes appended to a list and the first matching one is used, so additional identical routes are never run).

We can see this with a simple example:

require 'sinatra'

get "/foo" do
 "foo"
end

get "/foo" do
 "bar"
end
$ curl http://localhost:4567/foo
foo   # The result is 'foo', not 'bar'

Clearly, Rack::Reloader is not very useful if you can’t change the contents of any route. The solution is to throw away the old routes when the file is reloaded using

Sinatra::Application.reset!

, like so:

configure :development do
  Sinatra::Application.reset!
  use Rack::Reloader
end
$ curl http://localhost:4567/hello
greetings!

Success!

A word of caution: you MUST call

reset!

very early in your file – before you add any middleware, do any other configuration, or add any routes.

This method has worked well enough for our Sinatra application. However, code reloading is always tricky and is bound to occasionally produce some weird results. If you want to significantly reduce the chances for strange bugs (at the expense of code loading time), try Shotgun or Rerun. Happy reloading!

Advertisements

Written by Ben

December 21, 2009 at 3:20 pm

Single-file Sinatra apps with specs baked-in

It’s so easy to create little single-file apps in Sinatra that it almost seems a shame to start a second file just for tests.  The other day Dan and I decided to see if we could create a Sinatra app with everything – including the tests – baked right in.  Here’s what we came up with.

#!/usr/bin/env ruby
require 'rubygems'
gem 'rack', '=0.9.1'
gem 'thin', '=1.0.0'
require 'sinatra'

get '/' do
  content_type 'text/plain'
  "Hello, world"
end

# Run me with 'spec' executable to run my specs!
if $0 =~ /spec$/
  set :environment, :test
  set :run,         false       # Don't autostart server

  require 'spec/interop/test'
  require 'sinatra/test'

  describe "Example App" do
    include Sinatra::Test

    it "should serve a greeting" do
      get '/'
      response.should be_ok
      response.body.should == "Hello, world"
    end

    it "should serve content as text/plain" do
      get '/'
      response.headers['Content-Type'].should == 'text/plain'
    end

  end
end

view this gist
The code switches modes on the name of the executable used to run the file. If we run it with the

spec

command, we get a test run:

$ spec -fs sinatra-tests-baked-in.rb

Example App
- should serve a greeting
- should serve content as text/plain

Finished in 0.007221 seconds

2 examples, 0 failures

Otherwise, if we call it as a Ruby program, it runs the Sinatra server as we would expect:

$ ruby sinatra-tests-baked-in.rb
== Sinatra/0.9.1.1 has taken the stage on 4567 for development with backup from Thin
>> Thin web server (v1.0.0 codename That's What She Said)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:4567, CTRL+C to stop

And there you have it: a true single-file application, specs and all.

Written by avdi

May 13, 2009 at 9:00 am

Posted in Development, Hacking, Ruby

Tagged with , ,

Managing Amazon EC2 with your iPhone

I wanted a quick way when out and about to easily manage our AWS EC2 instances while out and about. It hasn’t happened often, but occasionally I am away from the computer and I need to reboot the instances. Perhaps I remember our developer cluster isn’t being used and want to shut it down to save some money.

I didn’t find anything simple and free with a quick Google search, so in a about an hour I wrote a nice little Sinatra app that will let me view our instances, shutdown, or reboot any specific instance or all of them. The tiny framework actually turned out to be even more useful as I now have options that let us tail error logs, reboot Apache, reboot mongrel clusters, or execute any common system administration task.

I won’t be going into detail on how to build a iPhone webapp using Sinatra and iUI, because Ben already created an excellent post detailing all of those steps. In fact I used his old project as the template when I created this project. I can’t begin to explain how amazingly simple it is to build an iPhone webapp using Sinatra, so if you have been thinking of a quick project I highly recommend it.

Here are some screen shots showing the final app. (screenshot courtesy of iPhoney):

ec2 manager home view

ec2 manager home view.

ec2 manager describe view

ec2 manager describe instances view.

ec2 manager instance view.

ec2 manager instance view.

This app uses the Amazon EC2 API Tools to do all the heavy lifting. So this app assumes that you already have the tools installed and working on the machine you want this app to run on. This normally involves installing the tools and setting up some environment variables like EC2_HOME, so make sure you can run ec2-describe-instances from the machine. After that you should just have to change EC2_HOME in the Sinatra app to match the path where you installed the EC2 tools.

Let me know if you have any issues, it is quick and dirty, but I have already found it useful.

To run the app:
cmd> ruby -rubygems ./ec2_manager.rb

require 'sinatra'

EC2_HOME = '~/.ec2'

use Rack::Auth::Basic do |username, password|
  [username, password] == ['some_user', 'some_pass']
end

get "/" do
  @links = %w{describe_ec2s restart_all_ec2s shutdown_all_ec2s}.map { |cmd|
    cmd_link(cmd)
  }.join
  erb :index
end

get "/describe_ec2s" do
  results = `cd #{EC2_HOME}; ec2-describe-instances`
  instances = results.scan(/INSTANCE\ti-\w*/).each{|i| i.sub!("INSTANCE\t",'')}
  @links = instances.map { |i|
    instance_link(i)
  }.join
  erb :index
end

get "/restart_all_ec2s" do
  @results = `cd #{EC2_HOME}; ec2-describe-instances`
  instances = @results.scan(/INSTANCE\ti-\w*/).each{|i| i.sub!("INSTANCE\t",'')}
  cmd="cd #{EC2_HOME}; ec2-reboot-instances #{instances.join(' ')}"
  @results = `cmd`
  erb :index
end

get "/shutdown_all_ec2s" do
  @results = `cd #{EC2_HOME}; ec2-describe-instances`
  instances = @results.scan(/INSTANCE\ti-\w*/).each{|i| i.sub!("INSTANCE\t",'')}
  cmd="cd #{EC2_HOME}; ec2-terminate-instances #{instances.join(' ')}"
  @results = `cmd`
  erb :index
end

get "/instance/:id" do
  id = params[:id] if params[:id]
  verify_id(id)
  @results = `cd #{EC2_HOME}; ec2-describe-instances #{id}`
  @links = "<li><a href='/shutdown/#{id}' target='_self'>shutdown #{id}</a></li>"
  @links += " <li><a href='/reboot/#{id}' target='_self'>reboot #{id}</a></li>"
  erb :index
end

get "/reboot/:id" do
  id = params[:id] if params[:id]
  verify_id(id)
  @results = `cd #{EC2_HOME}; ec2-reboot-instances #{id}`
  erb :index
end

get "/shutdown/:id" do
  id = params[:id] if params[:id]
  verify_id(id)
  @results = `cd #{EC2_HOME}; ec2-terminate-instances #{id}`
  erb :index
end

helpers do

  def cmd_link(cmd)
    "<li><a href='#{cmd}' target='_self'>#{cmd}</a></li>"
  end

  def instance_link(instance)
    "<li><a href='/instance/#{instance}' target='_self'>#{instance}</a></li>"
  end

  def verify_id(id)
    raise Sinatra::ServerError, 'bad-id, What you doin?' unless id.match(/i-\w*/)
  end

end

use_in_file_templates!

__END__

@@ index



@import "/stylesheets/iui.css";




<div class="toolbar">
<h1 id="pageTitle"></h1>
</div>


<ul id="home">
<li><a href='/' target='_self'>home</a></li>


</ul>





<li><strong>results</strong></li>

<ul id="home">
<li><a href='/' target='_self'>home</a></li>

&lt;%= @results.gsub(&quot;\n&quot;,&quot;<br />") %&gt;
</ul>




view this gist

Written by DanM

March 5, 2009 at 10:03 am