对于Rails web应用,涉及到最频繁的操作就是增、删、查、改操作,因此为了满足用户体验,批量删除(或更新)操作是必不可少。
在Rails自动生成的模板中,每个控制器(Controller)都包含7个基本Action,即index、show、new、create、edit、update、destroy,每个Action都只能处理一个对象,而批量操作必然要求能够处理多个对象,为此需要自定义一个批量操作的Action,下面就介绍如何在Rails中实现批量删除功能。
首先,客户要求批量删除功能的效果,如下图所示:
上图首先以表格的方式列出来库表(departs)里的所以记录(表格每一行,就代表库表的一条记录),然后每一行的第一列设置了一个单选框,用于标记是否选中此行,最后通过 “删除选中”链接,即可完成批量删除被选中记录功能,而且还通过JS实现了选中全部记录的功能。
要实现上面的效果,本人采用的一个解决方案,步骤如下:
①、通过 JS文件实现,“全选”和“反选”功能,js代码如下:
/*全选*/
function checkall (s,k){
var a = document.getElementsByTagName('input');
var n = a.length;
for (var i=0; i<n; i++){
if((a[i].type == "checkbox") && ( a[i].name.substr(0,k-1)==s )){
a[i].checked = true;
}
}
}
/*反选*/
function uncheck (s,k){
var a = document.getElementsByTagName('input');
var n = a.length;
for (var i=0; i<n; i++){
if((a[i].type == "checkbox") && (a[i].name.substr(0,k-1)==s) ){
if(a[i].checked == true){
a[i].checked = false;
}
else{ a[i].checked = true; }
}
}
}
function doall (s,k,n ){
if( k ){ checkall (s,n ) }
else{ uncheck (s,n ) }
}
而在视图中首先是设计每一行的checkbox的id,然后在最后一个代表“全选”的checkbox中调用上面代码的doall 方法,由参数的不同实现,全选和反选。代码片段如下:每一行的checkbox:
<td width="5%" align='left' height='30' valign='middle' >
<%= check_box_tag ' depart_ ' + depart.id .to_s ,'yes',false %>
</td>
生成的html代码就是如下格式:
<input id ="depart_ 7198 " type ="checkbox " value ="yes " name ="depart_7198 " />
最后代表“全选”的checkbox:
<input id="all" type="checkbox" οnclick="doall ( ' depart ' ,this.checked,7 );" /> 全选
其中,根据需求要选中的是所有 id 类似于"depart_ 7198 "的 checkbox,但是后面的 7198是 变化的为此应将所有id含有 depart_ 的 checkbox都选择,那么就需要进行配备即JS中的 a[i].name.substr(0,k-1)==s) 而7 就是字符串“ depart_ ”的长度
②、实现 “删除选中”链接的功能。
通过分析Rails模板自带的删除(destroy)Action,以及视图中实现“删除”链接的代码:<%= link_to '删除', depart, :confirm => '是否确定?', :method => :delete %>,可知其实使用了一个变量 :method,用它来表示 Http的DELETE动作。因为浏览器不支持DELETE动作,所以,Rails 会生成一些javascript来解决这个问题:例如下面的代码
link_to ' 删除 ' ,depart,:confirm => ' 是否确定? ' , :method => :delete
会自动生成相应的javascript代码,如下:
<a href="/departs/1"
οnclick="var f = document.createElement( ' form ' );
f.style.display = ' none ' ; this.parentNode.appendChild(f);
f.method = ' POST ' ; f.action = this.href;
var m = document.createElement( ' input ' );
m.setAttribute( ' type ' , ' hidden ' );
m.setAttribute( ' name ' , ' _method ' );
m.setAttribute( ' value ' , ’delete’);
f.appendChild(m);f.submit(); return false;">删除</a>
这段javascript代码会生成一个form,把 Http 的DELETE动作放在隐藏变量里传递给服务器,然后,Rails 会判断这个变量,决定是否去调用Controller的destroy 方法。
那么仿照这段javascript代码,并加以适当处理,使得其中的form,包含多个记录,再设置链接的地址,使其能提交给事先在Controller里自定义好的批量删除方法:destroy_selected,那么就能够实现批量删除的功能。
由于这部分代码是使用 link_to 这个Helper 方法来构造一个链接,那么我们要实现的批量删除链接,就应该是重载这个link_to方法,但是重载link_to方法可能影响到其它链接的生成,为此需要自定义一个Helper方法:destroy_selected (action_name,paramsname,obj_id,url),即在application_helper.rb中添加该方法,代码如下:
def destroy_selected ( action_name,paramsname,obj_id,url )
"<a οnclick=\"if (confirm('是否确定?')){
var f = document.createElement('form');
f.style.display = 'none';
this.parentNode.appendChild(f);
f.method = 'POST';
var m = document.createElement('input');
m.setAttribute('type', 'hidden');
m.setAttribute('name', '_method');
m.setAttribute('value', '#{action_name}');
f.appendChild(m);
var s = document.createElement('input');
s.setAttribute('type', 'hidden');
s.setAttribute('name', ' authenticity_token ');
s.setAttribute('value', authenticity_token);
f.appendChild(s);
var a = document.getElementsByTagName('input');
var n = a.length;
ii = 0
for (var i=0;i<n;i++){
if((a[i].type == 'checkbox' )&& (a[i].checked == true ) &&
(a[i].id != 'all')){
var s = document.createElement('input');
s.setAttribute('type', 'hidden');
s.setAttribute('name', '#{paramsname}');
s.setAttribute('value', a[i].name.slice(#{obj_id}) );
f.appendChild(s);
ii = ii + 1
}
}
if (ii > 0){ f.action = this.href; f.submit(); }
};
return false;\" href=\"" + url_for(url).to_s+"\" >删除选中</a> "
end
上面的代码主要处理了4个事件,即粗体部分:下面简要分析
1)、 m.setAttribute('value', '#{action_name}'); 根据传入的参数确
定链接要提交的目标action.
2)、 s.setAttribute('name', ' authenticity_token '); 由于Rails的安
全机制,需要为该方法传入 authenticity_token。
3)、 s.setAttribute('name', '#{paramsname}');
s.setAttribute('value', a[i].name.slice(#{obj_id}) );
为form添加被选择的需要删除的记录,然后以参数形式,传给action。
4)、 href=\"" + url_for(url).to_s+"\", 设置链接地址。
根据上面的方法,在视图中的 “删除选中”链接代码就是 :
<%=destroy_selected ('destroy_selected ','depart_id[] ',7 , [:destroy_selected,:departs]) %>
接下来就是,DepartsController里的destroy_selected方法了:
def destroy_selected
respond_to do |format|
if not params[:depart_id] .nil?
begin
Depart.transaction do
params[:depart_id].each do |did|
if did != ""
@depart = Departs.find(did)
@depart.destroy
end
end
end
flash[:notice] = "删除数据成功!"
rescue
flash[:notice] = "删除数据失败!"
end
else
flash[:notice] = '请选择要删除的部门!'
end
format.html { redirect_to(departs_url) }
format.xml { head :ok }
end
end
首先,根据前面的设计,“删除选择”链接提交时会通过form传出参数(数组):depart_id[],然后destroy_selected方法遍历depart_id[],再通过@depart = Departs.find(id);@depart.destroy 来逐个删除;为了保证数据安全和同步,代码中还使用了数据库的事务机制:Depart.transaction do,这样只要其中一条记录删除失败,那么所有记录都不会被删除。
最后,还要在路由中配置这个action的url ,即:
map.resources :departs,:collection => { :destroy_selected => :post}
这样,只要为数据库的每一张表对应的Controller都设计一个action——destroy_selected,那么就可以轻松地实现资源的批量删除工作。而其它批量操作(批量更新、排序....)都可以仿照上面的方法,加以实现。