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.
Thanks