Rails生成Ext Tree

在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腳本: 

create table nested_objects (
  id int(11) unsigned not null auto_increment,
  parent_id int(11),
  lft int(11),
  rgt int(11),
  name varchar(32),
  primary key (id)
);

 

Ruby Model:

class NestedObject < ActiveRecord::Base
  acts_as_nested_set
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:

class Category < ActiveRecord::Base
  acts_as_nested_set
end

6.下面在CategoriesController中加入index方法,讓它來轉到index.html頁面,並且爲EXT TREE來生成JSON數據:

class CategoriesController < ApplicationController
  def index(id = params[:node])
    respond_to do |format|
      format.html # render static index.html.erb
      format.json { render :json => Category.find_children(id) }
    end
  end
end

 index方法有一個參數id,用來接收一個樹的節點的id,我們就可以通過一個id來查找該節點的子節點。

7.實現CategoriesController中的find_children方法:


#首先先得到樹的根節點,再根據傳過來的id找到根的子節點
def self.find_children(start_id = nil)
    start_id.to_i == 0 ? root_nodes : find(start_id).direct_children
end
#如果parent_id爲空,則爲樹的根節點
def self.root_nodes
    find(:all, :conditions => 'parent_id IS NULL')
end

 

到這裏,已經實現了基本的樹形結構,但卻還有一個問題,如果是樹葉節點,既沒有子節點的節點,圖標應該顯示爲"-" ,不應該再能夠伸展了,Ext Tree中提供的示例中給出的JSON數據中有一個leaf的屬性,如果爲true,則爲樹葉節點,如果爲false,則爲樹枝節點,所以,我們還需要讓我們生成的JSON數據用來leaf來標識樹枝節點與樹葉節點,在Category.rb中添加如下代碼:

def leaf
    unknown? || children_count == 0
end

def to_json_with_leaf(options = {})
    self.to_json_without_leaf(options.merge(:methods => :leaf))
end

alias_method_chain :to_json, :leaf

 對於alias_method_chain,需要先說一下Ruby中的alias_method方法,在Ruby中有這樣的用法:

 

alias_method :old_method_name :new_method_name

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

在Ruby中,可以使用方法鏈的手段來實現mix-in,如果想要用new_method來override old_method方法,就可以這樣使用:

alias_method :old_method_name :new_method_name
alias_method :new_method_name :old_method_name
 而在Rails中,提供了一個更強大的方法:alias_method_chain。

 

下面是index.html.erb文件:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8"/>
    <title>Rails Ext Tree</title>
    <%= stylesheet_link_tag "../ext/resources/css/ext-all.css" %>
    <%= javascript_include_tag :defaults %>
    <%= javascript_include_tag "../ext/adapter/prototype/ext-prototype-adapter.js" %>
    <%= javascript_include_tag "../ext/ext-all.js" %>
</head>
<body>
<div id="category-tree" style="padding:20px"></div>
<% javascript_tag do -%>
    Ext.onReady(function(){     
        root = new Ext.tree.AsyncTreeNode({
        text: 'Invisible Root',
        id:'0'
    });
   
    new Ext.tree.TreePanel({
        loader: new Ext.tree.TreeLoader({
            url:'/categories',
            requestMethod:'GET',
            baseParams:{format:'json'}
        }),
        renderTo:'category-tree',
        root: root,
        rootVisible:false
    });
    
    root.expand();
    });
<% end -%>
</body>
</html>
 

添加測試數據:

root = Category.create(:text => 'Root')

root.add_child(c1 = Category.create(:text => 'Child 1'))
c1.add_child(Category.create(:text => 'Child 1.1'))
c1.add_child(Category.create(:text => 'Child 1.2'))

root.add_child(c2 = Category.create(:text => 'Child 2'))
c2.add_child(c21 = Category.create(:text => 'Child 2.1'))
c2.add_child(c21 = Category.create(:text => 'Child 2.2'))
 

最後的顯示效果:

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章