Post on 15-Jan-2015
description
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