2. About me
• 鄧慕凡(Mu-Fan Teng)
•a.k.a: Ryudo
•http://twitter.com/ryudoawaru
•Ruby developer since 2007
•Founder of Tomlan Software Studio.
2
11年8月26日星期五
15. Hello Sinatra!
require 'rubygems'
require 'sinatra'
get '/' do
'Hello Sinatra!'
end
15
11年8月26日星期五
16. 流程
BEFORE ROUTES AFTER
before get ‘/’ after
before ‘/users’ post ‘/users’ after ‘/users’
put ‘/users/:id’
get ‘/users/:id’
16
11年8月26日星期五
17. 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成員.
17
11年8月26日星期五
18. 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]中.
18
11年8月26日星期五
19. 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區域變數
19
11年8月26日星期五
20. 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
以上所介紹的路由表示式都可以將匹配的內容
指派到區塊的區域變數中
20
11年8月26日星期五
21. Conditional route
get '/foo', :agent => /MSIEs(d.d+)/ do
"You're using IE version #{params[:agent][0]}"
# => IE特製版
end
get '/', :host_name => /^admin./ do
"Admin Area, Access denied!"
end
也可以用user_agent或host_name來做路由啟發
的條件; 注意的是agent可以是字串或正規表示
式, hostname只能是正規表示式.
21
11年8月26日星期五
22. Conditions(2)
get '/', :provides => 'html' do
erb :index
end
get '/', :provides => ['rss', 'atom', 'xml']
do
builder :feed
end
provides的條件是看accept header而非path
22
11年8月26日星期五
23. 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
23
11年8月26日星期五
24. Route Return Values
1. HTTP Status Code
2. Headers
3. Response Body(Responds to #each)
24
11年8月26日星期五
25. 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
25
11年8月26日星期五
27. 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
27
11年8月26日星期五
28. 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
28
11年8月26日星期五
29. Inline Templates
get '/' do
erb :"root"
end
template :root do
<<"EOB"
<p>Hello Sinatra!</p>
EOB
end
29
11年8月26日星期五
30. 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
30
11年8月26日星期五
32. 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
32
11年8月26日星期五
33. 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
33
11年8月26日星期五
34. 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
34
11年8月26日星期五
35. Helpers
helpers do
def member2json(id)
Member.find(id).attributes.to_json
end
end
get '/members/:id.json' do
member2json(params[:id])
end
35
11年8月26日星期五
36. Helpers
Helpers can use in:
1. filters
2. routes
3. templates
36
11年8月26日星期五
37. Halt & Pass
• Halt
• 相當於控制結構中的break, 阻斷後續執
行強制回應.
• Pass
• 相當於控制結構中的next.
37
11年8月26日星期五
38. 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
38
11年8月26日星期五
39. 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
39
11年8月26日星期五
40. body,status,headers
get '/' do
status 200
headers "Allow" => "BREW, POST, GET, PROPFIND, WHEN"
body 'Hello' # => 設定body
body "#{body} Sinatra" # => 加料body
end
40
11年8月26日星期五
41. url and redirect
get '/foo' do
redirect to('/bar')
end
url(別名to)可以生成包含了baseuri的url; redirect則
同其名可進行http重定向並可附加訊息或狀態碼.
41
11年8月26日星期五
46. 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掉.
46
11年8月26日星期五
47. 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, 在行程啟動時執行⼀一次.
47
11年8月26日星期五
48. 錯誤處理
1. not_found
2. 特定error class
3. http status code
4. 對應所有錯誤
48
11年8月26日星期五
49. 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及環境變數等
49
11年8月26日星期五
50. 自定義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的錯誤並附加訊息.
50
11年8月26日星期五
51. 自定義error block
error 403 do
'Access forbidden'
end
get '/secret' do
403
end
error 400..510 do
'Boom'
end
針對特定的HTTP CODE設定錯誤處理區塊, 可以是
代碼範圍
51
11年8月26日星期五
52. not_found block
not_found do
'This is nowhere to be found'
end
not_found區塊等於 error 404區塊
52
11年8月26日星期五
53. 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.
53
11年8月26日星期五
54. 模組化
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.
54
11年8月26日星期五
55. 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
下面.
55
11年8月26日星期五
57. 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
57
11年8月26日星期五
61. 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定義的設定值
61
11年8月26日星期五
64. 實體變數的定義範圍
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.
64
11年8月26日星期五
65. 存在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物件
65
11年8月26日星期五
67. 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
67
11年8月26日星期五
68. 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
68
11年8月26日星期五
72. 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
72
11年8月26日星期五