Sinatra通过helpersregister函数进行扩展
首先看一下helpers函数,用来是来扩展Base类的实例方法。

1
2
3
4
def helpers(*extensions, &block)
class_eval(&block) if block_given?
include(*extensions) if extensions.any?
end

在看一下register函数,用来扩展Base类的类方法

1
2
3
4
5
6
7
8
def register(*extensions, &block)
extensions << Module.new(&block) if block_given?
@extensions += extensions
extensions.each do |extension|
extend extension
extension.registered(self) if extension.respond_to?(:registered)
end
end

两个方法都支持通过代码块扩展,也支持通过Module来扩展。

两种扩展方法对应Sinatra的两种编程风格。

Sinatra编程的两种风格:

经典风格:

1
2
3
4
5
require'sinatra'
get '/' do
"hello world"
end

模块化风格:

1
2
3
4
5
6
7
8
9
10
require'sinatra/base'
class Hello < Sinatra::Base
get "/" do
"hello world"
end
run!
end

为两种风格编写扩展:

Sinatra扩展也分为两种:

helper 型
dsl 型

helper型:

1
2
3
4
5
6
7
8
9
10
11
require 'sinatra/base'
module Sinatra
module FormatHelper
def escape_html(text)
Rack::Utils.escape_html(text)
end
end
helpers FormatHelper
end

classic style 使用extension

1
2
3
4
5
require 'sinatra'
get '/' do
escape_html("x > y")
end

modular style 使用extension

1
2
3
4
5
6
7
8
9
10
11
12
require 'sinatra/base'
class Hello < Sinatra::Base
helpers Sinatra::FormatHelper
get '/' do
escape_html("x > y")
end
run!
end

这里的helpers其实相当于include

dsl型:

1
2
3
4
5
6
7
8
9
10
11
12
13
require 'sinatra/base'
module Sinatra
module Devise
def authenticate!
before {
halt 403, "You Bastards!"
}
end
end
register Devise
end

classic style 使用 dsl extension

1
2
3
4
5
6
7
require 'sinatra'
authenticate!
get '/' do
escape_html("x > y")
end

modular style 使用 dsl extension

1
2
3
4
5
6
7
8
9
10
11
12
13
require 'sinatra/base'
class Hello < Sinatra::Base
register Sinatra::Devise
authenticate!
get '/' do
"hello world"
end
run!
end

上面就是两种扩展的用法,我们在深入一点

Sinatra/main的最后几行可以看到extend Sinatra::Delegator,其实当 require 'sinatra'的时候就是执行的上面那行代码,作为整个Sinatra的入口。

看一下Delegator模块,默认的target是Application,也就是helpersregister等Sinatra的一干关键字get post ...等都是通过动态派发给Application类执行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module Delegator
def self.delegate(*methods)
methods.each do |method_name|
define_method(method_name) do |*args, &block|
return super(*args, &block) if respond_to? method_name
Delegator.target.send(method_name, *args, &block)
end
private method_name
end
end
delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink,
:template, :layout, :before, :after, :error, :not_found, :configure,
:set, :mime_type, :enable, :disable, :use, :development?, :test?,
:production?, :helpers, :settings, :register
class << self
attr_accessor :target
end
self.target = Application
end

Delegator模块功能就是定义派发给Application的函数(不管实现,只是转发)

整个通过Delegator.delegate注册的方法的实现都在Helper这个模块里,在Base类里include各种方法的实现,然后Application在继承Base,有点绕~。

1
2
3
4
5
6
class Base
include Rack::Utils
include Helpers
include Templates
...
end

通过一张图理清楚他们的关系

定义自己的关键字,像get post这样。

扩展方法首先在Delegator模块调用delegate方法的时候添加你扩展的关键字名称,类似delegate :get, :patch, ... , :my_fun, 然后在Helper模块里定义my_fun的实现

1
2
3
4
5
6
module Helpers
my_fun
p "my_fun"
end
...
end

这样就可以像关键字一样使用my_fun了,当然也可以通过上面介绍的两个方法进行扩展。

参考 http://saito.im/note/Sinatra-Extensions/