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

Introduction à Sinatra

Introduction à Sinatra

Présentation donnée le 10 mars 2011 à ConFoo 2011 (Montréal).

8c4cee1129bc11fbe9a0b9379dce2cb1?s=128

Rémi Prévost

March 10, 2011
Tweet

Transcript

  1. Introduction à Sinatra Rémi Prévost — ConFoo 2011

  2. Rémi Prévost @remi + http://remiprevost.com Développeur Web

  3. • Présentation • Installation • Utilisation • Déploiement Sinatra

  4. Historique et possibilités

  5. 2008 Blake Mizerany & Adam Wiggins Historique

  6. Problème Services Web légers Historique

  7. Solution Un micro-framework Historique

  8. Possibilités infinies Historique

  9. Prototypes d’applications Web Historique

  10. Applications Web complètes Historique

  11. APIs « RESTful » Historique

  12. Avantages Les bons côtés

  13. Installation rapide Avantages

  14. $ gem install sinatra => Successfully installed rack-1.2.1 Successfully installed

    tilt-1.2.2 Successfully installed sinatra-1.2.0
  15. Développement minimaliste Avantages

  16. # contenu de hello.rb require "sinatra" get "/" do "Hello

    world." end $ ruby -rubygems hello.rb => == Sinatra/1.2 has taken the stage on 4567… $ curl http://localhost:4567/ => Hello world.
  17. $ rails new blogue => create README create Rakefile create

    config.ru create .gitignore create Gemfile create app … $ du -hd0 => 428K .
  18. Déploiement facile Avantages

  19. Désavantages Les moins bons côtés

  20. • Fonctionnalités réduites • Expansion moins guidée • Documentation moins

    large Désavantages
  21. Rack Interface HTTP

  22. Framework pour frameworks Rack

  23. # contenu de config.ru class RackApp def call(env) [200, {

    "Content-type" => "text/html" }, "Hello world."] end end run RackApp.new $ rackup --env development => INFO WEBrick::HTTPServer#start: pid=37743 port=9292 $ curl http://localhost:9292/ => Hello world.
  24. # contenu de config.ru require "blogue" run Blogue.new # contenu

    de blogue.rb require "sinatra" class Blogue < Sinatra::Base # Code l’application Sinatra end
  25. # contenu de config.ru require "blogue" run Sinatra::Application # contenu

    de blogue.rb require "sinatra" # Code l’application Sinatra au premier niveau
  26. Routes Les directions

  27. REST Basées sur les méthodes HTTP Routes

  28. Routes • GET • POST • PUT • DELETE

  29. methode_http(route) { reponse }

  30. methode_http(route) { reponse } get("/") { "Hello world." }

  31. Statiques Routes fixes Routes

  32. get "/" do "Page principale du blogue" end post "/admin/article"

    do "Création d’un nouvel article" end
  33. Paramètres Routes variables Routes

  34. get "/auteur/:username" do "Les billets de #{params[:username]}" end delete "/articles/:id"

    do "Suppression de l’article #{params[:id]}" end put "/articles/:id" do |id| "Modification de l’article #{id}" end
  35. Splat Routes avec « wildcards » Routes

  36. get "/fichiers/*.*" do # GET /fichiers/images/2011/03/foo.jpg # params[:splat] => ["/images/2011/03/foo",

    ".jpg"] end get "/*" do # GET /url/inconnu # params[:splat] => ["url/inconnu"] end
  37. Regex Routes avec expressions Routes

  38. get /^\/(\d{4})$/ do |annee| "Les articles de l’année #{annee}" #

    params["captures"] => [annee] end get %r{^(\d{4})/(\d{2})$} do |annee, mois| "Les articles du mois #{mois} de #{annee}" # params["captures"] => [annee, mois] end # seulement ruby 1.9 get %r{^(?<annee>\d{4})/(?<mois>\d{2})$} do "Les articles du mois #{params[:mois]} de #{params[:annee]}" end
  39. Conditions Routes conditionnelles Routes

  40. get "/", :agent => /msie [\w.]+/i do "Page d’accueil pour

    Internet Explorer" end get "/" do "Page d’accueil pour les autres user-agents" end
  41. set(:secret) do |value| condition { params.include?(:secret) == value } end

    get "/", :secret => true do "Page d’accueil secrète!" end get "/" do "Page d’accueil régulière." end
  42. Routes • methode_http(route) { reponse } • Paramètres (réguliers, regex,

    splat) • Conditions
  43. Exécution Passer, arrêter ou filtrer

  44. Passer d’une route à la suivante Exécution

  45. get "/admin/dashboard" do pass unless authenticate! "Le tableau de bord

    secret" end get "/admin/*" do "Vous semblez ne pas être identifié." end
  46. Arrêter l’exécution du code Exécution

  47. get "/admin/dashboard" do halt(401, "Vous voulez hacker ce blogue?") unless

    authenticate! "Le tableau de bord secret" end
  48. Filtrer avant et après Exécution

  49. before Avant la route Exécution

  50. before "/admin/*" do halt(401, "Vous voulez hacker ce blogue?") unless

    authenticate! end get "/admin/dashboard" do "Tableau de bord de quelqu’un d’authentifié" end post "/admin/billets" do "Création d’un billet par quelqu’un d’authentifié" end
  51. before "/compte/*" do @user = User.find(session[:user_id]) end get "/compte/photo" do

    "Formulaire de modification de la photo de #{@user}" end get "/compte/motdepasse" do "Formulaire de modification du mot de passe de #{@user}" end
  52. before :agent => /msie 6\.0/i do @message = "Vous utilisez

    un navigateur dépassé…" end
  53. after Après la route Exécution

  54. after "*" do headers "X-Secret-Data" => "LOL" end

  55. Templates Les vues

  56. Réponses compatibles avec Rack Templates

  57. get "/" do "Page principale du blogue" end

  58. get "/" do [200, "Page principale du blogue"] end get

    "/api/articles.json" do [200, { "Content-type": "application/json" }, "[]"] end
  59. Tilt Templates à la demande Templates

  60. get "/" do haml :index end get "/css/screen.css" do sass

    :screen end get "/api/articles.xml" do nokogiri :"api/articles" end get "/api/articles.json" do coffee :"api/articles" end
  61. Options pour chaque engin Templates

  62. get "/" do haml :index, :format => :html4 end get

    "/css/screen.css" do scss :screen, :style => :compressed end
  63. set :haml, :format => :html5, :ugly => true set :scss,

    :style => :compressed
  64. Internes Stockés dans le code Ruby Templates

  65. enable :inline_templates get "/" do haml :index end __END__ @@

    layout !!! %html %body =yield @@ index %h1 Bienvenue sur mon blogue.
  66. template :layout do "!!!\n%html\n%body\n=yield\n" end template :index do "%h1 Bienvenue

    sur mon blogue." end get "/" do haml :index end
  67. get "/" do haml "!!!\n%body Bienvenue sur mon blogue." end

  68. Externes Stockés en tant que fichiers Templates

  69. get "/" do haml :index # /views/index.haml end get "/css/screen.css"

    do sass :screen # /views/screen.sass end get "/api/articles.xml" do builder :"api/articles"# /views/api/articles.builder end get "/api/articles.json" do coffee :"api/articles" # /views/api/articles.coffee end
  70. set :views, Proc.new { File.join(root, "templates") }

  71. Layout Template commun Templates

  72. get "/" do haml :index # template: views/index.haml # layout:

    views/layout.haml end get "/article/:id" do @article = Article.find(params[:id]) markdown :article, :layout_engine => :haml # template: views/article.markdown # layout: views/layout.haml end get "/ajax/article/:id.html" do @article = Article.find(params[:id]) haml :article, :layout => false # template: views/article.haml # layout: n/a end
  73. Données Les utiliser dans les templates Templates

  74. get "/" do @articles = Article.all haml :index end -#

    contenu de index.haml .hfeed - @articles.each do |article| .entry %h1= article.titre .entry-content = markdown(article.contenu)
  75. get "/" do articles = Article.all haml :index, :locals =>

    { :articles => articles } end -# contenu de index.haml .hfeed - articles.each do |article| .entry %h1= article.titre .entry-content = markdown(article.contenu)
  76. get "/" do @articles = Article.all haml :index end -#

    contenu de index.haml .hfeed - @articles.each do |article| .entry = haml :article, :locals => { :article => article } -# contenu de article.haml .entry %h1= article.titre .entry-content = markdown(article.contenu)
  77. Helpers Utilitaires disponibles partout Templates

  78. helpers do def heading(level, text) "<h#{level}>#{text}</h#{level}>" end end %h1 Derniers

    articles %ul.articles - @articles.each do |article| %li =heading(2, article.title)
  79. helpers do def link_to(path, text) path = "#{request.host}#{path}" if request.xhr?

    "<a href=\"#{path}\">#{text}</a>" end end def other_link_to(path, text) # n’a pas accès à `request` "<a href=\"#{path}\">#{text}</a>" end %h1 Bienvenue %p= link_to "/", "Accueil" %p= other_link_to "/", "Accueil encore"
  80. Templates • Tilt • Options • Internes + Externes •

    Données • Helpers
  81. Configuration et environnements

  82. Globale à tous les environnements Configuration

  83. configure do DataMapper.setup :default, ENV["DATABASE_URL"] DataMapper::Pagination.defaults[:per_page] = 20 DataMapper::Logger.new $stdout,

    :debug end get "/" do @articles = Article.all haml :index end
  84. Spécifique à un environnement Configuration

  85. $ shotgun --env development == Shotgun/WEBrick on http://127.0.0.1:9393/ $ thin

    start --env production >> Thin web server (v1.2.8 codename Black Keys) >> Listening on 0.0.0.0:3000, CTRL+C to stop $ rackup --env development => INFO WEBrick::HTTPServer#start: pid=37743 port=9292 $ rackup --env production => INFO WEBrick::HTTPServer#start: pid=37743 port=9292
  86. configure :development do set :scss, :style => :expanded set :haml,

    :ugly => false end configure :production do set :scss, :style => :compressed set :haml, :ugly => true end
  87. configure :development, :test do set :s3, { :bucket => "blogue-dev",

    :key => "efg456" } end configure :production do set :s3, { :bucket => "blogue", :key => "abc123" } end get "/" do "La valeur de s3/bucket est de #{settings.s3[:bucket]}" end
  88. Erreurs gérées comme des routes

  89. Routes introuvables Erreurs

  90. not_found do "Cette page n’a pu être trouvée" end

  91. not_found do haml :erreur end

  92. HTTP Codes d’erreurs standards Erreurs

  93. error 403 do haml :"erreurs/interdit" end error 405..500 do haml

    :"erreurs/autre" end
  94. Exceptions personnalisées Erreurs

  95. error UnauthenticatedUser do haml :"erreurs/non_authentifie" end before "/admin/*" do raise

    UnauthenticatedUser unless authenticate! end
  96. Fichiers et téléchargements

  97. Fichiers publics Fichiers

  98. $ tree . => "## blogue.rb "## config.ru "## public

    "## css % &## screen.css &## js &## global.js $ curl http://localhost:9292/css/screen.css => … $ curl http://localhost:9292/js/global.js => …
  99. set :public, Proc.new { File.join(root, "fichiers/statiques") }

  100. Téléchargements de fichiers Fichiers

  101. get "/live/report.txt" do # Construction dynamique du fichier /tmp/report.txt #

    … send_file "/tmp/report.txt", :type => :attachment end
  102. Sessions et cookies

  103. Sessions Données temporaires encryptées Sessions

  104. enable :sessions before "/admin/*" do unless session[:admin] halt "Vous devez

    être <a href=\"/login\">connecté</a>." end end get "/login" do haml :login end post "/login" do if params[:username] == "foo" and params[:password] == "bar" session[:admin] = true redirect "/admin" end end
  105. Cookies Données persistantes Sessions

  106. before do unless request.cookies.include?("deja_venu_ici") response.set_cookies("deja_venu_ici", { :value => true, :expires

    => Time.now + (60*60*24*365) }) @nouveau_visiteur = true end end get "/" do haml :index # peut utiliser @nouveau_visiteur end
  107. Tests Vérifier le fonctionnement

  108. Rack::Test Tests pour applications Rack Sessions

  109. $ gem install rack-test => Successfully installed rack-test-0.5.7

  110. require "blogue" require "test/unit" require "rack/test" class BlogueTest < Test::Unit::TestCase

    include Rack::Test::Methods def app; Blogue; end def test_page_accueil get "/" assert_equal "Page d’accueil du blogue", last_response.body end end
  111. require "blogue" require "test/unit" require "rack/test" class BlogueTest < Test::Unit::TestCase

    include Rack::Test::Methods def app; Blogue; end def test_redirection_mauvais_acces_au_tableau_de_bord get "/admin/dashboard" assert_equal "/admin/login", last_request.url assert last_response.ok? end def test_redirection_connexion_au_tableau_de_bord post "/admin/login", :username => "foo", :password => "bar" assert_equal "/admin/dashboard", last_request.url assert last_response.ok? end end
  112. $ ruby test.rb => Loaded suite test Started .. Finished

    in 0.009936 seconds. 2 tests, 2 assertions, 0 failures, 0 errors
  113. Déploiement d’une application

  114. Bundler Gestionnaire de gems Déploiement

  115. Sans Bundler Déploiement

  116. # Contenu de config.ru require "rubygems" require "sinatra" require "haml"

    require "dm-core" require "blogue" run Blogue.new
  117. $ gem install sinatra dm-core haml => Successfully installed rack

    1.2.1 Successfully installed tilt-1.2.2 Successfully installed sinatra-1.1.3 Successfully installed extlib-0.9.15 Successfully installed dm-core-1.0.2 Successfully installed haml-3.0.25
  118. Avec Bundler Déploiement

  119. $ gem install bundler => Successfully installed bundler-1.0.10

  120. # Contenu du fichier Gemfile source "http://rubygems.org" gem "sinatra" gem

    "haml" gem "dm-core", "~> 1.0"
  121. $ bundle install --path .bundle/gems => Fetching source index for

    http://rubygems.org/ Installing addressable (2.2.4) Installing extlib (0.9.15) Installing dm-core (1.0.2) Installing haml (3.0.25) Installing rack (1.2.1) Installing tilt (1.2.2) Installing sinatra (1.1.3) Using bundler (1.0.10) Your bundle is complete! It was installed into ./bundle/gems
  122. # Contenu de config.ru require "bundler" Bundler.require require "blogue" run

    Blogue.new
  123. $ bundle exec rackup => INFO WEBrick::HTTPServer#start: pid=22866 port=9292

  124. Heroku Plateforme de déploiement Déploiement

  125. $ gem install heroku => Successfully installed configuration-1.2.0 Successfully installed

    launchy-0.3.7 Successfully installed heroku-1.17.16 3 gems installed
  126. $ git init => Initialized empty Git repository in /Code/blogue/.git/

    $ echo ".bundle" > .gitignore
  127. $ heroku create blogue => Creating blogue.... done http://blogue.heroku.com/ |

    git@heroku.com:blogue.git Git remote heroku added
  128. $ git add . $ git commit -m "Initial commit"

    $ git push heroku master => Counting objects: 14, done. Delta compression using up to 2 threads. Compressing objects: 100% (10/10), done. Writing objects: 100% (14/14), 1.81 KiB, done. Total 14 (delta 0), reused 0 (delta 0) -----> Heroku receiving push -----> Sinatra app detected -----> Gemfile detected, running Bundler version 1.0.7 Unresolved dependencies detected; Installing... … Your bundle is complete! Compiled slug size is 924K -----> Launching... done http://blogue.heroku.com deployed to Heroku To git@heroku.com:blogue.git * [new branch] master -> master
  129. Résumé Sinatra en bref

  130. Rack Compatible avec tout (!) Résumé

  131. Développement minimaliste Résumé

  132. Routes orientées « REST » Résumé

  133. Templates flexibles Résumé

  134. Sessions, cookies, tests, filtres, etc. Résumé

  135. Déploiement facile avec Bundler Résumé

  136. Résumé Sinatra en bref

  137. • sinatrarb.com • sinatra-book.gittr.com/ • peepcode.com/products/sinatra • irc.freenode.net/sinatra (IRC) •

    github.com/remiprev/nid (exemple) Ressources
  138. Questions? Commentaires? @remi