rails acts_as_nested_set

来源:互联网 发布:云计算相关股票有哪些 编辑:程序博客网 时间:2024/06/09 14:18

rails acts_as_nested_set


在Rails中使用has_one 、has_many 、belongs_to 和 has_and_belongs_to_may 来声明关系型数据库中的一对一,一对多和多对多的关系,但当想以树形的数据结构来表示分类的时候,这些基本的关联功能并不够,Rails在has_XXX关系的基础上,提供了acts as的扩展功能,如acts_as_list 、acts_as_tree 、 acts_as_nested_set。acts_as_tree就提供树状的结构来组织记录。(不知道为什么Rails2.0以后会取消掉,需要通过插件的方式来安装)

 

acts_as_nested_set的官方解释:

A Nested Set is similar to a tree from ActsAsTree. However the nested set allows building of the entire hierarchy at once instead of querying each nodes children, and their children. When destroying an object, a before_destroy trigger prunes the rest of the branch of object under the current object.

上面是引用自rubyonrails.org上的对于acts_as_nested_set的描述,并提供了一个简单的示例:

 

SQL脚本: 

Sql代码  收藏代码
  1. create table nested_objects (  
  2.   id int(11) unsigned not null auto_increment,  
  3.   parent_id int(11),  
  4.   lft int(11),  
  5.   rgt int(11),  
  6.   name varchar(32),  
  7.   primary key (id)  
  8. );  

 

Ruby Model:

Ruby代码  收藏代码
  1. class NestedObject < ActiveRecord::Base  
  2.   acts_as_nested_set  
  3. end  

 

acts_as_nested_set提供的方法:

  • root?() – 是否是根对象
  • child?() – 是否是子对象(拥有父对象)
  • unknown?() – 不知道该对象的状态(既不是根对象,也不是子对象)
  • add_child(child_object) – 为根对象添加一个子对象(如果child_object是一个根对象的话,则添加失败)
  • children_count() – 根对象的所有子对象的个数
  • full_set() – 重新找到所有对象
  • all_children() – 根对象的所有子对象
  • direct_children() –根对象的直接子对象

下面就使用acts_as_nested_set来生成一个Ext的Tree。

比如生成如下的树:

root    |_ Child 1    |  |_ Child 1.1    |  |_ Child 1.2    |_ Child 2       |_ Child 2.1       |_ Child 2.2

先来看一下对上面的树的一个图形化的解释:

 

这图还是比较清除的,请理解横线中的1到14这些数字,对应这个树,我们可能会有下面的数据:

 

这个也就是SQL脚本中的的lft和rgt的解释

 

 

1.创建Rails工程:

    rails ExtTree

2.安装act_as_nested_set:

    ruby script/plugin install acts_as_nested_set

3.下载ext,解压下载后的压缩包并拷贝到ExtTree工程的public目录(public/ext)

 

4.创建模型对象:

    ruby script/generate resource Category parent_id:integer lft:integer rgt:integer text:string

5.给模型对象Category加入acts_as_nested_set:

Ruby代码  收藏代码
  1. class Category < ActiveRecord::Base  
  2.   acts_as_nested_set  
  3. end  

6.下面在CategoriesController中加入index方法,让它来转到index.html页面,并且为EXT TREE来生成JSON数据:

Ruby代码  收藏代码
  1. class CategoriesController < ApplicationController  
  2.   def index(id = params[:node])  
  3.     respond_to do |format|  
  4.       format.html # render static index.html.erb  
  5.       format.json { render :json => Category.find_children(id) }  
  6.     end  
  7.   end  
  8. end  

 index方法有一个参数id,用来接收一个树的节点的id,我们就可以通过一个id来查找该节点的子节点。

7.实现CategoriesController中的find_children方法:


Ruby代码  收藏代码
  1. #首先先得到树的根节点,再根据传过来的id找到根的子节点  
  2. def self.find_children(start_id = nil)  
  3.     start_id.to_i == 0 ? root_nodes : find(start_id).direct_children  
  4. end  
  5. #如果parent_id为空,则为树的根节点  
  6. def self.root_nodes  
  7.     find(:all:conditions => 'parent_id IS NULL')  
  8. end  

 

到这里,已经实现了基本的树形结构,但却还有一个问题,如果是树叶节点,既没有子节点的节点,图标应该显示为"-" ,不应该再能够伸展了,Ext Tree中提供的示例中给出的JSON数据中有一个leaf的属性,如果为true,则为树叶节点,如果为false,则为树枝节点,所以,我们还需要让我们生成的JSON数据用来leaf来标识树枝节点与树叶节点,在Category.rb中添加如下代码:

Ruby代码  收藏代码
  1. def leaf  
  2.     unknown? || children_count == 0  
  3. end  
  4.   
  5. def to_json_with_leaf(options = {})  
  6.     self.to_json_without_leaf(options.merge(:methods => :leaf))  
  7. end  
  8.   
  9. alias_method_chain :to_json:leaf  

 对于alias_method_chain,需要先说一下Ruby中的alias_method方法,在Ruby中有这样的用法:

 

Ruby代码  收藏代码
  1. alias_method :old_method_name :new_method_name  

 它同alias很类似,但只能用法方法。

在Ruby中,可以使用方法链的手段来实现mix-in,如果想要用new_method来override old_method方法,就可以这样使用:

Ruby代码  收藏代码
  1. alias_method :old_method_name :new_method_name  
  2. alias_method :new_method_name :old_method_name  
 而在Rails中,提供了一个更强大的方法:alias_method_chain。

 

下面是index.html.erb文件:

Html代码  收藏代码
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"  
  2.         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
  3. <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">  
  4. <head>  
  5.     <meta http-equiv="content-type" content="text/html;charset=UTF-8"/>  
  6.     <title>Rails Ext Tree</title>  
  7.     <%= stylesheet_link_tag "../ext/resources/css/ext-all.css" %>  
  8.     <%= javascript_include_tag :defaults %>  
  9.     <%= javascript_include_tag "../ext/adapter/prototype/ext-prototype-adapter.js" %>  
  10.     <%= javascript_include_tag "../ext/ext-all.js" %>  
  11. </head>  
  12. <body>  
  13. <div id="category-tree" style="padding:20px"></div>  
  14. <% javascript_tag do -%>  
  15.     Ext.onReady(function(){       
  16.         root = new Ext.tree.AsyncTreeNode({  
  17.         text: 'Invisible Root',  
  18.         id:'0'  
  19.     });  
  20.      
  21.     new Ext.tree.TreePanel({  
  22.         loader: new Ext.tree.TreeLoader({  
  23.             url:'/categories',  
  24.             requestMethod:'GET',  
  25.             baseParams:{format:'json'}  
  26.         }),  
  27.         renderTo:'category-tree',  
  28.         root: root,  
  29.         rootVisible:false  
  30.     });  
  31.       
  32.     root.expand();  
  33.     });  
  34. <% end -%>  
  35. </body>  
  36. </html>  
 

添加测试数据:

Ruby代码  收藏代码
  1. root = Category.create(:text => 'Root')  
  2.   
  3. root.add_child(c1 = Category.create(:text => 'Child 1'))  
  4. c1.add_child(Category.create(:text => 'Child 1.1'))  
  5. c1.add_child(Category.create(:text => 'Child 1.2'))  
  6.   
  7. root.add_child(c2 = Category.create(:text => 'Child 2'))  
  8. c2.add_child(c21 = Category.create(:text => 'Child 2.1'))  
  9. c2.add_child(c21 = Category.create(:text => 'Child 2.2'))  
 

最后的显示效果:

 

转自:http://www.iteye.com/topic/177501
0 0
原创粉丝点击