原文请前往Let's Build Sinatra
曾经看过一本书《Rebuilding Own Rails》,土豪请支持作者。对于中国读者来说,此书确实挺贵。
看了这两本书以后,略作总结,顺便和大家分享一下。
无论是 Rails、还是 Sinatra 这些基于 Rack 的框架,原理都是调用 Rack 的 call 函数,此函数接受一个参数,env。 Rack 的基本原理,是把对 Rack 对象发送 HTTP 请求的“环境“,变量 env,作为参数来调用 call 方法,然后把返回值当作是对请求的回应。 在我们生成的 Rails 应用程序里面,有个文件config.ru,这个文件就是为 Rack 准备的。 我们可以试试如下代码 (节选至《代码的未来》,210 页):
# hello.rb
class HelloApp
def call(env)
[ 200, # 依次是 HTTP 状态码,200表示成功;
{"Content-Type" => "text/plain"}, # HTTP 响应头部(即HTML文件中header部分),放在Hash中;
["Hello, Rack World!"] # HTTP 响应主体(即HTML文件中body部分,也就是我们打开浏览器看到的内容)
#必须是字符串数组。(为什么是字符串数组,而不是字符串?)
]
end
end
call 函数的返回值,最后一个 body 部分,因为在 Ruby 1.9 里,如果是字符串,就会导致程序崩溃,所以必须是 Enumerator 类型的,(可接受 each 方法),具体前往Rack SPEC
另外需要准备一个配置文件,以.ru结尾
# hello.ru
require 'rubygems'
require 'rack'
require 'hello'
#至为关键的一句
run HelloApp.new
然后在命令行终端运行:
$ rackup hello.ru
在 Rails 项目里,相应的配置文件名为config.ru
# config.ru
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__) # 包含启动文件
run Rails.application #启动程序
如果你在 Rails 应用的目录下运行以下命令,会有如下输出:
diancai.in master % rackup config.ru
[2015-10-12 21:36:37] INFO WEBrick 1.3.1
[2015-10-12 21:36:37] INFO ruby 2.2.3 (2015-08-18) [x86_64-darwin15]
[2015-10-12 21:36:37] INFO WEBrick::HTTPServer#start: pid=6490 port=9292
神奇吧!
好了,说完 Rack 的基础知识,再看看 Sinatra 是怎么使用 Rack 的。
这个是用来简化代码,让框架看起来更像是 DSL。 以下代码来自于Let's Build Sinatra
module Nancy
class Base
end
Application = Base.new
end
这个其实很容易理解,但是对简化代码来说,不得不说是一个巧妙的设计。
这一点稍微有点让人费解
module Nancy
module Delegator
def self.delegate(*methods, to:)
Array(methods).each do |method_name|
define_method(method_name) do |*args, &block|
to.send(method_name, *args, &block)
end
private method_name
end
end
delegate :get, :patch, :put, :post, :delete, :head, to: Application
end
end
这段代码采用元编程的技术,让我们在调用函数时不必这样写:
Application.get "/", {}
而可以直接写成
get '/', {}
上面的代码利用 define_method 动态定义了:get, :patch 等,而且把这些方法作为 Application(即:Nancy::Base.new 实例的实例方法)。
它的真正的 trick 在于,在利用这个框架,写的应用程序,如:
# app.rb
# run with `ruby app.rb`
require "./nancy"
get "/" do
"Hey there!"
end
中,表面上好像直接调用 Object 的 get 方法,于是你查看所有 ruby 文档也没有 get 这个方法,你可能有点崩溃了,不知道它是来自于那里,干什么用的。
其实,就是因为在nancy.rb里面有一句include Nancy::Delegator
, 我们把get
方法补全,就一下看明白这个故意设计的 trick;
# app.rb
# run with `ruby app.rb`
require "./nancy"
Nancy::Application.get "/" do
"Hey there!"
end
或者更近一步:
# app.rb
# run with `ruby app.rb`
require "./nancy"
Nancy::Base.new.get "/" do
"Hey there!"
end
这样写,就清楚明了了,但是可能写两句你就很不乐意了。
总结,无论是 Rails,还是 Sinatra,在使用了这些元编程技术之后,让我们的代码更加简洁,也让我们对于代码所要完成的任务更加清晰明了。