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. “SWING WHEN YOU’RE WINNING” AN INTRODUCTION TO SINATRA & RUBY

  3. I build stuff and I write about building stuff too

  4. None
  5. 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
  6. LIVE CODE DEMOS? WHAT COULD POSSIBLY GO WRONG?

  7. NOT SURE IF I SHOULD BE HERE OR WATCHING SOMEONE

    ELSE
  8. WHAT ELSE IS ON? Using personas in service design -

    continuously Mura 6 for developers Tuüli Aalto-Nyyssonen Steve Withington
  9. WAIT! You’re a ColdFusion developer, aren’t you? And this is

    a ColdFusion conference, isn’t it?
  10. YES I AM! (and proud of it)

  11. None
  12. None
  13. None
  14. None
  15. BACK TO SINATRA

  16. 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)
  17. 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)
  18. 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...
  19. MORE BOUNCE TO THE OUNCE

  20. DEVELOPMENT IS QUICK!

  21. “simplicity is the ultimate sophistication” Leonardo da Vinci

  22. 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)
  23. HOW SIMPLE?

  24. application.rb require 'rubygems' require 'sinatra' get '/hi' do "Hello World!"

    end THIS SIMPLE!
  25. 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
  26. IMPRESSIVE, RIGHT?

  27. None
  28. require 'rubygems' require 'sinatra' get '/hi' do "Hello World!" end

    WHAT’S GOING ON?
  29. require 'rubygems' require 'sinatra' get '/hi' do "Hello World!" end

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

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

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

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

    NO RETURN? <--- the return is implied
  35. 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.
  36. REST Uniform Interface Client-Server Cacheable HATEOAS Layered System Stateless Hypermedia

    as the Engine of Application State
  37. SINATRA CAN DO THAT! Caching Multiple Routes Content Types All

    HTTP verbs supported Good times!
  38. 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
  39. ROUTES get '/' do "Home page" end get '/hello' do

    "Hello!" end get '/hello/:name' do "Hello! #{params[:name]}" end
  40. 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
  41. 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
  42. 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
  43. GETTING STARTED Install Ruby Bathe in Ruby deliciousness Create your

    application file Install the Sinatra gem
  44. http://rvm.io

  45. monkeh$ rvm install 1.9.3 monkeh$ rvm use --default 1.9.3 monkeh$

    rvm use jruby http://cheat.errtheblog.com/s/rvm
  46. GEMS?

  47. GEMS? RubyGem distributes libraries apt-get Similar in functionality to: A

    library is self-contained in a gem portage yum
  48. GEMS! monkeh$ gem install [gem] monkeh$ gem uninstall [gem] monkeh$

    gem list monkeh$ gem fetch [gem]
  49. BACK TO SINATRA

  50. INSTALLING monkeh$ gem install sinatra monkeh$ gem install shotgun

  51. DEMO TIME

  52. DIRECTORY STRUCTURE | -- application.rb

  53. 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
  54. VIEWS Multiple template languages available, including: builder erb haml sass

    liquid markdown
  55. 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>
  56. DIRECTORY STRUCTURE | -- application.rb | -- views -- index.erb

  57. 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
  58. 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>
  59. 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>
  60. DIRECTORY STRUCTURE | -- application.rb | -- views -- index.erb

    -- layout.erb
  61. STATIC CONTENT All static content is stored within a new

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

    -- layout.erb | -- public -- css -- style.css -- javascript
  63. STATIC CONTENT?

  64. 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
  65. 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
  66. CONFIGURATION Will run once at startup Can be used to

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

    # Production only end configure :development, :test do # Development and Test end
  68. 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
  69. CONFIGURATION configure :development do # very useful for debugging parameters

    sent via the console before do puts '[Params]' p params end end
  70. None
  71. DATAMAPPER Same API can talk to multiple datastores Uses adapters

    to achieve this sqlite mysql postgresql
  72. DATAMAPPER Define mappings in your model Comes bundled with tools

    to assist with migration constraints transactions timestamps validations ...and more!
  73. INCLUDE THE LIBRARY application.rb require 'rubygems' require 'sinatra' require 'data_mapper'

    <--- DataMapper gem
  74. 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
  75. 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
  76. 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
  77. 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>
  78. GETTING DATA application.rb get '/:short_url' do @URLData = ShortURL.get(params[:short_url]) end

  79. DIRECTORY STRUCTURE | -- application.rb | -- views -- index.erb

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

    JSON XML HTML monkeh$ gem install json May need some more gems to process
  81. 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
  82. TESTING

  83. WHY?

  84. BECAUSE!

  85. TESTING GEMS monkeh$ gem install rspec monkeh$ gem install rack

    monkeh$ gem install rack/test
  86. 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
  87. AUTO TESTING monkeh$ gem install ZenTest

  88. AUTO TESTING NOTIFICATIONS monkeh$ gem install autotest-growl monkeh$ gem install

    autotest-fsevent
  89. DIRECTORY STRUCTURE | -- application.rb | -- views -- index.erb

    -- layout.erb | -- public -- css -- style.css -- javascript | -- lib -- shorturl.rb | -- spec -- application_rspec.rb
  90. None
  91. http://gembundler.com/v1.3/gemfile.html monkeh$ gem install bundler

  92. 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
  93. monkeh$ bundle install monkeh$ git add Gemfile Gemfile.lock BUNDLE TIME

  94. CONFIG.RU Many reasons to use, but especially if.. you are

    deploying to a different Rack handler Heroku Passenger
  95. config.ru require './application.rb' run Sinatra::Application CONFIG.RU

  96. Procfile web: bundle exec thin -R config.ru start -p $PORT

    -e $RACK_ENV PROCFILE
  97. HEROKU DEPLOYMENT monkeh$ git init monkeh$ git add . monkeh$

    git commit -m "Initial commit"
  98. monkeh$ heroku create urlshrinkapp Creating urlshrinkapp... done, stack is cedar

    http://urlshrinkapp.herokuapp.com/ | [email protected]:urlshrinkapp.git Git remote heroku added HEROKU
  99. 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
  100. 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
  101. 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
  102. monkeh$ heroku open Opening urlshrinkapp... done HEROKU

  103. DIRECTORY STRUCTURE | -- application.rb | -- views -- index.erb

    -- layout.erb | -- public -- css -- style.css -- javascript | -- Gemfile | -- lib | -- spec | -- config.ru | -- Procfile
  104. BACK TO SINATRA

  105. None
  106. USEFUL LINKS http://sinatrarb.com https://github.com/sinatra/sinatra http://rvm.io

  107. None
  108. == Sinatra has ended his set (crowd applauds)