Sinatra Tutorial@Rubyconf.TW2011
Upcoming SlideShare
Loading in...5
×
 

Sinatra Tutorial@Rubyconf.TW2011

on

  • 2,789 views

介紹Sinatra這套Ruby DSL Framework的基礎, 以及如何透過Sinatra輔助Rails

介紹Sinatra這套Ruby DSL Framework的基礎, 以及如何透過Sinatra輔助Rails

Statistics

Views

Total Views
2,789
Views on SlideShare
2,780
Embed Views
9

Actions

Likes
12
Downloads
58
Comments
0

5 Embeds 9

https://twitter.com 3
http://twitter.com 2
https://www.linkedin.com 2
http://localhost 1
https://abs.twimg.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Sinatra Tutorial@Rubyconf.TW2011 Sinatra Tutorial@Rubyconf.TW2011 Presentation Transcript

  • 用Sinatra建置高效能 與高自訂性的系統 Mu-Fan Teng@Rubyconf.TW 2011 TOMLAN Software Studio 111年8月26日星期五
  • About me • 鄧慕凡(Mu-Fan Teng) •a.k.a: Ryudo •http://twitter.com/ryudoawaru •Ruby developer since 2007 •Founder of Tomlan Software Studio. 211年8月26日星期五
  • 311年8月26日星期五
  • Agenda • Sinatra Introduction • Tutorial • Rails Metal • Production tips 411年8月26日星期五
  • Why Sinatra 511年8月26日星期五
  • Load lesser, run faster! Rails 3.1預設: 不含project本身約20個middleware 611年8月26日星期五
  • Load lesser, run faster! Sinatra:預設不超過6個 def build(*args, &bk) builder = Rack::Builder.new builder.use Rack::MethodOverride if method_override? builder.use ShowExceptions if show_exceptions? builder.use Rack::CommonLogger if logging? builder.use Rack::Head setup_sessions builder middleware.each { |c,a,b| builder.use(c, *a, &b) } builder.run new!(*args, &bk) builder end 711年8月26日星期五
  • Sinatra • very micro web framework(少於1600行) • pure Ruby/Rack • DSL化設計 811年8月26日星期五
  • 完全自訂性 • 自己選擇的ORM • 自己選擇template engine • 可簡單可複雜的路由設定 911年8月26日星期五
  • 適用場合 1011年8月26日星期五
  • 1111年8月26日星期五
  • 1211年8月26日星期五
  • Sinatra適合 • Tiny WEB App • 聊天室 • Widget • WEB API 1311年8月26日星期五
  • Tutorial開始 1411年8月26日星期五
  • Hello Sinatra! require rubygems require sinatra get / do Hello Sinatra! end 1511年8月26日星期五
  • 流程 BEFORE ROUTES AFTER before get ‘/’ after before ‘/users’ post ‘/users’ after ‘/users’ put ‘/users/:id’ get ‘/users/:id’ 1611年8月26日星期五
  • Named Route Params require rubygems require sinatra get /hello/:name do # => get /hello/Rubyconf # params[:name] => Rubyconf "Hello #{params[:name]}!" end Sinatra中的Route就如同Rails的Action + Route, 可 透過路由表示式的設定將URI字串中符合批配樣 式的內容(冒號開頭)化為特定的params hash成員. 1711年8月26日星期五
  • Splat Param Route get /say/*/to/* do # /say/hello/to/world params[:splat] # => ["hello", "world"] end get /download/*.* do # /download/path/to/file.xml params[:splat] # => ["path/to/file", "xml"] end 在路由表示式中的*號會以陣列成員的形式集 中到特定的params[:splat]中. 1811年8月26日星期五
  • Route matching with Regular Expressions get %r{/posts/name-([w]+)} do # => get /posts/name-abc, params[:captures][0] = abc "Hello, #{params[:captures].first}!" end get %r{/posts/([w]+)} do |pid| # => put match content to block param(s) # => matches 「([w]+)」 to 「pid」 end 路由表示式也接受Regular Expression並可將match 內容化為特定的params[:captures]陣列成員, 也可直 接設定為Block區域變數 1911年8月26日星期五
  • Route with block paramaters get /thread/:tid do |tid| # => tid == params[:tid] end get /pictures/*.* do |filename, ext| # => filename == first * # => ext == second * # => GET /pictures/abc.gif then filename = "abc" and ext = "gif" "filename = #{filename}, ext = #{ext}" end get %r{/posts/([w]+)} do |pid| # => put match content to block param(s) # => matches 「([w]+)」 to 「pid」 end 以上所介紹的路由表示式都可以將匹配的內容 指派到區塊的區域變數中 2011年8月26日星期五
  • Conditional route get /foo, :agent => /MSIEs(d.d+)/ do "Youre using IE version #{params[:agent][0]}" # => IE特製版 end get /, :host_name => /^admin./ do "Admin Area, Access denied!" end 也可以用user_agent或host_name來做路由啟發 的條件; 注意的是agent可以是字串或正規表示 式, hostname只能是正規表示式. 2111年8月26日星期五
  • Conditions(2) get /, :provides => html do erb :index end get /, :provides => [rss, atom, xml] do builder :feed end provides的條件是看accept header而非path 2211年8月26日星期五
  • RESTful Routes get /posts do # => Get some posts end get /posts/:id do # => Get 1 post end create /posts do # => create a post end put /posts/:id # => Update a post end delete /posts/:id # => Destroy a post end 2311年8月26日星期五
  • Route Return Values 1. HTTP Status Code 2. Headers 3. Response Body(Responds to #each) 2411年8月26日星期五
  • Http Streaming class FlvStream #http://goo.gl/B8BdU .... def each ... end end class Application < Sinatra::Base # Catch everyting and serve as stream get %r((.*)) do |path| path = File.expand_path(STORAGE_PATH + path) status(401) && return unless path =~ Regexp.new(STORAGE_PATH) flv = FlvStream.new(path, params[:start].to_i) throw :response, [200, {Content-Type => application/x-flv, "Content-Length" => flv.length.to_s}, flv] end end 由於只要有each這個method就可以當做Response Body, 因此在App Server支援的前提下就可以做出Http Streaming 2511年8月26日星期五
  • Template Engines • 支援數不清的樣板引擎(erb/haml/sass/ coffeescript/erubis/builder/markdown...) • 支援inline template • 透過Tilt可自訂樣板引擎 2611年8月26日星期五
  • Template Eengines get / do haml :index, :format => :html4 # overridden #render /views/index.haml end get /posts/:id.html do @post = Post.find(params[:id]) erb "posts/show.html".to_sym, :layout => layouts/app.html.to_sym #render /views/posts/show.html.erb with #layout /views/layouts/app.html.erb end get /application.css do sass :application #render /views/application.sass end 所有的view名稱都必需是symbol, 不可以是String 2711年8月26日星期五
  • Compass Integration set :app_file, __FILE__ set :root, File.dirname(__FILE__) set :views, "views" set :public, static configure do Compass.add_project_configuration(File.join(Sinatra::Application.root, config, compass.config)) end get /stylesheets/:name.css do content_type text/css, :charset => utf-8 sass(:"stylesheets/#{params[:name]}", Compass.sass_engine_options ) end 2811年8月26日星期五
  • Inline Templates get / do erb :"root" end template :root do <<"EOB" <p>Hello Sinatra!</p> EOB end 2911年8月26日星期五
  • Before filters before /rooms/:room_id do puts "Before route「/rooms/*」 only" @room = Room.find(params[:room_id]) end before do puts "Before all routes" end get / do ... end get /rooms/:room_id do puts "object variable 「@room」 is accessiable now!" "You are in room #{@room.name}" end 3011年8月26日星期五
  • Before filters • 和路由使用相同表示式 • 沒有Rails的「prepend_before_filter」 • 先載入的先執行 3111年8月26日星期五
  • Before filter order before /rooms/:room_id do # 先執行 # Before route「/rooms/*」 only @room = Room.find(params[:room_id]) end before do # 後執行 # Before all routes end get / do ... end get /rooms/:room_id do # object variable 「@room」 is accessiable now! "You are in room #{@room.name}" end 3211年8月26日星期五
  • Session enable :sessions # equal to 「use Rack::Session::Cookie」 post /sessions do @current_user = User.auth(params[:account], params[:passwd]) session[:uid] = @current_user.id if @current_user end delete /sessions do session[:uid] = nil redirect / end 3311年8月26日星期五
  • Cookies get / do response.set_cookie(foo, :value => BAR) response.set_cookie("thing", { :value => "thing2", :domain => localhost, :path => /, :expires => Time.today, :secure => true, :httponly => true }) end get /readcookies do cookies[thing] # => thing2 end 3411年8月26日星期五
  • Helpers helpers do def member2json(id) Member.find(id).attributes.to_json end end get /members/:id.json do member2json(params[:id]) end 3511年8月26日星期五
  • Helpers Helpers can use in: 1. filters 2. routes 3. templates 3611年8月26日星期五
  • Halt & Pass • Halt • 相當於控制結構中的break, 阻斷後續執 行強制回應. • Pass • 相當於控制結構中的next. 3711年8月26日星期五
  • Halt Example helpers do def auth unless session[:uid] halt 404, You have not logged in yet. end end end before /myprofile do auth end get /myprofile do #如果session[:uid]為nil,則此route不會執行 end 3811年8月26日星期五
  • Pass Example get /checkout do pass if @current_member.vip? #一般客結帳處理 erb "normal_checkout".to_sym end get /checkout do #VIP專用結帳處理 erb vip_checkout.to_sym end 3911年8月26日星期五
  • body,status,headers get / do status 200 headers "Allow" => "BREW, POST, GET, PROPFIND, WHEN" body Hello # => 設定body body "#{body} Sinatra" # => 加料body end 4011年8月26日星期五
  • url and redirect get /foo do redirect to(/bar) end url(別名to)可以生成包含了baseuri的url; redirect則 同其名可進行http重定向並可附加訊息或狀態碼. 4111年8月26日星期五
  • send_file get /attachments/:file do send_file File.join(/var/www/attachments/, params[:file]) #可用選項有 # filename:檔名 # last_modified:顧名思義, 預設值為該檔案的mtime # type:內容類型,如果沒有會從文件擴展名猜測。 # disposition:Content-Disposition, 可能的包括: nil (默認), :attachment (下載附件) 和 :inline(瀏覽器內顯示) # length:Content-Length,預設為檔案size end 另⼀一個helper: attachment等於send_file的disposition 為:attachement的狀況. 4211年8月26日星期五
  • request物件 # 在 http://example.com/example 上運行的應用 get /foo do request.body # 被客戶端設定的請求體(見下) request.scheme # "http" request.script_name # "/example", 即為SUB-URI request.path_info # "/foo" request.port # 80 request.request_method # "GET" request.query_string # "" 查詢參數 request.content_length # request.body的長度 request.media_type # request.body的媒體類型 end 4311年8月26日星期五
  • settings #以下三行都是同一件事 set :abc, 123 set :abc => 123 settings.abc = 123 ###可用區塊###### set(:foo){|val| puts(val) } get / do settings.foo(Sinatra) #will puts "Sinatra" "setting abc = #{settings.abc}" end Sinatra提供了多種方式讓你在Class Scope和Request Scope都能取用與設定資料或區塊, 其中有⼀一些預設 的settings是有關系統運作與設定的. 4411年8月26日星期五
  • 重要的setting • public(public) • 指定public目錄的位置 • views(views) • 指定template/views目錄位置 • static(true) • 是否由Sinatra處理靜態檔案, 設為false交給WEB伺服器會增強效能 • lock(false) • 設定false開啟thread模式(單⼀一行程⼀一次處理多個requests) • methid_override(視情況而定) • 開始「_method」參數以使用get/post以外的http method • show_exceptions(預設值與environment有關) • 是否像rails⼀一樣顯示error stack 4511年8月26日星期五
  • settings的特別用途 set(:probability) { |value| condition { rand <= value } } get /win_a_car, :probability => 0.1 do "You won!" end get /win_a_car do "Sorry, you lost." end condition是⼀一個Sinatra內建的method,可以視傳 入區塊的執行結果為true或false決定視否執行 該route或pass掉. 4611年8月26日星期五
  • configure do Configure Block # 直接設定值 set :option, value # 一次設定多個值 set :a => 1, :b => 2 # 等於設定該值為true enable :option # 同上 disable :option # 可用區塊 set(:css_dir) { File.join(views, css) } end configure :production do # 可針對環境(RACK_ENV)做設定 LOGGER.level = Logger::WARN end get / do settings.a? # => true settings.a # => 1 end 類似Rails的environment.rb, 在行程啟動時執行⼀一次. 4711年8月26日星期五
  • 錯誤處理 1. not_found 2. 特定error class 3. http status code 4. 對應所有錯誤 4811年8月26日星期五
  • error block error do #透過env[sinatra.error] 可取得錯誤物件 Sorry there was a nasty error - + env[sinatra.error].name end error 處理區塊在任何route或filter拋出錯誤的時候 會被調用。 錯誤物件可以通過sinatra.error的env hash項目取得, 可以使用任何在錯誤發生前的filter或 route中定義的instance variable及環境變數等 4911年8月26日星期五
  • 自定義error block error MyCustomError do So what happened was... + env[sinatra.error].message #輸出為:「 So what happened was... something bad」 end get / do raise MyCustomError, something bad end 如同Ruby的rescue區塊, error處理⼀一樣可以針對 error class做定義;也可以在執行期故意啟發特定 error class的錯誤並附加訊息. 5011年8月26日星期五
  • 自定義error block error 403 do Access forbidden end get /secret do 403 end error 400..510 do Boom end 針對特定的HTTP CODE設定錯誤處理區塊, 可以是 代碼範圍 5111年8月26日星期五
  • not_found block not_found do This is nowhere to be found end not_found區塊等於 error 404區塊 5211年8月26日星期五
  • Rack Middleware require rubygems require sinatra use Rack::Auth::Basic, "Restricted Area" do |username, password| [username, password] == [admin, 12345] end get / do "You are authorized!" end 和Rails⼀一樣, Sinatra也是基於Rack的middleware, 所以 可以使用其它的Rack middleware. 5311年8月26日星期五
  • 模組化 require rubygems require sinatra/base #不可以require "sinatra" 以避免頂層Object Class被汙染 class MyApp < Sinatra::Base set :sessions, true set :foo, bar get / do Hello world! end end 為了建構可重用的組件,需要將你的Sinatra應用程式 模組化以將程式化為⼀一個獨立的Rack Middleware. 5411年8月26日星期五
  • Multiple App in 1 process #config.ru require rubygems require sinatra/base class App1 < Sinatra::Base get / do I am App1 end end class App2 < Sinatra::Base get / do I am App2 end end map / do run App1 end map /app2 do run App2 end 利用Rack::Builder可將不同的Rack App掛在不同的uri 下面. 5511年8月26日星期五
  • 何時需要模組化 • 特定的Rack App Server(Passenger/Heroku等) • 將你的Sinatra App當做⼀一個Middleware而非終 點(endpoint), 例如: 1. ⼀一次掛載多個Sinatra App在同⼀一個rackup 2. 在Rails內掛載Sinatra App 5611年8月26日星期五
  • Rails Metal #routes.rb TestMixin::Application.routes.draw do devise_for :users mount ApiApp, :at => /api# => 掛載 ApiApp在/api 下 root :to => "welcome#index" end #lib/api_app.rb class ApiApp < Sinatra::Base get /users/:id.json do User.find(params[:id]).attributes.to_json end end 5711年8月26日星期五
  • 限制 • 「可以」使用Rails的model • 「不可以」使用Rails的helper/controller • 「可能可以」使用Rails的views, 但極不建 議 • not_found的錯誤是「由Rails端」處理 • 「不可以」直接使用綁定Rails(Railties)的 組件 5811年8月26日星期五
  • Devise Mix-in #config/routes.rb TestMixin::Application.routes.draw do devise_for :users mount ApiApp, :at => /api# => 掛載 ApiApp在/api 下 end #lib/api_app.rb class ApiApp < Sinatra::Base get /users/:id.json do #等效 devise的 authenticate_user! request.env[warden].authenticate!(:scope => user) User.find(params[:id]).attributes.to_json end end 由於Devise是建構在Warden(Rack Middleware)之上, 雖然不能直接使用Devise的認證helper, 但可以用 warden的方式來處理認證的問題. 5911年8月26日星期五
  • Scopes and Binding • Sinatra的變數定義域分成兩種 1. Application/Class Scope 2. Request/Instance Scope 6011年8月26日星期五
  • Scopes-範例 class MyApp < Sinatra::Base # => Application/Class Scope configure do # => Application/Class Scope set :foo, 100 end self.foo # => 100 helpers do # => Application/Class Scope def foo # => Request scope for all routes end end get /users/:id do # => Request scope for "/users/:id" only settings.foo # => 100 end get / do # => Request scope for / only end end Request scope可透過settings helper取得在 Application scope定義的設定值 6111年8月26日星期五
  • Application/Class scope 1. 應用程式的Class Body 2. helpers/configure的區塊內 3. 傳遞給set的區塊 6211年8月26日星期五
  • Request/Instance scope 1. session/request物件只在這裡有效 2. 作用於: 2.1.routes區塊 2.2.helper methods 2.3.view/templates 2.4.before/after filters 2.5.error block 2.6. 傳給settings的區塊中的condition區塊 6311年8月26日星期五
  • 實體變數的定義範圍 class MyApp < Sinatra::Base before do # => Request scope for all routes @varall = 100 end before /posts/:id do @post = Post.find(params[:id]) end get /posts/:id do # => Request scope for 「/posts/:id」 @post.nil? # => false @varall # => 100 settings.get(/foo){ # => Request scope for 「/foo」 only @varall # => 100 @post.nil? # => true } end get / do @varall # => 100 @post # => nil end end 就算是在route block中定義的另⼀一個route block, ⼀一樣 不能共用實體變數, 在before filter中定義的實體變數 會傳到下⼀一個符合條件的before filter與route block. 6411年8月26日星期五
  • 存在Class Body中的Request Scope #####sinatra/base.rb## module Sinatra class Base class << self def host_name(pattern) condition { pattern === request.host } end end end end ##等同於自行定義############# set(:host_name){|pattern| condition { pattern === request.host }} ###################### get /, :host_name => /^admin./ do "Admin Area, Access denied!" end 傳遞給condition method的區塊內的scope是Request Scope, 所以可以使用request物件 6511年8月26日星期五
  • Production Tips 1. ORM 2. Useful Extensions 3. Paginators 4. boot.rb 6611年8月26日星期五
  • ActiveRecord require rubygems require sinatra require active_record #先建立連線 ActiveRecord::Base.establish_connection( :adapter => sqlite3, :database => sinatra_application.sqlite3.db ) #require或宣告class class Post < ActiveRecord::Base end get / do @posts = Post.all() erb :index end 6711年8月26日星期五
  • Mongoid require rubygems require sinatra require mongoid Mongoid.configure do |config| config.master = Mongo::Connection.new.db("godfather") end class User include Mongoid::Document include Mongoid::Timestamps end get /users/:id.json do User.find(params[:id]).to_json end 6811年8月26日星期五
  • Sinatra More • MarkupPlugin • 設定form以及html tag的helper等 • RenderPlugin • content_for/yield等 • WardenPlugin • 綁定單⼀一Class做登入處理 • MailerPlugin • 顧名思義, 但不是使用ActionMailer • RoutingPlugin • 做出有namespace以及restful的路由 • code generator • 建立project框架 6911年8月26日星期五
  • WillPaginate • 3.0後版本已直接以extension型式支援Sinatra • 2.3版本需要手動改寫renderer • 由於Sinatra不像Rails有固定的url形式約束, 必要時還是要自己改寫renderer 7011年8月26日星期五
  • Kaminari • Model Paginate功能可正常使用 • View Helpers的部份無法使用 • 需依照ORM require正確的組件 7111年8月26日星期五
  • boot.rb require bundler Bundler.setup Bundler.require class FreeChat3 < Sinatra::Base configure do #settings set :sessions, true #Middlewares use Rack::Flash #Sinatra Extensions register SinatraMore::MarkupPlugin #DB Connections ActiveRecord::Base.establish_connection(DB_CONFIG[RACK_ENV]) Mongoid.load! File.join(ROOT_DIR, /config/mongoid.yml) #load Model/Controller/helper/Libs Dir.glob(File.join(ROOT_DIR, /lib/*.rb)).each{|f| require f } Dir.glob(File.join(ROOT_DIR, /app/models/*.rb)).each{|f| require f } Dir.glob(File.join(ROOT_DIR, /app/helpers/*.rb)).each{|f| require f } Dir.glob(File.join(ROOT_DIR, /app/controllers/*.rb)).each{|f| load f } end helpers do include ApplicationHelper include ActionView::Helpers::TextHelper end end 7211年8月26日星期五
  • Q&A 7311年8月26日星期五