Introduction à Sinatra

Post on 15-Jan-2015

2.875 views 0 download

description

 

Transcript of Introduction à Sinatra

Introduction à

SinatraRémi Prévost — ConFoo 2011

Rémi Prévost

@remi + http://remiprevost.com

Développeur Web

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

Sinatra

Historiqueet possibilités

2008Blake Mizerany & Adam Wiggins

Historique

ProblèmeServices Web légers

Historique

SolutionUn micro-framework

Historique

Possibilitésinfinies

Historique

Prototypesd’applications Web

Historique

ApplicationsWeb complètes

Historique

APIs« RESTful »

Historique

AvantagesLes bons côtés

Installationrapide

Avantages

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

Développementminimaliste

Avantages

# contenu de hello.rbrequire "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.

$ rails new blogue=> create README create Rakefile create config.ru create .gitignore create Gemfile create app …

$ du -hd0=> 428K .

Déploiementfacile

Avantages

DésavantagesLes moins bons côtés

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

Désavantages

RackInterface HTTP

Frameworkpour frameworks

Rack

# contenu de config.ruclass RackApp def call(env) [200, { "Content-type" => "text/html" }, "Hello world."] endend

run RackApp.new

$ rackup --env development=> INFO WEBrick::HTTPServer#start: pid=37743 port=9292

$ curl http://localhost:9292/=> Hello world.

# contenu de config.rurequire "blogue"

run Blogue.new

# contenu de blogue.rbrequire "sinatra"

class Blogue < Sinatra::Base # Code l’application Sinatraend

# contenu de config.rurequire "blogue"

run Sinatra::Application

# contenu de blogue.rbrequire "sinatra"

# Code l’application Sinatra au premier niveau

RoutesLes directions

RESTBasées sur les méthodes HTTP

Routes

Routes

• GET• POST• PUT• DELETE

methode_http(route) { reponse }

methode_http(route) { reponse }

get("/") { "Hello world." }

StatiquesRoutes fixes

Routes

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

post "/admin/article" do "Création d’un nouvel article"end

ParamètresRoutes variables

Routes

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

Splat Routes avec « wildcards »

Routes

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

RegexRoutes avec expressions

Routes

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

ConditionsRoutes conditionnelles

Routes

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

get "/" do "Page d’accueil pour les autres user-agents"end

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

Routes

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

ExécutionPasser, arrêter ou filtrer

Passerd’une route à la suivante

Exécution

get "/admin/dashboard" do pass unless authenticate! "Le tableau de bord secret"end

get "/admin/*" do "Vous semblez ne pas être identifié."end

Arrêterl’exécution du code

Exécution

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

Filtreravant et après

Exécution

beforeAvant la route

Exécution

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

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

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

afterAprès la route

Exécution

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

TemplatesLes vues

Réponsescompatibles avec Rack

Templates

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

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

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

TiltTemplates à la demande

Templates

get "/" do haml :indexend

get "/css/screen.css" do sass :screenend

get "/api/articles.xml" do nokogiri :"api/articles"end

get "/api/articles.json" do coffee :"api/articles"end

Optionspour chaque engin

Templates

get "/" do haml :index, :format => :html4end

get "/css/screen.css" do scss :screen, :style => :compressedend

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

InternesStockés dans le code Ruby

Templates

enable :inline_templates

get "/" do haml :indexend

__END__

@@ layout!!!%html %body =yield

@@ index%h1 Bienvenue sur mon blogue.

template :layout do "!!!\n%html\n%body\n=yield\n"end

template :index do "%h1 Bienvenue sur mon blogue."end

get "/" do haml :indexend

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

ExternesStockés en tant que fichiers

Templates

get "/" do haml :index # /views/index.hamlend

get "/css/screen.css" do sass :screen # /views/screen.sassend

get "/api/articles.xml" do builder :"api/articles"# /views/api/articles.builderend

get "/api/articles.json" do coffee :"api/articles" # /views/api/articles.coffeeend

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

LayoutTemplate commun

Templates

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

get "/article/:id" do @article = Article.find(params[:id]) markdown :article, :layout_engine => :haml # template: views/article.markdown # layout: views/layout.hamlend

get "/ajax/article/:id.html" do @article = Article.find(params[:id]) haml :article, :layout => false # template: views/article.haml # layout: n/aend

DonnéesLes utiliser dans les templates

Templates

get "/" do @articles = Article.all haml :indexend

-# contenu de index.haml.hfeed - @articles.each do |article| .entry %h1= article.titre .entry-content = markdown(article.contenu)

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)

get "/" do @articles = Article.all haml :indexend

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

HelpersUtilitaires disponibles partout

Templates

helpers do def heading(level, text) "<h#{level}>#{text}</h#{level}>" endend

%h1 Derniers articles%ul.articles - @articles.each do |article| %li =heading(2, article.title)

helpers do def link_to(path, text) path = "#{request.host}#{path}" if request.xhr? "<a href=\"#{path}\">#{text}</a>" endend

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"

Templates

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

Configurationet environnements

Globaleà tous les environnements

Configuration

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

get "/" do @articles = Article.all haml :indexend

Spécifiqueà un environnement

Configuration

$ 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

configure :development do set :scss, :style => :expanded set :haml, :ugly => falseend

configure :production do set :scss, :style => :compressed set :haml, :ugly => trueend

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

Erreursgérées comme des routes

Routesintrouvables

Erreurs

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

not_found do haml :erreurend

HTTPCodes d’erreurs standards

Erreurs

error 403 do haml :"erreurs/interdit"end

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

Exceptionspersonnalisées

Erreurs

error UnauthenticatedUser do haml :"erreurs/non_authentifie"end

before "/admin/*" do raise UnauthenticatedUser unless authenticate!end

Fichierset téléchargements

Fichierspublics

Fichiers

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

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

Téléchargementsde fichiers

Fichiers

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

Sessionset cookies

SessionsDonnées temporaires encryptées

Sessions

enable :sessions

before "/admin/*" do unless session[:admin] halt "Vous devez être <a href=\"/login\">connecté</a>." endend

get "/login" do haml :loginend post "/login" do if params[:username] == "foo" and params[:password] == "bar" session[:admin] = true redirect "/admin" endend

CookiesDonnées persistantes

Sessions

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 endend

get "/" do haml :index # peut utiliser @nouveau_visiteurend

TestsVérifier le fonctionnement

Rack::TestTests pour applications Rack

Sessions

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

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

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

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

Déploiementd’une application

BundlerGestionnaire de gems

Déploiement

SansBundler

Déploiement

# Contenu de config.rurequire "rubygems"require "sinatra"require "haml"require "dm-core"

require "blogue"

run Blogue.new

$ 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

AvecBundler

Déploiement

$ gem install bundler=> Successfully installed bundler-1.0.10

# Contenu du fichier Gemfilesource "http://rubygems.org"

gem "sinatra"gem "haml"gem "dm-core", "~> 1.0"

$ 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

# Contenu de config.rurequire "bundler"Bundler.require

require "blogue"

run Blogue.new

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

HerokuPlateforme de déploiement

Déploiement

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

$ git init=> Initialized empty Git repository in /Code/blogue/.git/

$ echo ".bundle" > .gitignore

$ heroku create blogue=> Creating blogue.... done http://blogue.heroku.com/ | git@heroku.com:blogue.git Git remote heroku added

$ 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

RésuméSinatra en bref

RackCompatible avec tout (!)

Résumé

Développementminimaliste

Résumé

Routesorientées « REST »

Résumé

Templatesflexibles

Résumé

Sessions,cookies, tests, filtres, etc.

Résumé

Déploiementfacile avec Bundler

Résumé

RésuméSinatra en bref

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

Ressources

Questions?Commentaires?

@remi