用 Sinatra 编写博客应用

阅读数:9385 2011 年 1 月 21 日

Sinatra 是 Blake Mizerany 在 2007 年 9 月开发的 Ruby 语言的 Web 框架。它最突出的特点就是轻量、快速。更难能可贵的是,Sinatra 的源代码只有一千多行。

在第一次接触到 Sinatra 的时候,我便被它深深地吸引住了。随后,我在 09 年 3 月的 Shanghai on Rails 活动向大家介绍了这个框架。10 年 8 月份我有幸可以在 RubyKaigi 这样的全球级 Ruby 社区会议上作为演讲者和听众交流 Sinatra。本文则是对 10 年 10 月份在上海 Linux 用户组介绍 Sinatra 的讲座的一些整理和总结。希望读者能够通过本例子能体会到 Sinatra 的精妙之处。

最新版本: 1.1

截止到本文成文为止,Sinatra 最新的版本是 10 年 10 月 24 日发布的 1.1 版本。很幸运的是,我对于 README 的翻译正好在发布的前一天被合并进入了主分支。于是在 1.1 的正式版本中,中文的读者可以直接阅读到中文的 README,从而更好的了解 Sinatra 的用法。官网上也有此文档的链接,http://www.sinatrarb.com/intro-zh.html。本文的代码全部以 1.1 版本为准。

Sinatra 的基本结构

让我们从 Sinatra 最常见的 Hello world 程序开始:

get '/' { "Hello, world!" }

这段简单的 Hello world 程序包含了 Sinatra 程序的三个基本组成部分:

  • 路由(route):

    '/' 就是路由。路由可以是单一的路径,或者带有参数的路径(比如 /:name),甚至是正则表达式。对于 Sinatra 不知道的路由,Sinatra 会返回 404 错误(作为 App 运行的时候),或者传递给下面的中间件(作为中间件运行的时候)。

  • 方法(method):

    get是方法。在 Sinatra 中,HTTP 的四个方法GET/POST/PUT/DELETE都有相应的方法get/post/put/delete

  • 处理器(handler):

    处理器就是最后的代码块,处理器的返回值就是 Sinatra 返回给客户端(主要是浏览器)的内容。返回值主要以字符串为主,也可以是包含状态码,消息头,消息体的数组。

渲染模版

Sinatra 支持的模版类型也在逐渐增加中。Haml 是笔者常用的格式,因为它使用了 CSS 选择符构造 HTML 标签,从而节省编写时间。另一种常见的格式是 Ruby 自带的 ERB,本例子将使用 Haml 作为博客的模版。

渲染模版在 Sinatra 中是很容易的事:

get '/' do
  haml :index
end

在这里haml :index,就表示使用 Haml 渲染'views/index.haml'这个模版。

传递参数也是很容易的事,可以使用实例变量:

# in app.rb
get '/' do
  @now = Time.now
  haml :index
end
# in views/index.haml
Hello, now is #{@now}

或者用 locals 传递参数(如例子中的哈希):

# in app.rb
get '/' do
  now = Time.now
  haml :index, :locals => { :now => now }
end
# in views/index.haml
Hello, now is #{now}

熟悉了路由和模版,就可以开始构建 Web 应用程序了,Sinatra 也提供了一些简单的辅助方法,比如过滤器、helpersconfigurehaltpass等等,这些就不再这里一一叙述了,更多的内容请仔细参考官方文档。

开始博客应用

文件格式

本博客应用将使用dorothy格式的文件存储,不会使用数据库。

例子如下:

# 文件名: 2010-10-10-a-lucky-day.txt
title: "A Lucky Day"
date: 2010-10-10
author: "吴江"
 
# 今天是我的幸运日 
 
早上在地铁门将要关上的那一刻,我冲进了车厢,于是约会没有迟到...
 
中午提前了一点去港丽,居然只排了 42 分钟...
 
晚上又赶上了末班车...
 
到家数了数,钱包里面正好有 42 块钱...

该文件的结构是:以第一个连续换行符("\n\n")为界线,前一半是YAML格式的配置信息,后一半则是markdown格式的文本。YAML格式是一种表示数据的标记语言。这里只使用到它的键值对结构。markdown则是很方便的用纯文本编写 HTML 的格式。比如"# header1"会生成"<h1>header1</h1>""*emphasis*"会生成"<em>emphasis</em>"等等。

安装环境

本博客应用使用 Ruby 1.8.7 版本。安装好后,首先安装 Bundler(gem install bundler),然后编写 Gemfile(见下),运行bundle install即可一次性安装好所需的 gems。

# Gemfile
source "http://rubygems.org"
gem 'haml'      # Haml 模版 
gem 'rdiscount' # 渲染 Markdown
gem 'sinatra'   # Sinatra
gem 'thin'      # 应用服务器 
gem 'shotgun'   # 重启服务器 
group :test do
  gem 'rspec'     # 单元测试 
  gem 'nokogiri'  # 解析 HTML 输出 
end

测试驱动开发

使用测试驱动开发并非为了赶时髦,只是为了能够帮助我们写出更好的代码。

在本例子中,我们的测试需要能够达到以下目标:

  1. 访问"/"的时候能够正确返回文章列表(虽然只有一篇文章)

  2. 访问"/:year/:month/:date/:title"的时候能够正确地展示文章内容

正式编写

在本例子中,将只接受两个路由请求,'/''/:year/:month/:date/:title'

首先编写如下的测试:

# in app_spec.rb
describe 'blog' do
  before do
    @req = MockRequest.new(Sinatra::Application)
  end
 
  it "should show index correctly" do
    resp = @req.get '/'
    resp.status.should == 200
  end
end

运行rspec app_spec.rb可以看到失败结果。先编写简单的代码让测试通过。

# in app.rb
get '/' do
  ""
end

然后继续增加测试,我们想让返回的页面中有链接到/2010/10/10/a-lucky-day这个日志的链接

# in app_spec.rb
  ...
  it "should show index correctly" do
    resp = @req.get '/'
    resp.status.should == 200
 
    doc = Nokogiri.new(resp)
    (doc/'a[href="/2010/10/10/a-lucky-day"]').text.should == "A Lucky Day"
  end

为了通过这个测试则要写一些长一点的代码,为了省略篇幅,Article类的代码在这里忽略:

# in app.rb
get '/' do
  @articles = []
  Dir.glob("articles/*.txt").each do |article_file|
    @articles << Article.new(article_file)
  end
  haml :index
end

在上文的代码中,首先读取了 articles 目录下的所有 txt 后缀的文件,就是全部的日志。 并把这些日志装到@articles这个数组类型的实例变量。

在视图中,则简单的把日期和日志名称罗列出来。

# in views/index.haml
    ...
      - @articles.each do |article|
        %header
          %h2
            = article.date.strftime("%Y 年 %m 月 %d 日")
            %a{ :href => article.path }= article.title

接下来使用同样的方式来编写显示日志具体内容的代码:

it "should show article correctly" do
  resp = @req.get '/2010/10/10/a-lucky-day'
  resp.status.should == 200
  doc = Nokogiri(resp.body)
  (doc/'title').text.should == "A Lucky Day"
  (doc/'article h1').text.should == "今天是我的幸运日"
  resp.body.should match "钱包里面正好有 42 块钱"
end

实现所用的代码相对会少一些:

# in app.rb
get '/:year/:month/:day/:title' do |year, month, day, title|
  article_file = "articles/#{year}-#{month}-#{day}-#{title}.txt"
  @article = Article.new(article_file)
  haml :show
end
 
# in views/show.haml
!!!
%html
  %head
    %title= @article.title
  %body
    %header
      %h1
        = @article.title
    %article= @article.body

测试通过以后,也可以使用shotgun app.rb -s thin开启服务器, 访问http://localhost:9393就可以看到在浏览器中的效果。

部署

Heroku是目前为止最好用的 Ruby 应用部署服务之一。在Heroku的帮助下,我们可以快速地把这个应用发布给全世界使用。

首先编写config.ru

# in config.ru
run Sinatra::Application

然后运行如下代码:

# git 初始化 
git init .
git commit -a -m "Initial Commit"
 
# heroku 部署 
heroku create
git push heroku master

当看到"Launching ... done"的字样的时候,就说明我们的程序部署成功了,赶快点击下面的链接看看结果吧!

评论

Disqus是目前我知道的最好用的评论管理系统。更要命的是,它能够很简单的把一个评论系统加到我们的博客中:

<section class="comments">
  <script type="text/javascript" src="http://disqus.com/forums/#{username}/embed.js">
</section>

只要把上面这段 html 代码加入到我们的系统中,一个完善的评论系统就出现在用户的眼前。本地调试的时候则要额外加上一句:

<script type="text/javascript">var disqus_developer = 1;</script>

借助了 Disqus,我们的评论系统就不会逊色于任何的博客应用。

思考

如果读者能够在整个过程中感受到快乐或者惊奇,那么我编写本文章的目的就算达到了。 详细的代码请参考本文的项目地址:https://github.com/nouse/text-blog

以下则为笔者在制作这个应用过程之中的一些思考。

5 年前,Rails 的创造者 David Heinemeier Hansson 向全世界介绍了 15 分钟编写 blog 应用(优酷视频链接)。在 5 年后,我们又用 Sinatra 重复造轮子,如果读者对比两者的差别, 就能深刻感觉到这 5 年里 Ruby 世界的一些变化。

基本工具(RVM 和 Bundler)

这 5 年间,Ruby 基本工具有了很大的发展。这其中最大的亮点就是RVM(Ruby Version Manager)。 除了如它的名字所述,可以帮助开发人员安装不同版本的 Ruby 以外。它的 gemset 功能也非常 好用。不同的 gemset 之间是一个个独立的环境,从而避免同一个 gem 的不同版本之间的干扰。

如果在项目目录下添加.rvmrc(rvm use version@gemset),就可以让项目处于一个独立的环境之中。 再编写好 Gemfile,将项目中需要的 Ruby 库全部交给 Bundler 管理, 就不会出现部署的时候缺乏相应的库导致失败的情况了。

方便的部署

Git 的普及和 Heroku 的崛起,大大简化了部署的过程。如果 5 年前有 Heroku 的话, DHH 的博客应用可以有更大的反响。“编写完成”-->“git push”-->“上线!”。 一个博客应用就一瞬间仿佛活了一样,从一个本地的演示项目变成了一个真正的线上应用。

Disqus 等第三方应用的兴起

5 年前,Web 2.0 刚刚兴起,只要编写一个使用 Ajax 增强交互功能的应用, 就可以吸引用户的眼球。但是随着 Web 2.0 的概念深入人心,做一个 blog 显然不再能吸引用户的眼球了。

如果 Disqus 这样的第三方应用能够逐渐增多,那么我们就能够把更多的时间放在我们真正想实现的功能上。 就像这里,我们只要把博客的内容展示做好就够了,其他的则交给成熟的服务来处理。 Rails 的成功就在于简化了开发 Web 2.0 应用的时间。借用一下 jQuery 的口号“write less, do more”, “写的更少,做的更多”是软件开发永远的主题。

Sinatra 和 Rails 的关系

DHH 在推出 Rails 的时候,让深陷于 Java 世界的开发人员看到了希望,Rails 也借助 Web 2.0 的热潮迅速走红。 其实,笔者所做的演示的功能模仿的是一个 Rack 应用程序,toto。 所以读者们也不必迷信,用 Sinatra 经过 15 分钟能做出更好的博客应用,就说明 Sinatra 会取代 Rails。

当前最流行的方式是融合,比如 gemcutter.org,也就是现在的 rubygems.org。 他们整个站点使用的是 Rails 3,而客户下载 gem 的请求则是被 Sinatra 处理。 这样就可以保证网站在升级的时候不会影响下载 gem 的请求,而且 Sinatra 处理请求的速度也优于 Rails 3, 用来处理每天超过访问网站数倍的下载请求也十分合适。

不管怎样,只有更多的了解一个框架的优缺点,才能在真正使用的时候做出正确的选择。而 Sinatra 的源代码只有一千行,要了解它并做出选择,相信不是件难事。


关于作者:吴江,Ruby 和 Javascript 程序员。从 09 年开始,在国内社区中积极宣传和推广 Sinatra。10 年 8 月底,以演讲者的身份参加了在日本举行的 RubKaigi。现在上海一家 Ruby 行业的咨询公司工作。

收藏

评论

微博

发表评论

注册/登录 InfoQ 发表评论