Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Historique et possibilités

Slide 5

Slide 5 text

2008 Blake Mizerany & Adam Wiggins Historique

Slide 6

Slide 6 text

Problème Services Web légers Historique

Slide 7

Slide 7 text

Solution Un micro-framework Historique

Slide 8

Slide 8 text

Possibilités infinies Historique

Slide 9

Slide 9 text

Prototypes d’applications Web Historique

Slide 10

Slide 10 text

Applications Web complètes Historique

Slide 11

Slide 11 text

APIs « RESTful » Historique

Slide 12

Slide 12 text

Avantages Les bons côtés

Slide 13

Slide 13 text

Installation rapide Avantages

Slide 14

Slide 14 text

$ gem install sinatra => Successfully installed rack-1.2.1 Successfully installed tilt-1.2.2 Successfully installed sinatra-1.2.0

Slide 15

Slide 15 text

Développement minimaliste Avantages

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

$ rails new blogue => create README create Rakefile create config.ru create .gitignore create Gemfile create app … $ du -hd0 => 428K .

Slide 18

Slide 18 text

Déploiement facile Avantages

Slide 19

Slide 19 text

Désavantages Les moins bons côtés

Slide 20

Slide 20 text

• Fonctionnalités réduites • Expansion moins guidée • Documentation moins large Désavantages

Slide 21

Slide 21 text

Rack Interface HTTP

Slide 22

Slide 22 text

Framework pour frameworks Rack

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

# contenu de config.ru require "blogue" run Sinatra::Application # contenu de blogue.rb require "sinatra" # Code l’application Sinatra au premier niveau

Slide 26

Slide 26 text

Routes Les directions

Slide 27

Slide 27 text

REST Basées sur les méthodes HTTP Routes

Slide 28

Slide 28 text

Routes • GET • POST • PUT • DELETE

Slide 29

Slide 29 text

methode_http(route) { reponse }

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

Statiques Routes fixes Routes

Slide 32

Slide 32 text

get "/" do "Page principale du blogue" end post "/admin/article" do "Création d’un nouvel article" end

Slide 33

Slide 33 text

Paramètres Routes variables Routes

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Splat Routes avec « wildcards » Routes

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Regex Routes avec expressions Routes

Slide 38

Slide 38 text

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{^(?\d{4})/(?\d{2})$} do "Les articles du mois #{params[:mois]} de #{params[:annee]}" end

Slide 39

Slide 39 text

Conditions Routes conditionnelles Routes

Slide 40

Slide 40 text

get "/", :agent => /msie [\w.]+/i do "Page d’accueil pour Internet Explorer" end get "/" do "Page d’accueil pour les autres user-agents" end

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Routes • methode_http(route) { reponse } • Paramètres (réguliers, regex, splat) • Conditions

Slide 43

Slide 43 text

Exécution Passer, arrêter ou filtrer

Slide 44

Slide 44 text

Passer d’une route à la suivante Exécution

Slide 45

Slide 45 text

get "/admin/dashboard" do pass unless authenticate! "Le tableau de bord secret" end get "/admin/*" do "Vous semblez ne pas être identifié." end

Slide 46

Slide 46 text

Arrêter l’exécution du code Exécution

Slide 47

Slide 47 text

get "/admin/dashboard" do halt(401, "Vous voulez hacker ce blogue?") unless authenticate! "Le tableau de bord secret" end

Slide 48

Slide 48 text

Filtrer avant et après Exécution

Slide 49

Slide 49 text

before Avant la route Exécution

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

before :agent => /msie 6\.0/i do @message = "Vous utilisez un navigateur dépassé…" end

Slide 53

Slide 53 text

after Après la route Exécution

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

Templates Les vues

Slide 56

Slide 56 text

Réponses compatibles avec Rack Templates

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

get "/" do [200, "Page principale du blogue"] end get "/api/articles.json" do [200, { "Content-type": "application/json" }, "[]"] end

Slide 59

Slide 59 text

Tilt Templates à la demande Templates

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

Options pour chaque engin Templates

Slide 62

Slide 62 text

get "/" do haml :index, :format => :html4 end get "/css/screen.css" do scss :screen, :style => :compressed end

Slide 63

Slide 63 text

set :haml, :format => :html5, :ugly => true set :scss, :style => :compressed

Slide 64

Slide 64 text

Internes Stockés dans le code Ruby Templates

Slide 65

Slide 65 text

enable :inline_templates get "/" do haml :index end __END__ @@ layout !!! %html %body =yield @@ index %h1 Bienvenue sur mon blogue.

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

Externes Stockés en tant que fichiers Templates

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

set :views, Proc.new { File.join(root, "templates") }

Slide 71

Slide 71 text

Layout Template commun Templates

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

Données Les utiliser dans les templates Templates

Slide 74

Slide 74 text

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)

Slide 75

Slide 75 text

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)

Slide 76

Slide 76 text

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)

Slide 77

Slide 77 text

Helpers Utilitaires disponibles partout Templates

Slide 78

Slide 78 text

helpers do def heading(level, text) "#{text}" end end %h1 Derniers articles %ul.articles - @articles.each do |article| %li =heading(2, article.title)

Slide 79

Slide 79 text

helpers do def link_to(path, text) path = "#{request.host}#{path}" if request.xhr? "#{text}" end end def other_link_to(path, text) # n’a pas accès à `request` "#{text}" end %h1 Bienvenue %p= link_to "/", "Accueil" %p= other_link_to "/", "Accueil encore"

Slide 80

Slide 80 text

Templates • Tilt • Options • Internes + Externes • Données • Helpers

Slide 81

Slide 81 text

Configuration et environnements

Slide 82

Slide 82 text

Globale à tous les environnements Configuration

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

Spécifique à un environnement Configuration

Slide 85

Slide 85 text

$ 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

Slide 86

Slide 86 text

configure :development do set :scss, :style => :expanded set :haml, :ugly => false end configure :production do set :scss, :style => :compressed set :haml, :ugly => true end

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

Erreurs gérées comme des routes

Slide 89

Slide 89 text

Routes introuvables Erreurs

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

not_found do haml :erreur end

Slide 92

Slide 92 text

HTTP Codes d’erreurs standards Erreurs

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

Exceptions personnalisées Erreurs

Slide 95

Slide 95 text

error UnauthenticatedUser do haml :"erreurs/non_authentifie" end before "/admin/*" do raise UnauthenticatedUser unless authenticate! end

Slide 96

Slide 96 text

Fichiers et téléchargements

Slide 97

Slide 97 text

Fichiers publics Fichiers

Slide 98

Slide 98 text

$ 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 => …

Slide 99

Slide 99 text

set :public, Proc.new { File.join(root, "fichiers/statiques") }

Slide 100

Slide 100 text

Téléchargements de fichiers Fichiers

Slide 101

Slide 101 text

get "/live/report.txt" do # Construction dynamique du fichier /tmp/report.txt # … send_file "/tmp/report.txt", :type => :attachment end

Slide 102

Slide 102 text

Sessions et cookies

Slide 103

Slide 103 text

Sessions Données temporaires encryptées Sessions

Slide 104

Slide 104 text

enable :sessions before "/admin/*" do unless session[:admin] halt "Vous devez être connecté." 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

Slide 105

Slide 105 text

Cookies Données persistantes Sessions

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

Tests Vérifier le fonctionnement

Slide 108

Slide 108 text

Rack::Test Tests pour applications Rack Sessions

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

$ ruby test.rb => Loaded suite test Started .. Finished in 0.009936 seconds. 2 tests, 2 assertions, 0 failures, 0 errors

Slide 113

Slide 113 text

Déploiement d’une application

Slide 114

Slide 114 text

Bundler Gestionnaire de gems Déploiement

Slide 115

Slide 115 text

Sans Bundler Déploiement

Slide 116

Slide 116 text

# Contenu de config.ru require "rubygems" require "sinatra" require "haml" require "dm-core" require "blogue" run Blogue.new

Slide 117

Slide 117 text

$ 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

Slide 118

Slide 118 text

Avec Bundler Déploiement

Slide 119

Slide 119 text

$ gem install bundler => Successfully installed bundler-1.0.10

Slide 120

Slide 120 text

# Contenu du fichier Gemfile source "http://rubygems.org" gem "sinatra" gem "haml" gem "dm-core", "~> 1.0"

Slide 121

Slide 121 text

$ 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

Slide 122

Slide 122 text

# Contenu de config.ru require "bundler" Bundler.require require "blogue" run Blogue.new

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

Heroku Plateforme de déploiement Déploiement

Slide 125

Slide 125 text

$ gem install heroku => Successfully installed configuration-1.2.0 Successfully installed launchy-0.3.7 Successfully installed heroku-1.17.16 3 gems installed

Slide 126

Slide 126 text

$ git init => Initialized empty Git repository in /Code/blogue/.git/ $ echo ".bundle" > .gitignore

Slide 127

Slide 127 text

$ heroku create blogue => Creating blogue.... done http://blogue.heroku.com/ | [email protected]:blogue.git Git remote heroku added

Slide 128

Slide 128 text

$ 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 [email protected]:blogue.git * [new branch] master -> master

Slide 129

Slide 129 text

Résumé Sinatra en bref

Slide 130

Slide 130 text

Rack Compatible avec tout (!) Résumé

Slide 131

Slide 131 text

Développement minimaliste Résumé

Slide 132

Slide 132 text

Routes orientées « REST » Résumé

Slide 133

Slide 133 text

Templates flexibles Résumé

Slide 134

Slide 134 text

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

Slide 135

Slide 135 text

Déploiement facile avec Bundler Résumé

Slide 136

Slide 136 text

Résumé Sinatra en bref

Slide 137

Slide 137 text

• sinatrarb.com • sinatra-book.gittr.com/ • peepcode.com/products/sinatra • irc.freenode.net/sinatra (IRC) • github.com/remiprev/nid (exemple) Ressources

Slide 138

Slide 138 text

Questions? Commentaires? @remi