自定义 Action
我们已经知道,在 /config/routes.rb
里定义的路由,会自动生成对资源的CRUD的操作。但是我们如何处理那些并不是CRUD的操作?下面我们就用一个例子来说明这一点。例如我们在
ProjectController里有一个close的方法。这个close并不是真正的删除一个资源,而只是把给这个资源设置一个标志:表示这个
资源被关闭了。
首先修改一下数据库:
> ruby script/generate migration add_closed_to_projects
exists db/migrate
create db/migrate/003_add_closed_to_projects.rb
Listing 1.16: ontrack/db/migrate/003 add closed to projects.rb
class AddClosedToProjects < ActiveRecord::Migration
def self.up
add_column :projects, :closed, :boolean, :default => false
end
def self.down
remove_column :projects, :closed
end
end
rake db:migrate
现在,我们在IteratinController的index.rhtml上创建一个 close 的链接。
Listing 1.17: ontrack/app/views/projects/index.rhtml
<% for project in @projects %>
<tr id="project_<%= project.id %>">
<td><%=h project.name %></td>
<td><%= link_to "Show", project_path(project) %></td>
...
<td><%= link_to "Close", <WHICH_HELPER?> %></td>
</tr>
<% end %>
现在有2个问题摆在我们面前:
1.使用 http 协议的哪个动作来发送这个请求呢?
2.对于这个链接,该如何生成那些 helper方法呢?
因为这个 close 动作并不是CRUD中的任何一个,所以Rails 也不知道该用http的哪个来做这个事情。不过既然 close 也是
update 中的一种,所以应该使用post来发送这个请求。我们还是得在 /config/routes.rb
里定义这个路由,当然定义完路由之后,就会有相应的path和url的helper方法了。因为这个close的操作,仍然是针对projects
这个资源的,所以,我们可以在定义路由的时候,使用一个名字叫“member”的hashmap,这个hashmap
的key,就是自定义action的名字,hashmap的value,就是所使用的http的动作。
map.resources :projects, :member => { :close => :post }
hashmap 的value可以使用 :get, :put, :post, :delete, :any。如果使用了:any,那么可以用http的任何动作来发送这个请求。
定义完这个路由后,我们就可以使用helper方法了:
<td><%= link_to "Close", close_project_path(project) %></td>
因为我们定义的是“:member => { :close => :post
}”,所以,这个请求只能以post的方式来发送,如果使用其它方式如“get”,那么请求就是无效的。为了安全起见,我们还是把它改成用按钮的方式来发
送,幸运的是我们可以使用Rails 提供的button_to 来做这件事情:
<td><%= button_to "Close", close_project_path(project) %></td>
=>
<td>
<form method="post" action="/projects/1;close" class="button-to">
<div><input type="submit" value="Close" /></div>
</form>
</td>
现在我们要做的就是写完 ProjectController中的 close 方法:
Listing 1.18: ontrack/app/controllers/projects controller.rb
def close
respond_to do |format|
if Project.find(params[:id]).update_attribute(:closed, true)
flash[:notice] = "Project was successfully closed."
format.html { redirect_to projects_path }
format.xml { head :ok }
else
flash[:notice] = "Error while closing project."
format.html { redirect_to projects_path }
format.xml { head 500 }
end
end
end
除了“:member”,我们还可以使用“:collection”,“:new”。
“:collection”的用途是:所操作的资源不是一个,而是很多个。下面是一个用“:collection”方式得到一个资源的列表的例子:
map.resources :projects, :collection => { :rss => :get }
--> GET /projects;rss (maps onto the #rss action)
所以,有的时候,“:member”更多的是更新一个资源,而“:collection”是得到一堆资源。
对于“:new”,一般用于那些还没有被保存的资源:
map.resources :projects, :new => { :validate => :post }
--> POST /projects/new;validate (maps onto the #validate action)
我们是否仍然“DRY”(Don’t Repeat Yourself)?
我们是否为了“DRY”原则?似乎是这样的:我们不仅在controller里定义了action,同时在 /config/routes.rb 里也定义了一遍。
作为替换REST风格的调用的方式,您可以用传统的方式来调用一个方法:
<%= link_to "Close", :action => "close", :id => project %>
但是别忘了,即使用传统的方式,你也得在/config/routes.rb里定义一个路由:“map.connect ’:controller/:action/:id’”。
自定义信息格式
目前 respond_to 可以返回如下的信息格式:
respond_to do |wants|
wants.text
wants.html
wants.js
wants.ics
wants.xml
wants.rss
wants.atom
wants.yaml
end
你可以通过增加新的MIME类型的信息来扩展这个功能。假设您已经开发了一个“PIM”应用系统,现在你希望把地址信息用”vcard” 格式来传送。要实现这一共能,首先你需要注册新的信息格式在
/config/environment.rb。
Mime::Type.register "application/vcard", :vcard然后,我们来修改一下show action,使得返回的信息以vcard 的格式来传送。
def show
@address = Address.find(params[:id])
respond_to do |format|
format.vcard { render :xml => @address.to_vcard }
...
end
end
这个 to_vcard 方法不是 ActiveRecord 的标准方法,所以必须按照
vcard的标准来实现(RFC2426)。如果实现正确的话,那么通过下面的URL,就可以得到正确的信
息:“http://localhost:3000/addresses/1.vcard”。
在REST 中使用AJAX
在REST风格的系统中使用AJAX?非常简单,可以说这一小节没什么新鲜的玩意要学习。您还是使用以前所使用的 remote
系列的helper 方法,只不过传递的参数需要改变,现在使用 path helper 方法,而不是以前所使用的
contoller,action 。下面的例子会让您更清晰的明白这一点:
link_to_remote "Destroy", :url => project_path(project),
:method => :delete
=>
<a href="#" onclick="new Ajax.Request("/projects/1",
{asynchronous:true, evalScripts:true, method:"delete"});
return false;">Async Destroy</a>
给您提醒一下:千万千万记得导入相应的ajax javascript 文件,不然当您的ajax无效,而气得把键盘砸坏的时候,我们就无能为力了。导入相应的javascript 文件相当的简单:
Listing 1.19: ontrack/app/views/layouts/projects.rhtml
<head>
<%= javascript_include_tag :defaults %>
...
</head>
这个“Destroy”链接将会调用ProjectsController的destroy方法。从逻辑上来说现在一切正常:用户点这个链接,系统删除相
应的资源。不过我们还是漏了一点:在 respond_to 中,我们应该增加新的返回类新,也就是javascript类型。
Listing 1.20: ontrack/app/controllers/projects controller.rb
def destroy
@project = Project.find(params[:id])
@project.destroy
respond_to do |format|
format.html { redirect_to projects_url }
format.js # default template destroy.rjs
format.xml { head :ok }
end
end
可以看出来,唯一的改变就是增加了“format.js”。因为这个“format.js”并不是一个要被执行的代码块,所以,Rails 会按照标准显示destroy.rjs。
Listing 1.21: ontrack/app/views/projects/destroy.rjs
page.remove "project_#{@project.id
}"
这个 rjs 文件从当前的浏览页面中删除了 “project_ID”这个DOM元素,为了让这个删除起到效果,我们就需要在显示 project 上进行修改:
Listing 1.22: ontrack/app/views/projects/index.rhtml
...
<% for project in @projects %>
<tr id="project_<%= project.id %>">
这是一个遵循DRY原则和减少对当前系统的修改的一个好例子!也体现了REST的优势,只需要在controller里增加一行,就可以处理javascript请求了。
同时也告诉了我们一个REST编程的原则:在 respond_to 外实现逻辑处理,能够极大地降低重复的代码。
测试
不管开发REST风格的应用是多么的让我们激动,我们也不能忘记最重要的一个朋友:测试!
之前我们写了那么多代码,但是一次单元测试测试都没运行过!下面我们来运行一下吧!
> rake
...
Started
EEEEEEE.......
好消息是,所有的单元测试和功能测试都可以运行。坏消息是,关于IterationsController的7个功能测试,全部失败!
如果测试用例抛出异常,那么很明显的—这里存在一些错误。我们遇到的错误也很明显:所有的IterationsController的测试用例都是
scaffold 来生成的,并没有一个“父”资源的关联—还记得吗,我们已经让iterations 资源成为了projects 资源的“子”资源。
为了让我们的测试用例通过,我们必须给每个测试方法都增加project_id。例如:
Listing 1.23: ontrack/test/functional/iterations controller test.rb
def test_should_get_edit
get :edit, :id => 1, :project_id => projects(:one)
assert_response :success
end
当然了,你需要加载必要的fixtures:
fixtures :iterations, :projects
改完全部的测试用例以后,我们发现还是有2个测试用例无法通过:
test_should_create_iteration
test_should_update_iteration
失败的代码来自这行“assert_redirected_to iteration_path(assigns(:iteration))”。
错误是非常显然的:我们已经知道iteration_path的第一个参数应该是project id。我们同样需要修改一下:
assert_redirected_to iteration_path(projects(:one), assigns(:iteration))
另外,在使用 redirect 断言的时候,path helper 方法的使用,是REST和非REST风格应用的唯一区别。
REST 风格的客户端:ActiveResource
我们总是把 ActiveResource 和 REST一起提及。ActiveResource 是一个Rails 的库,用来开发基于REST的WEB服务客户端。这种基于REST的客户端,也是适用 http 的4个标准的动作来和服务器通信。
ActiveResource 并不是Rails 1.2 的一部分,但是您可以使用svn 从网站下载它的代码:
> cd ontrack/vendor
> mv rails rails-1.2
> svn co http://dev.rubyonrails.org/svn/rails/trunk
rails
ActiveResource把客户端资源抽象成一个类,这个类继承自ActiveResource::Base。例如通过下面的例子,我们来调用服务器上的project 资源:
require "activeresource/lib/active_resource"
class Project < ActiveResource::Base
self.site = "http://localhost:3000
"
end
可以看到,我们导入了ActiveResource 的库,然后,服务器的地址赋给了类的变量 site。这个 Project 类,把服务器上的资源抽象成了一个客户端的类,这就让开发人员觉得他们就好像操作一个ActiveRecord 一样。
例如,我们用一个project id 和 find 方法去请求服务器上的一个资源:
wunderloop = Project.find 1
puts wunderloop.name
这个 find 方法会执行标准的GET动作:GET /projects/1.xml
然后服务器返回xml格式的信息。客户端把xml信息转化成一个ActiveResource对象 wunderloop,就好像一个ActiveRecord对象,可以得到和改变它的任何属性。那么我们如何去更新一个资源呢?
wunderloop.name = "Wunderloop Connect"
wunderloop.save
save 方法会是用put 动作向服务器传递信息。
PUT /projects/1.xml
刷新一下你的浏览器看看,那条记录肯定被改变了。和 find,save一样简单,创建一个新的资源也是非常方便:
bellybutton = Project.new(:name => "Bellybutton")
bellybutton.save
新的资源将会以post方式传递给服务器,并且保存到数据库里。
POST /projects.xml
刷新浏览器,会看到新建立的资源。最后,我们来看一下删除一个资源。
bellybutton.destroy
destroy方法将会以DELETE方式发送给服务器,并且删除这个资源。
DELETE /projects/2.xml
ActiveResource 使用http的4个动作来和服务器交互。对于REST的资源,它提供了非常好的抽象,此外,在ActiveRecord中的许多方法,在ActiveResource中仍然找得到。例如查找一个资源的全部记录:
Project.find(:all).each do |p|
puts p.name
end
相信使用ActiveResource可以开发出很好的松耦合的系统,我们不如马上去下载ActiveResource的代码,亲自体验一下吧!
大结局
这个世界并不是非得需要REST。有很多的解决方案可以考虑,并且可以很容易实现。大概更多的时候,是您可能现在正处于某个项目的中期,这时,您发
现了Rails这个新特性。我想如果您此时就开发一个单独的模块,并且使用REST的风格,是毫无问题的。如果您要准备开始一个全新的项目,那么不妨考虑
一下使用
REST,理由十分明显:清晰的架构,更少的代码,多个客户端的支持。
终于结束了,有关rails2.0 rest风格的新特性建议看一篇文章叫"Rolling with Rails 2.0 - The First Full Tutorial
"。http://www.iteye.com/topic/162536
相关推荐
### 应用Rails进行REST开发 #### 1.1 什么是REST? REST(Representational State Transfer),这是一种由Roy Fielding在他的博士论文中提出的架构风格。REST的核心思想是通过标准HTTP协议中的GET、POST、PUT、...
《Web开发敏捷之道:应用Rails进行敏捷Web开发(第3版)》:Ruby on Rails是一个全套的MVC web框架,它能帮你开发高质量又美观的web应用,而且开发速度快得出乎你想象。你只须集中精力于应用程序本身,Rails就会帮你...
在本篇内容中,我们将深入探讨如何利用Ruby on Rails(简称Rails)这一强大的Web应用程序框架来构建可伸缩且易于维护的RESTful API。Rails以其简洁优雅的语法、高效的开发速度以及良好的社区支持而闻名,这使得它...
### 应用Rails进行敏捷Web开发:探索框架的精髓与敏捷的魅力 Rails,全称Ruby on Rails,是由David Heinemeier Hansson创建的一种基于Ruby语言的开源Web开发框架。自诞生以来,Rails以其独特的魅力迅速席卷了Web...
《应用Rails进行敏捷Web开发 第三版》是关于Ruby on Rails框架的一本经典教程,针对的是Web开发领域的专业人士。Rails是一种流行的开源Web应用程序框架,它基于Ruby编程语言,旨在简化和加速Web应用的开发过程,强调...
Rails,全称Ruby on Rails,是一款基于Ruby语言的开源Web应用程序框架,遵循MVC(Model-View-Controller)架构模式,特别适合快速开发RESTful(Representational State Transfer,表述性状态转移)风格的Web应用。...
《应用Rails进行敏捷Web开发(第2版)》是一本深度探讨如何利用Ruby on Rails框架进行高效、敏捷的Web应用程序开发的专业书籍。Rails是Ruby语言的一个开源Web开发框架,它倡导DRY(Don't Repeat Yourself)原则,强调...
标题与描述均指向了"使用Rails编写REST风格的Web应用"这一主题,这是一份深入探讨如何运用Ruby on Rails框架来构建遵循REST(Representational State Transfer)架构风格的Web应用程序的指南。REST作为一种架构风格...
相比第2版中的内容,Rails 2增加了REST、资源、轻量级web service等新特性。本书涵盖了这些全新的内容,因此能更好地体现出Rails框架的发展现状。 整体而言,全书既有直观的实例,又有深入的分析,同时还涵盖了web...
随着Ruby的经验不断成功,开发人员开始寻求把他们的Ruby应用程序与用其他语言编写的应用程序集成。Rails对Web服务提供了优秀的支持。本文介绍Rails中的Web服务,重点放在一个名为Representational State Transfer ...
Rails(Ruby on Rails)是一个采用Ruby语言编写的开源Web应用框架,它遵循模型-视图-控制器(MVC)的架构模式,设计用来快速开发数据库驱动的动态网页。随着Rails版本的更新迭代,此书聚焦于一个特定的版本,帮助...
- **启动和应用设置**:这部分介绍如何配置Rails项目的启动过程以及如何设置各种环境变量,包括开发、测试和生产环境的差异配置。 - **不同模式下的配置**: - **开发模式**:通常包含更多的调试信息和详细的错误...
它允许Rails应用作为客户端,通过HTTP与遵循REST原则的远程资源进行交互,从而实现数据的获取和更新。 5. **rake-0.8.1.gem**:Rake是Ruby社区广泛使用的构建工具,类似于Java的Ant或Python的setup.py。它允许...
Rails鼓励使用REST(Representational State Transfer)架构风格来构建Web应用。RESTful路由允许开发者通过HTTP动词(GET、POST、PUT、DELETE等)来定义资源的操作,使URL更加语义化,提高可读性和可维护性。 **4. ...
Rails::API 是 Rails 的精简版本,针对不需要使用完整...也可以用来编写在 Web 应用和客户端之间进行数据共享的后端程序,允许开发者创建接受 JSON 格式数据并以传统 RoR 应用方式存储的REST端点。 标签:rails
Rails是一个基于Ruby编程语言的开源Web应用程序框架,遵循MVC(Model-View-Controller)架构模式,致力于实现“约定优于配置”(Convention over Configuration, CoC)和“Don't Repeat Yourself”(DRY)的原则,极...