Slapp: 简易聊天墙的Merb教程

有问题或评价,请联系: socialface@gmail.com

程序截图: http://www.socialface.com/slapp/screenshot.jpg

简介

欢迎来到Slapp的教程。本文的主要目标是通过构建一个简易的聊天墙应用来介绍一下Merb微框架的主要组件。

本文其次的目标是成为最好的Merb开放教程并能不断更新。同时,我们希望本教程可以逐渐变得丰富来展现Merb框架的所有方面和开发方式。

许可

This tutorial is Copyright 2008 Social Corp. and is licensed under a Creative
Commons Attribution-Noncommercial 3.0 United States License, available at:

相关的源代码以MIT形式的开发源代码许可:

直接浏览代码:

也可以直接下载:

参与人员名单

本教程最初由来自#merb的Slurry撰写。Slurry在撰写本文时主要参照了Merb的官方文档[^a]以及#merb IRC频道的内容。

在学习Merb的RSpec的过程中,还咨询了John Hornbeck的Blerb[^b],参考了Tim Connor的“Isolate controller and view testing in merb”一文[^c]——虽然帮助很大,真正掌握Merb/Rspec还是得益于来自#merb的benburkert。

中文版由ShiningRay翻译

前期决定

设计上来说,Merb是一个ORM无关的框架。当然,这仅仅是表示有不同的模型层可以选择。对于Merb v0.9.2来说,包括:DataMapper、Sequel和ActiveRecord(来自Rails)。

尽管DataMapper和Sequel都是很好的选择,但现在关注Merb的主流人群基本上都是来自Rails背景;因此,我们在本教程中会继续使用ActiveRecord。

1.) 创建模型

在像Merb这样快速成长的社区中,一个问题可能只需更新或者重装Merb相关的包(gem)就能简单解决,所以让我们先花点时间这样准备一下:

完成了前两行命令后,Merb、ActiveRecord以及Rspec应该已经安装好或已经更新了,同时后两行命令则应该创建了一个初始的空Merb应用。

从这里开始,我们就要告诉Merb:我们要怎样用ActiveRecord,要使用哪个测试框架,以及需要载入哪些确切的merb_helpers包(如表单相关的东西)

编辑:slapp/config/init.rb 并取消第2742的注释,同时将:dependency "merb_helpers" 添加在
Merb::BootLoader.after_app_loads do 这一行之前:

现在我们已经告诉Merb要使用ActiveRecord: use_orm :activerecord, 让我们来生成第一个模型:

靠,居然没效。不过等等,你应该还有一个全新的slapp/config/database.yml.sample 文件等待配置,对吧?[^1]

好,现在你要做的就是将这个文件名字改成database.yml并在其中插入合适的数据库链接信息,像这样:

让我们重新运行上面的merb-gen命令,不过这次我们还要再加上一些Post的默认属性:

如果一切正常,那么第一个模型类就有了。让我们使用rake对数据库进行迁移,来加上表:

现在,Slapp应该有一个有效可用的Post模型了。为了以防万一,我们将介绍RSpec[^2].

2.) RSpec

RSpec——你可能已经知道了——是Test::Unit的另一种替代测试方案。与RSpec交互主要通过rake任务和spec命令:

或者,更加详细的:

(重要:这里使用的rake任务来自Merb前沿代码,应该很快会发布为标准包。在那之前,可以使用以下同等任务:
rake specs 以及 rake spec TASK=controllers/pages)

两个任务会运行slapp/spec/*中的任何代码,简洁起见,我们这里会使用rake。

让我们再次运行测试:

如你所见,Merb已经为我们创建了一个默认的测试,但是他还不能通过:

打开: slapp/spec/models/post_spec.rb 看看这个测试在哪里:

将该spec改成实用的代码,比如:

该测试看上去好像不多,但是它确实可以验证Merb、RSpec、ActiveRecord和我们的数据库都已经安装成功并工作正常。

同时,如果我们在此运行新的spec,数据库又出现一个问题。确切地说,我们忘记了创建slapp_test 数据库并将slapp_development的结构复制过去。

重新运行spec:

这时成功在向我们问候:1 example, 0 failures,这一行表示所有的spec都通过了。更重要的是,我们遇到并克服了实际使用RSpec的第一个问题。

3.) 控制器

虽然现在从技术上说我们没有控制器也能启动Merb,但是基本上作不了什么。事实上就是什么都作不了,我们还是先来创建一个控制器:

如你所见,Merb的控制器的命名方式是模型名(或者资源等)的名字复数化,且不使用Controller后缀。

看一下slapp/app/controllers/posts.rb,你应该看到我们新的Posts控制器里有一个默认的#index动作。另外,Merb还应该创建了一个新的spec: slapp/spec/controllers/posts_spec.rb ,里面的内容应该类似于:

让我们编辑:slapp/spec/controllers/posts_spec.rb,更改内容为:

指定控制器是比较直观的,在上面的例子中,我们描述了Posts控制器(从dispatch_to中返回的)有一个#index动作并且它能被外部世界成功调用(即,它返回一个HTTP 20x 代码)[^3].

我们想再次运行spec,但是由于我们没有添加任何模型或视图测试,所以让我们执行更加明确的“仅控制器”的rake任务:

成功的测试结果:

漂亮。现在我们已经有了一个可以工作的控制器和一个有效的模型——我们只缺一些好的视图了。

4.) The View from Above

前面当我们创建Posts控制器时,Merb同时也为#index动作创建了一个草图。位于:slapp/app/views/posts/index.html.erb,你也许会看看里面有什么,然而,让我们先暂时忽略这个视图,并回到控制器。

slapp/app/controllers/posts.rb 中,我们执行一个简单的 ActiveRecord #find
并在#index动作中将结果存储为一个实例变量:

然后我们确认该动作仍然是可以调用的:

就和Rails(或者其他Web框架)中一样,在控制器中创建的实例变量可以在对应的视图中调用。

在本案中,我们可以回到:slapp/app/views/posts/index.html.erb 并将临时文本替换成显示@posts变量内容的代码。

为了能保持模块化,我们将使用Merb #partial[^4] 功能来达到这个目的。

slapp/app/views/posts/index.html.erb 的内容替换成:

不看HTML,调用#partial应该还是比较容易理解的——
我们想多次渲染slapp/app/views/shared/_post.html.erb 视图来呈现@posts的内容。

现在创建: shared/ 目录以及: _post.html.erb 文件:

并编辑 slapp/app/views/shared/_post.html.erb 的内容为:

上面调用: partial("/shared/post", :with => @posts) 会反复传递一个单个post对象给视图_post.html.erb并渲染。

5.) 启动Merb

现在我们有了模型、控制器、以及一个视图,让我们启动Merb:

你应该看到了一个普通的欢迎页,再转到:

这时你应该看到slapp/app/views/posts/index.html.erb的内容了。当然,由于我们还未创建任何帖子,所以应该看不到任何东西。

让我们使用交互Merb会话(其实就是一个在Merb应用的内容中启动的IRB)修正上面的问题:

使用典型的 ActiveRecord #create 方法,我们现在创建了一个Post。重新载入
Posts#index 页面:

我们应该成功地看到了新的帖子。

6.) 特殊的视图

现在我们已经实现了视图,也许他们不会经常更改,让我们先描述他们。

首先,创建目录和spec文件:

然后将一下代码放入slapp/spec/views/posts/index_spec.rb

当使用RSpec描述对象时,常常需要在运行测试的前后维护测试特定的内容。毫不奇怪,RSpec向我们提供了#before#after块来实现这个目的。

回到代码中:我们在#before块中首先做的是从我们的 Posts 创建@controller实例。下面我们使用fake_request助手[^5]来模拟HTTP请求。

回想一下我们的视图:

我们知道我们还需要一组帖子来进行测试,碰巧,这就是#before块的第二和第三行所做的事情:

@controller.instancevariableset(:@posts, @posts)

这里,我们仅仅将插入了一组记录并保存为@controller@posts实例变量。记住,调用Posts控制器的#index动作和我们之前所做的没有什么区别,除了我们使用了一个ActiveRecord的#find,而不是手工使用#new创建帖子:

前面的Posts控制器Our Posts controller from earlier:

最后,#before的第四和最后一行渲染了视图并将响应的主体(本案中是HTML)放入了@body 实例变量。

(本质上来说,我们是使用fake_request来“查看”Posts#index动作。)

现在,我们的控制器已经设置好了,同时视图也渲染了,我们就开始列出我们对视图中应该有什么的预期。[^6]

首先,我们描述HTML里应该最外面有一个div来放每一个单独的帖子的div:

下面,我们断定容器div确实包含着帖子的div:

然后,我们进入每个帖子的div来验证内容准确地匹配对应的Post:

最后,我们使用#after块来删除在#before块中创建的帖子:

尽管我们还没真正完成,我们先来验证一下整个应用:

7.) 表单创建

有了浏览帖子的能力之后,现在就可以实现同样重要的创建帖子的功能了。

打开slapp/app/views/posts/index.html.erb并在帖子列表下面添加该表单[^7]:

几乎不言自明,我们是要构建一个简单的表单,有一个文本输入框和一个提交按钮。

你应该已经注意到我们已经将表单设置为递交到Posts控制器的#create动作。我们需要实现这个动作,不过在我们继续之前,我们先快速描述一下这个表单。

编辑: slapp/spec/views/posts/index_spec.rb并在#after 块上面添加一下内容:

和前面一样,我们使用#match_selector 来断言表单、正文输入框以及提交按钮的存在。唯一不同的是我们使用了一个基于HTML属性的选择器[^8]form[@action=/posts/create]

Run the specs:

我们可以再次启动Merb来亲眼检验一下新的表单了。不过,因为我们已经使用了RSpec,这步不是非常必须的,我们可以立刻继续往下。

说道RSpec,这次,当我们在要去完成#create动作的时候,我们应该在写代码之前先写出该步骤的spec。

将以下内容复制到 slapp/spec/controllers/posts_spec.rb

这里,在#before块中,我们准备了一个只有一个:body键的@params表以便开始描述#create动作 。下面,我们列出了第一个预期:create动作在它成功创建一个帖子之后应该重定向到#index动作。

(这就是如果一个浏览器通过表单提交了某些信息,在我们的应用中能看到的情况。)

我们用一个lambda表达式检验一个帖子是不是被创建了,其中RSpec会调用两次:先执行一次,然后等相关的{...}块执行完之后再执行另一次。

这两次中,RSpec都会调用Post.count,如果两次调用返回不同的值,那么我们就能确信Post被创建了,那么这个块(该动作)就是成功的。

因为我们还没有编码#create,所以spec显然会失败:

切换到: slapp/app/controllers/posts.rb并再次复制以下内容:

现在我们定义了#create,现在回到spec文件,应该就可以通过了:

通过了这些spec,我们就有了一个可以正常工作的聊天墙了。

听起来是个好消息,我们也几乎就要完成了。我们还需要做得就是检验创建一个新的帖子需要一定的文本,否则则会出现一个异常。

8.) 收尾

现在,任何都可以点击 “Post Message!” 然后创建一个新的帖子。因为我们并不想让一堆空白的帖子占据聊天墙,所以我们应该在创建新帖子之前校验至少有一些文本被提交了。

因为本文是一个教程,我们不打算将所有东西都仔细进行合适的处理,所以这里我们直接使用ActiveRecord的validates_length_of过滤器。 ;-)

打开: slapp/spec/models/post_spec.rb 并观察我们现有的spec:

因为我们要校验正文文本的存在,这个spe已经不再有效,我们需要如下的内容来替代:

和平时一样,我们首先运行失败的spec来建立我们对于特定行为的预期:

然后实现上面提出的修正,在本案中,则是在slapp/app/models/post.rb中加入一样:

再次运行spec来检验我们的修正是否有效:

这时我们的模型完成了。让我们继续给Posts控制器添加一个spec——
我们需要描写当提交一个帖子没有包含正文的时候,我们没有真正去处理这个错误而已直接返回由我们的ORM抛出的异常。

编辑slapp/spec/controllers/posts_spec.rb并加入下面这个spec:

如你所见,我们仅仅是不带@params表来调用#create。这创建一个没有正文的空Post,这样就会导致ActiveRecord的校验失败,并抛出我们预期的RecordInvalid异常。

有了这个,我们的第一个聊天墙的版本就完成了。就和前面一样,你可以通过启动Merb并浏览Posts#index动作来试试程序:

最后的思考

从这里开始,你可能还有很多东西想添加,例如:分页、动态发布/更新、SPAM过滤器、文本格式化等等。

这些对于任何优秀的聊天程序都是很基本的,你都可以利用Merb来实现。

同时,别忘了浏览官方的项目首页看看有没有最新的更新、看看别人的版本甚至创建属于你自己的:

有任何问题/评价,请致电socialface@gmail.com或者在#merb找 Slurry

脚注

[^1]: The observant may have also just found their first “Merb” bug.. the
generator claimed to have made a “database.sample.yml” file, although the file
is really named “database.yml.sample”. :-)

[^2]: RSpec links in order of approximate handyness to the beginner:

[^3]: Merb RSpec controller matchers:

[^4]: Merb partials:

[^5]: Merb fake_request helper:

[^6]: Merb RSpec view matchers:

[^7]: Merb Form Helpers:

[^8]: Hpricot CSS Selectors:

发表评论

电子邮件地址不会被公开。 必填项已用*标注