Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swing when you’re winning - an introduction to Ruby and Sinatra

Swing when you’re winning - an introduction to Ruby and Sinatra

Session originally given at Scotch on the Rocks 2013 in Edinburgh.

In this session we will explore how to build a RESTful-based application using the Sinatra framework, built on top of the Ruby programming language. We will explore installing Ruby, creating our first Sinatra application, the use of route definitions to handle multiple METHOD request types, including GET and POST requests, data persistence in a SQLite database, and how to return data in multiple formats including JSON and HTML. The RESTful approach and ease of use offered by Sinatra make it a great choice for underlying API requests which you can implement and call from any programming language of your choice.

Matt Gifford

June 07, 2013
Tweet

More Decks by Matt Gifford

Other Decks in Technology

Transcript

  1. == Sinatra has taken to the stage with backup from

    Matt Gifford @coldfumonkeh www.monkehworks.com
  2. WHAT WE’LL COVER: Getting your first Sinatra project up and

    running What is Sinatra? Brief overview of a Sinatra application structure Handling routes and formats Deploying to Heroku * * other options do exist
  3. WHAT ELSE IS ON? Using personas in service design -

    continuously Mura 6 for developers Tuüli Aalto-Nyyssonen Steve Withington
  4. WHY NOT RAILS? It is awesome though... Rails is quite

    large and sometimes too much monkeh$ rails create [stuff] Ships with ORM, routes, test suites etc Easily installed with RubyInstaller(.org)
  5. SO WHY SINATRA? Doesn’t force any framework Extremely lightweight A

    Domain Specific Language (DSL) Incredibly quick and simple (but plays well with others if you want to)
  6. SO WHY SINATRA? Runs on Rack Can be a single

    file web application Can use a number of JavaScript libraries Can use a number of template engines It also means I can show pictures like this...
  7. SINATRA IS GREAT FOR REST APIs Small apps useful for

    AJAX calls to data Keeping your simple application simple If it gets too big, consider using Rails (or CF)
  8. monkeh$ ruby application.rb == Sinatra/1.4.2 has taken to the stage

    on 4567 for development with backup from Thin >> Thin web server (v1.5.1 codename Straight Razor) >> Maximum connections set to 1024 >> Listening on localhost:4567, CTRL+C to stop RUNNING THE APP
  9. require 'rubygems' require 'sinatra' get '/hi' do "Hello World!" end

    WHAT’S GOING ON? <--- Ruby package manager
  10. require 'rubygems' require 'sinatra' get '/hi' do "Hello World!" end

    WHAT’S GOING ON? <--- Ruby package manager <--- Sinatra gem
  11. require 'rubygems' require 'sinatra' get '/hi' do "Hello World!" end

    WHAT’S GOING ON? <--- Ruby package manager <--- Sinatra gem <--- GET request
  12. require 'rubygems' require 'sinatra' get '/hi' do "Hello World!" end

    WHAT’S GOING ON? <--- Ruby package manager <--- Sinatra gem <--- GET request <--- response
  13. get '/hi' do "Hello World!" end ROUTE BLOCKS <--- this

    is a route block A route block is an HTTP method paired with a URL-matching pattern. Processing takes place within the block (database calls etc) and the result is sent to the browser.
  14. REST get '/' do .. show something .. end post

    '/' do .. create something .. end put '/' do .. replace something .. end patch '/' do .. modify something .. end delete '/' do .. annihilate something .. end options '/' do .. appease something .. end link '/' do .. affiliate something .. end unlink '/' do .. separate something .. end
  15. ROUTES get '/' do "Home page" end get '/hello' do

    "Hello!" end get '/hello/:name' do "Hello! #{params[:name]}" end
  16. ROUTES get '/say/*/to/*' do # matches /say/hello/to/world params[:splat] # =>

    ["hello", "world"] end get '/download/*.*' do # matches /download/path/to/file.xml params[:splat] # => ["hello", "world"] end get '/download*.*' do |path, ext| # matches /download/path/to/file.xml [path, ext] # => ["path/to/file", "xml"] end
  17. ROUTES get %r{/hello/([\w]+)} do "Hello, #{params[:captures].first}!" end get '/posts.?:format?' do

    # matches "/posts " and any extension # eg "GET /posts.xml" or "GET /posts.json" end
  18. ROUTES get '/hi', :agent => /Mozilla\/(\d\.\d)\s\w?/ do "You’re using Mozilla

    version #{params[:agent][0]}" end get '/hi' do # matches all non-Mozilla browsers end
  19. monkeh$ rvm install 1.9.3 monkeh$ rvm use --default 1.9.3 monkeh$

    rvm use jruby http://cheat.errtheblog.com/s/rvm
  20. SINGLE PAGE APP application.rb require 'rubygems' require 'sinatra' get '/'

    do html = '<form method="post">' html += '<input type="text" placeholder="Add the URL to shorten here..."' html += 'name="url" id="url" />' html += '<input type="submit" value="Shorten!" />' html += '</form>' html end post '/' do html = '<p>Thanks for submitting a URL.</p>' html end
  21. USING VIEWS application.rb require 'rubygems' require 'sinatra' get '/' do

    erb :index end post '/' do erb :index end views/index.erb <form method="post"> <input type="text" placeholder="Add the URL to shorten here..." name="url" id="url" /> <input type="submit" value="Shorten!" /> </form>
  22. LAYOUTS A template that calls yield to draw in view

    data Can also be managed through route blocks A template called “layout” will be used by default
  23. USING LAYOUTS views/layout.erb <!DOCTYPE html> <html> <head> <title>Sinatra Intro</title> </head>

    <body> <%= yield %> </body> </html> views/index.erb <form method="post"> <input type="text" placeholder="Add the URL to shorten here..." name="url" id="url" /> <input type="submit" value="Shorten!" /> </form>
  24. USING LAYOUTS views/layout.erb <!DOCTYPE html> <html> <head> <title>Sinatra Intro</title> </head>

    <body> <%= yield %> </body> </html> views/index.erb <form method="post"> <input type="text" placeholder="Add the URL to shorten here..." name="url" id="url" /> <input type="submit" value="Shorten!" /> </form>
  25. STATIC CONTENT All static content is stored within a new

    directory “public” This is primarily for Javascript CSS Images
  26. DIRECTORY STRUCTURE | -- application.rb | -- views -- index.erb

    -- layout.erb | -- public -- css -- style.css -- javascript
  27. HELPERS application.rb helpers do def random_string(length ) rand(36**length).to_s(36) end def

    get_site_url(short_url ) 'http://' + request.host + '/' + short_url end def generate_short_url(long_url @shortcode = random_string 5 get_site_url(@shortcode) end end
  28. FILTERS application.rb # This code will run before each event

    # Very useful for debugging parameters sent via the console before do puts '[Params]' p params end # This code will run after each event after do puts response.status end
  29. CONFIGURATION Will run once at startup Can be used to

    set application-wide values and options perform certain processes per environment
  30. CONFIGURATION configure do # All environments end configure :production do

    # Production only end configure :development, :test do # Development and Test end
  31. CONFIGURATION configure do set :variable, 'foo' # multiple options set

    :variable1 => 'Hello', :variable2 => 'world' # same as set :option, true enable :option # same as set :option, false disable :option end get '/' do settings.variable? # => true settings.variable # => 'foo' end
  32. CONFIGURATION configure :development do # very useful for debugging parameters

    sent via the console before do puts '[Params]' p params end end
  33. DATAMAPPER Define mappings in your model Comes bundled with tools

    to assist with migration constraints transactions timestamps validations ...and more!
  34. CONFIGURATION application.rb configure do # load models $LOAD_PATH.unshift("#{File.dirname(__FILE__)}/lib" ) Dir.glob("#{File.dirname(__FILE__)}/lib/*.rb")

    { |lib| require File.basename(lib, '.*') } DataMapper::setup(:default, "sqlite3://#{Dir.pwd}/myDatabase.db") DataMapper.finalize DataMapper.auto_upgrade! end
  35. MODEL lib/short_url.rb class ShortURL include DataMapper::Resource property :short_url, String, key:

    true, unique_index: true, required: true property :url, Text, required: true property :created_at, DateTime property :updated_at, DateTime end
  36. SAVING DATA application.rb def generate_short_url(long_url @shortcode = random_string 5 su

    = ShortURL.first_or_create( { :url => long_url }, { :short_url => @shortcode, :created_at => Time.now, :updated_at => Time.now }) get_site_url(su.short_url) end
  37. GETTING DATA application.rb # root page get '/' do #

    get the current object of all links stored @urls = ShortURL.all; erb :index end views/index.erb <h1>Serving <%= @urls.count %> links</h1>
  38. DIRECTORY STRUCTURE | -- application.rb | -- views -- index.erb

    -- layout.erb | -- public -- css -- style.css -- javascript | -- lib -- shorturl.rb
  39. CONTENT TYPES Return content in a number of formats, including

    JSON XML HTML monkeh$ gem install json May need some more gems to process
  40. CONTENT TYPE application.rb get '/' do if params[:url] and not

    params[:url].empty? @shortURL = generate_short_url(params[:url]) content_type :json { :original_url => params[:url], :short_url => @shortURL }.to_json else # get the current count of all links stored @urls = ShortURL.all; erb :index end end
  41. TESTING WITH RSPEC spec/application_rspec.rb require_relative '../application.rb' require 'rack/test' set :environment,

    :test def app Sinatra::Application end describe 'URL Shortening Service' do include Rack::Test::Methods it "should load the home page" do get '/' last_response.should be_ok end end
  42. DIRECTORY STRUCTURE | -- application.rb | -- views -- index.erb

    -- layout.erb | -- public -- css -- style.css -- javascript | -- lib -- shorturl.rb | -- spec -- application_rspec.rb
  43. Gemfile source 'https://rubygems.org' gem 'sinatra' gem 'json' gem 'dm-core' gem

    'dm-migrations' gem 'dm-postgres-adapter' group :development do gem 'shotgun' end group :production do gem 'thin' end GEMFILE
  44. CONFIG.RU Many reasons to use, but especially if.. you are

    deploying to a different Rack handler Heroku Passenger
  45. monkeh$ heroku create urlshrinkapp Creating urlshrinkapp... done, stack is cedar

    http://urlshrinkapp.herokuapp.com/ | [email protected]:urlshrinkapp.git Git remote heroku added HEROKU
  46. monkeh$ git push heroku master Total 19 (delta 2), reused

    0 (delta 0) -----> Ruby/Rack app detected -----> Installing dependencies using Bundler version 1.3.2 Running: bundle install --without development:test --path vendor/bundle --binstubs vendor/bundle/bin --deployment Fetching gem metadata from https://rubygems.org/......... Fetching gem metadata from https://rubygems.org/.. Installing data_objects (0.10.13) Installing dm-core (1.2.0) Installing dm-do-adapter (1.2.0) Installing dm-migrations (1.2.0) Installing do_postgres (0.10.13) Installing dm-postgres-adapter (1.2.0) Installing eventmachine (1.0.3) Installing json (1.8.0) Installing rack (1.5.2) Installing rack-protection (1.5.0) Installing tilt (1.4.1) Installing sinatra (1.4.2) Using bundler (1.3.2) Your bundle is complete! It was installed into ./vendor/bundle Cleaning up the bundler cache. -----> Discovering process types Procfile declares types -> web Default types for Ruby/Rack -> console, rake -----> Compiled slug size: 3.0MB -----> Launching... done, v4 http://urlshrinkapp.herokuapp.com deployed to Heroku To [email protected]:urlshrinkapp.git * [new branch] master -> master HEROKU
  47. monkeh$ heroku addons:add heroku-postgresql:dev Adding heroku-postgresql:dev on urlshrinkapp... done, v5

    (free) Attached as HEROKU_POSTGRESQL_BROWN_URL Database has been created and is available heroku pg:promote HEROKU_POSTGRESQL_BROWN_URL Promoting HEROKU_POSTGRESQL_BROWN_URL to DATABASE_URL... done HEROKU
  48. monkeh$ heroku run console Running `console` attached to terminal... up,

    run.2858 irb(main):001:0> require './application.rb' => true irb(main):002:0> DataMapper.auto_upgrade! => #<DataMapper::DescendantSet:0x000000035aacf0 @descendants=#<DataMapper::SubjectSet:0x000000035aaca0 @entries=#<DataMapper::OrderedSet:0x000000035aac78 @cache=#<DataMapper::SubjectSet::NameCache:0x000000035aac50 @cache={"ShortURL"=>0}>, @entries=[ShortURL]>>> irb(main):003:0> exit HEROKU
  49. DIRECTORY STRUCTURE | -- application.rb | -- views -- index.erb

    -- layout.erb | -- public -- css -- style.css -- javascript | -- Gemfile | -- lib | -- spec | -- config.ru | -- Procfile