Skip to content

Instantly share code, notes, and snippets.

@markbates
Created December 21, 2012 18:21
Show Gist options
  • Save markbates/4354727 to your computer and use it in GitHub Desktop.
Save markbates/4354727 to your computer and use it in GitHub Desktop.
Getting Started with Sinatra

In an earlier video we took a look at Rack to build incredibly lightweight web applications with Ruby. Rack's toolkit allowed us to quickly throw to get a working application, but we did have to put a little effort into it once we wanted to build something a little more complex.

Sometimes you want a fast and simple framework for building a simple web application. Perhaps you only need to respond to a handful of routes, or you want the response time for a small part of a bigger application to be lighting fast. The Sinatra framework is made for just these moments.

Today let's take a quick look at this framework and see how quickly we can build lightweight web applications.

To get started we first need to install the Sinatra gem:

gem install sinatra

I would also recommend installing the Thin web server as well, but it's not required.

gem install thin

With Sinatra installed let's write the most basic Sinatra application we can.

require 'sinatra'

get "/" do
  "Hello World!"
end

We can run this application using Ruby:

ruby basic.rb

If we navigate to http://localhost:4567 we should be greeted with "Hello World!".

In order to run Sinatra applications on a host, such as Heroku, we need to run the Sinatra applications using Rack. If we change the name of our file from basic.rb to basic.ru we can run the file using Rack.

rackup basic.ru

However when we try to run this file we get an error:

/Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/builder.rb:129:in `to_app': missing run or map statement (RuntimeError)
  from /Users/markbates/Dropbox/screencasts/getting_started_with_sinatra/basic.ru:1:in `<main>'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/builder.rb:40:in `eval'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/builder.rb:40:in `parse_file'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/server.rb:200:in `app'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/server.rb:301:in `wrapped_app'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/server.rb:252:in `start'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/server.rb:137:in `start'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/bin/rackup:4:in `<top (required)>'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/bin/rackup:19:in `load'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/bin/rackup:19:in `<main>'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/bin/ruby_noexec_wrapper:14:in `eval'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/bin/ruby_noexec_wrapper:14:in `<main>'

The error is telling us that we are missing a run or map statement so that Rack knows what to run.

We can correct this problem by adding one line to the bottom of our file:

require 'sinatra'

get "/" do
  "Hello World!"
end

run Sinatra::Application.run!

The Sinatra::Application.run! method returns a Rack application that we can pass to Rack's run method. Now we can run this file, navigate to http://localhost:4567 and see "Hello World!" again.

When we require Sinatra in our file Sinatra adds a handful of methods to the environment for us. These methods:

  • get
  • post
  • put
  • delete
  • patch
  • options

all correspond to the appropriate HTTP verb of the same name. There are other methods defined, but let's just focus on these for now. So in our code when we call the get method, giving it a pattern of "/" we are telling Sinatra to match a GET request to that pattern. This works great for something very simple and quick, like we've been doing, but, it can lead to a very big problem.

Let's take a look at that problem.

require 'sinatra'

class Problem
end

puts Problem.private_methods.grep(/^get$/)

First, we require Sinatra as we've been doing. Next, let's create a new class called Problem. We won't define any methods on this class.

Finally let's grep through the private methods for a method called get.

[markbates@markmini getting_started_with_sinatra]$ ruby problem_1.rb 
get

When we run this we run this code we see that we do, in fact, have a get method defined on the Problem class, even though we never defined one ourselves.

So where is this get method being defined? Well let's use a little bit of Ruby magic to get the method get from the Problem class and use the source_location method on that method to find out where the get method is being defined.

require 'sinatra'

class Problem
end

method = Problem.method(:get)
location = method.source_location.inspect
puts "Source location is: #{location}"
Source location is: ["/Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/sinatra-1.3.3/lib/sinatra/base.rb", 1658]

Running this code show us the source of the Problem class' get method is actually being defined by Sinatra.

As you can imagine, this could lead to some potentially disastrous bugs down the line.

Thankfully Sinatra does have a nice fix for this problem.

require 'sinatra/base'

get "/" do
  "Hello World!"
end

run Sinatra::Application.run!

If we require sinatra/base instead of just sinatra then Sinatra will no longer pollute the environment with these method definitions. However, if we were to run our code again we'd get a big error telling us that there is no get method defined.

[markbates@markmini getting_started_with_sinatra]$ rackup class.ru 
/Users/markbates/Dropbox/screencasts/getting_started_with_sinatra/class.ru:4:in `block in <main>': undefined method `get' for #<Rack::Builder:0x007f966b022200 @run=nil, @map=nil, @use=[]> (NoMethodError)
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/builder.rb:51:in `instance_eval'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/builder.rb:51:in `initialize'
  from /Users/markbates/Dropbox/screencasts/getting_started_with_sinatra/class.ru:1:in `new'
  from /Users/markbates/Dropbox/screencasts/getting_started_with_sinatra/class.ru:1:in `<main>'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/builder.rb:40:in `eval'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/builder.rb:40:in `parse_file'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/server.rb:200:in `app'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/server.rb:301:in `wrapped_app'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/server.rb:252:in `start'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/lib/rack/server.rb:137:in `start'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/gems/rack-1.4.1/bin/rackup:4:in `<top (required)>'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/bin/rackup:19:in `load'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/bin/rackup:19:in `<main>'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/bin/ruby_noexec_wrapper:14:in `eval'
  from /Users/markbates/.rvm/gems/ruby-1.9.3-p327/bin/ruby_noexec_wrapper:14:in `<main>'

To get us back on track we can create a new class and have that class subclass Sinatra::Base:

require 'sinatra/base'

class MyApp < Sinatra::Base

  get "/" do
    "Hello World!"
  end

end

run MyApp.run!

Inside of the MyApp class we now have access to the Sinatra methods again. Our application should run just like before.

Now that we've cleaned up the application, let's add a little spice to it. Let's change the greeting we present based on a parameter.

require 'sinatra/base'

class MyApp < Sinatra::Base

  get "/" do
    "Hello #{params[:name] || "World"}!"
  end

end

run MyApp.run!

If there is a name parameter we'll use that name to greet, otherwise we'll just use "World" instead.

When we navigate to http://localhost:4567 we see "Hello World!" as before. But, if we add the parameter name=Mark we should now see "Hello Mark!".

require 'sinatra/base'

class MyApp < Sinatra::Base

  before do
    @name = params[:name] || "World"
  end

  get "/" do
    "Hello #{@name}!"
  end

end

run MyApp.run!

We can clean this code up further by using a before block to set a @name instance variable that our code can use instead.

This before block will be run by any methods that follow it.

Before we go, let's very quickly look at in-line named parameters.

require 'sinatra/base'

class MyApp < Sinatra::Base

  get "/hello/:name" do
    "Hello #{params[:name]}!"
  end

end

run MyApp.run!

Let's create a route that matches /hello/:name. Whatever we type after "/hello/" for the URL will be translated into a parameter called name.

Now can navigate to http://localhost:4567/hello/Mark and be greeted with "Hello Mark!". This is great for making IDs or slugs part of your URLs.

Sinatra is a lot more than I showed you here, so I suggest you read the great documentation that's available on line for it. In future videos we'll dive deeper into Sinatra, as it's a great tool to have in your toolkit.

That's it, for now. I hope this helps.

@csi21
Copy link

csi21 commented Nov 7, 2018

Thanks

@Azhan-Aathir
Copy link

wow!!!

@Altonymous
Copy link

Any suggestions on how to get Sinatra to stop intercepting routes that aren't defined? I have an existing web server that's built-in to a gem I use for some basic pages.. and when I add Sinatra that integration breaks because Sinatra takes control of the routing for everything, even things that aren't defined in my code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment