上个月的博文里说过,由于要开发同济权益的二手书管理系统,所以用PHP写过这样的代码。第一个版本是纯PHP撸成的,没有用到任何的外部框架,路由也是纯手写.htaccess文件实现的,一共就四个文件,负责分发请求,最后交给处理数据库的文件来输出对应的JSON信息。现在看来,这大概叫强行RESTful吧,哈哈。话说回来呢,当时写的时候,甚至还有点基本的ORM思想,不过觉得太麻烦就没着手完成这个。后来才知道这个模型有个高大上的英文缩写叫做ORM。当然,这个版本的代码,模块性一点都不好。五百多行,基本没法维护。

后来寒假的时候,确实是觉得这个太不行了,需要找个框架重写,顺便学习一个。用哪个框架呢?知乎搜索了一下,就用CodeIgniter吧,相对轻量,又确实有用。这个框架是MVC架构的,但是也没有实现完整的关系对象模型,仅仅是封装了方便的数据库查询器而已。这个版本,自我感觉代码结构好多了,不过好像和前端交互的API还是没有改。

到了四月初的时候,听说软件学院要搞一个Android应用开发比赛,所以呢,后端可能还得改。这次大家坐在一起开了个会把需求相对完整地定了下来。不过,还没等得及下手,一切又变了。应用还没开始做呢,就又变卦了。这次是得改变架构,网页和后台不分离。大概就剩一个月的时间了。怎么搞呢?用Ruby on Rails吧。听说,这玩意很快啊。

要说之前,Ruby这个语言我就没有碰过。虽然它和Python我都不熟,不过其实还是用Python相对多一点。但是不行啊,硬着头皮也得上。所幸Rails的文档写得确实不错,我做这个网站的过程中很大程度参考了Ruby on Rails的中文指南。如果有朋友对Rails这个框架感兴趣,非常推荐这一系列文档。同时,也听说Ruby China在国内的技术社区里面是最有氛围的一个了。

Rails无非也就像是其他MVC的Web框架一样,在目录下通过命令生成一个新项目,然后运行服务器,打开看到一个正确运行的页面。这就标志着我们的开发工作正式开始了。当然由于Rails这个框架比较复杂,生成的代码也比较多,所以新建一个控制器还不能像CodeIgniter这种框架一样直接手动新建个controller文件就好了,需要用到generate命令。敲下命令之后,发现Rails确实爆炸,自动帮我生成了RESTful风格的路由和一堆代码。

由于朝RESTful看齐的特点,直接在Rails的路由文件routes.rb里像这样写:

Rails.application.routes.draw do
 
  resources :books
  resources :users
  resources :appointments

  root 'welcome#index'
end

寥寥几行代码,Rails就帮我们生成好了书、用户、预约三种资源的所有路由,已经定义了网站根目录对应的控制器方法。

说到生成,Rails还有一个更厉害的东西,叫做「脚手架」。利用它,可以一键生成控制器、视图和模型的代码。虽说这些代码直接拿来用不太现实,不过也可以作为初学者学习的重要参考。

Rails的脚手架用法如下:

rails g scaffold books title:string price:decimal

这样就快速创建了一个叫做books的资源,包含title和price两个属性。脚手架为我们快速创建了路由、控制器、视图、模型,甚至测试文件。脚手架生成的代码虽然完整,但是往往不能完全满足我们的需求,我们也未必喜欢它给我们强加上的一堆网页样式。我们可以基于脚手架的代码来修改,不过更多的时候只是用它当作例子来学习,看看标准的Rails代码是个什么样子。

Rails严格区分了资源的单复数。一个资源在作为Model层的时候是单数的,比如Book;而在View层和Controller层的时候是复数的,比如books_controller.打开Rails为我们创建的这个controller,我们可以发现七种RESTful的操作已经预先定义好了,并且自带html和json两种模式。很迷的是在许多方法里只有一行语句甚至没有语句,那么Rails是怎么渲染View的呢?

实际上,Rails会自动找到与controller里的方法同名的模版文件(Rails默认用的是erb)然后渲染。当然有些方法是不需要HTML模版的,比如POST、PUT这些HTTP请求使用的controller就不需要特别的模版,用得更多的是重定向。当然,我们可以手动选择要渲染的模板。即使用render方法。

Rails的模板是怎么写的呢?因为Ruby语言不靠缩进来标记语法层次,所以Ruby可以轻松地嵌入到HTML模板文件当中,这一点比不得不自造模板语言的Django高到不知哪里去了。这类模板也就是所谓的erb。Rails可以在设置里选用其他的模板,比如haml乃至markdown。

被渲染的erb模板能够正确地加载调用它的控制器里的所有实例变量。什么意思?比如像这样:

# controllers/books_controller.rb
class BooksController
  def index
    @time = Time.now
  end
end

# views/books/index.html.erb
<p>Current time is <= @time %>.</p>

所谓的带@符号的变量是Ruby的一种语法,称为实例变量,表示类的数据成员。如果我们去掉这里的@符号,把time定义成局部变量的话,模板渲染的时候就会报错了。

我们把Rails的视图层和控制器都极简地介绍了一下。尽管没说几句话,但是已经基本可以构建一个可以运行的网站了。这都要归功于Rails的良好封装和Ruby语言的自由度。现在我们来看看之前被忽略掉的Model层。实际上,Rails更加鼓励把更多的逻辑写到模型层里面。(Rails还需要我们为controller写什么代码吗?)对于书这种资源,我们也能在models目录下发现对应的代码文件book.rb,注意到book用的是单数。

class Book < ActiveRecord::Base
end

咦,这个Book里什么代码也没有,就一个空类嘛,有什么用?

别急,如果我们在Controller里面想要查询关于Book的信息,非常简单:

def get
  @book = Book.find_by_id params[:id]
end

有趣,这个find_by_id是哪来的?噢,忘说了,Ruby里面方法调用跟Perl一样不需要括号噢。

哈哈,这就是Ruby语言的威力,它可以把字符串和代码相互转换,并且可以在运行时动态定义新的方法,甚至覆盖旧的方法。Ruby看到我们find_by_id这个方法时,不会急着报错,而是去寻找有没有「当方法找不到时的解决方案」,而Rails在这里为我们提供了一个。它会去数据库的结构里查找id这个字段,然后返回给我们想要的结果。也就是说,如果我们的Book表里新增了一个字段叫ISBN,那么我们可以直接find_by_isbn了,什么也不用改。

这只是Rails的数据库子包ActiveRecord强大功能的冰山一角。另一个值得一提的地方是它的关联机制。比如这样:

class Article &lt; ActiveRecord::Base
  has_many :comments
end

# 注意单复数
class Comment &lt; ActiveRecord::Base
  belongs_to :article
end

# Rails会去寻找Comment表里article_id这个字段
some_article = Article.take
some_article.comments # 我们已经得到了此文章对应的所有评论

这里对article_id这个字段名的假定体现了Rails的一个重要原则:「约定大于配置」。相比于Java繁复的各种XML配置文件,不知道Rails的这种风格有没有让你觉得清爽呢?

拖了六个月,说了这么多,其实也就是Rails最基本的一点点。不过足以让许多人认识到Rails框架和Ruby语言的魔力了。整个Ruby社区很有趣的一点就是,从Rails的插件到各个方面的库,作者都会以这个库的使用方式「看起来像英语」为荣,比如:

every 3.hours do
  sleep 2.minutes
end

得益于独特的语言文化和语言的强大能力,这样的DSL在整个Ruby社区到处都是!

文章很短,不过希望你能从中感受到用Ruby编程的魅力。就像DHH说的,成为百万富翁以后才发现,开兰博基尼还不如写Rails来得快乐。(虽然我没开过)

以下有两个页面可供想学习Ruby on Rails的你参考: