Skip to content

Instantly share code, notes, and snippets.

@ashrithr
Last active December 31, 2016 04:58
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ashrithr/6460552 to your computer and use it in GitHub Desktop.
Save ashrithr/6460552 to your computer and use it in GitHub Desktop.
Intro to Sinatra

Sinatra

Installation:

Dependencies: ruby

gem install sinatra

BootStrapping:

Bare Minimum:

  • app.rb => contains application logic*

     require 'sinatra/base'
     #Application inheriting sinatra's base class
     class App < Sinatra::Base
     	#Routes
     	#get is a request with relative url and a block to execute
     	get '/' do
     		'Hello World'
     	end
     end
  • config.ru => rack app for configuring sinatra app

     require './app'
     run App
  • Or start the application in the same app.rb using App.run!

Running the App:

rackup

rackup will automatically lookup config.ru and starts the application using webbrick

To provide a custom port for rackup use rackup config.ru --port=4567

Routing:

verbs in http GET

  • GET equivalent to get in sinatra. curl -v http://localhost:9292 will show the request type of get '/'

  • POST equivalent to post in sinatra, post can be used to submit forms

     post '/' do
     	'Hello World'
     end
     
     curl -X POST -v -d "" http://localhost:9292
    
  • PUT equivalent to put in sinatra.

     put '/' do
     	'Hello World'
     end
     
     curl -X PUT -v -d "" http://localhost:9292
    
  • DELETE equivalent to delete

     delete '/' do
     	'Hello World'
     end
     
     curl -X DELETE -v -d "" http://localhost:9292
    

Dynamic Routes:

Routes built dynamically using variables inside requests

get "/hello/:name" do |app_name|
	"Hello #{app_name}"
end

try visiting: http://localhost:9292/your_name

Optional variables in routes:

#here lname is optional in the request
get "/hello/:fname/?:lname?" do |f, l|
	"Hello #{f} #{l}"
end

Templates:

HAML and ERB are popular templating langugaes for sinatra

Refreshing views|templates does not require rackup to reboot, because there is no ruby code being changed

Templates reside in views folder of the application

app.rb

IMAGES = [
  { title: "Utopia", url: "http://www.paradise-engineering.com/utopia/utopia.jpg" },
  { title: "Alaska", url: "http://www.travelalaska.com/~/media/Images/Travel%20Alaska/Content/HomePage/ChugachMountains.jpg" },
  { title: "Unknown", url: "http://www.beautifullife.info/wp-content/uploads/2010/12/31/the-unknown.jpg" }
]
get '/images/:id' do |index|
	index = index.to_i
	@image = IMAGES[index]
	
	haml :'images/show', layout:true
end

views/images/show.haml

%h1 Image: #{@image[:title]}

%p
  %img{src: @image[:url]}

views/layout.haml

!!! 5
%html
  %head
    %meta(charset="utf-8")
    %title Exhibit
  %body
    = yield

Filters:

There are 2 different types of filters, and they act as callback's that hook into a request execution.

before block is executed before each and every request, useful for logging and other preq stuff.

after block is executed after each and every request

Instance variables passed inside before and after blocks are shared with remaining requests

Filters specific to only particular requests using regular expressions, ex. only do something for images request's

before /images/ do
	@message = "images specific requests"
end

Sessions:

Stores temporary data over the life time of a session

Example to store user data from form to session variables:

  • enable sessions: enable :sessions

  • form for user to enter data, post data and store that data as session variable

    app.rb:

     get '/sessions/new' do
         erb :"sessions/new"
     end
     
     post '/sessions' do
     	#assign height from form into params to session variable called height
     	session[:height] = params[:height]
     	#puts request.inspect
     end
     
     before do
     	@user = 'Ashrith'
         #make session variable global for all requests
         @height = session[:height]
         puts '===> Entering request'
     end

    views/sessions/new.erb

     <form action='/sessions' method='post'>
       <p>
     	<input type='text' name='height' placeholder='what is your height?' /> cm
       </p>
    
       <p>
     	<input type='submit' value='store my height information' />
       </p>
     </form>

    views/layout.rb

     <!doctype html>
     <html>
     	<head>
     		<meta charset="utf-8" />
     		<title>Exhibit</title>
       </head>
       <body>
     	<h1>Using ERB layout.</h1>
         <p>The current user is <%= @user %>.</p>
    
     	<ul>
           <li>
     	    <a href="/sessions/new">New Session</a>
           </li>
       	  <li>
       	  	<!-- session variable -->
         	<%= @height %>
           </li>
     	</ul>
    
         <% if @message %>
           <p>You have message:</p>
           <blockquote>
             <%= @message %>
           </blockquote>
         <% end %>
         <%= yield %>
       </body>
     </html>

Logging

Sinatra has logging inbuilt

  • Enable logging using enable :logging

  • Then use logging as so:

     logger.info 'info level message'
     logger.debug 'debug level message'
     logger.warn 'warn level message'
     logger.error 'error level message'
     logger.fatal 'fatal level message'
    

The inbuilt logger has no ability to write out to file, to do so use comphrensive logging utility like log4r

config.ru

require 'log4r'
logger  = Log4r::Logger.new 'app'
logger.outputters << Log4r::Outputter.stderr

file = Log4r::FileOutputter.new('app-file', :filename => 'log/app.log')
file.formatter = Log4r::PatternFormatter.new(:pattern => "[%l] %d :: %m")
logger.outputters << file

and then disable default logging of sinatra by removing enable :logging and instantiate log4r logger object like so before using it:

  before do
    @user = 'Ashrith'
    #make session variable global for all requests
    @height = session[:height]
    logger = Log4r::Logger['app']
    logger.info '===> Entering request'
  end

MimeTypes and Attachements:

render image directly using sinatra, ?:format? will store the format of the file extension and can be passed as a block param

  #support formats, render image
  get "/images/:index.?:format?" do |index, format|
    index = index.to_i #routes are strings
    @index = index
    @image = IMAGES[index]

    if format == 'jpeg'
      content_type :jpeg # image/jpeg
      # send file uses path relative to this file
      send_file "images/#{index}.jpeg"
    else
      #this is how you send folders as reference to templates 'views/images/show.haml'
      #also specifying the layout file from 'views/layout.haml'
      haml :"images/show", layout: true
    end
  end

Making a image downloadle using attachment

  #downloadable images
  get "/images/:index/download" do |index|
    @image = IMAGES[index.to_i]
    # method that forces the app to make it a downloadable file, rather than just serving it inline
    attachment @image[:title]
    send_file "images/#{index}.jpeg"
  end

Configurations:

http://www.sinatrarb.com/configuration.html

Error handling:

Extensions:

Render PDF'S

Steps for using extensions:

  • Install the extension gem install dberkom-sinatra-prawn -s http://gems.github.com
  • Require the lib require sinatra/prawn
  • register the extensiosn register Sinatra::Prawn

Sample Code for rendeing pdf with prawn:

app.rb

  #Prawn pdf rendering
  get "/sample.pdf" do
    # attachment #make this file downloadable instead of printing it inline
    content_type :pdf
    @message = "Hello from PDF!"
    #use prawn template to generate pdf
    prawn :samplepdf
  end

views/samplepdf.prawn

pdf.text @message
# "This is sample PDF using the Sinatra::Prawn extension!"

More extensions: http://www.sinatrarb.com/extensions-wild.html

Using DataBases:

Gems used: data_mapper, sinatra-contrib (contains Namespace extension) and dm-sqlite-adapter (contains datamapper sqlite adapter)

db.rb

env = ENV['RACK_ENV'] || 'development'
url = "sqlite://#{Dir.pwd}/db/#{env}.sqlite3"
 #mysql_url = "mysql://user_name:password@localhost/db_name"
DataMapper.setup :default, url

 #Class to map database
class Image
  include DataMapper::Resource

  property :id, Serial
  property :title, String
  property :url, String, length: 0..250
  property :description, Text
end

DataMapper.finalize
DataMapper.auto_upgrade!

app.rb

  namespace "/images" do
    get do #/images
      @images = Image.all #fetch all images from database
      haml :"/images/index", layout_engine: :erb
    end

    get "/:id" do |id|
      @image = Image.get(id) #fetch image from db
      haml %s(images/show), layout_engine: :erb
    end

    post do #/images
      @image = Image.create params[:image]
      redirect "/images"
    end
  end

views/images/index.haml

%h1 Images

-if @images.count == 0
  %h4 You have no images.

-@images.each do |image|
  %h2
    %a{href: "/images/#{image.id}"}=image[:title]
  %p
    %img{src: image[url], width: 40}
  %blockquote=image[:description]

%h2 Store a new image

%form.row(action="/images" method="post")
  %p.six.columns
    %label Title
    %input(type="text" placeholder="Enter a title" name="image[title]")
  %p.six.columns
    %label URL
    %input(type="url" placeholder="Enter a url" name="image[url]")
  %p.twelve.columns
    %label Description
    %textarea(placeholder="Enter a quick description" rows="3" name="image[description")
  %p.twelve.columns
    %input.button(type="submit" value="Create Image")

views/images/show.haml

%h1 Image: #{@image.title}

%p
  %img{src: @image.url}

%p
  %a{href: "#{@index}/download"} Download this image
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment