自定义 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’”。