天天看点

acts as list/acts as tree

[color=red]Acts As List[/color]

在子表中使用acts_as_list,便能从父表的“视图”中得到像子表的行为。父表将能够

遍历子表,在列表内移动子表,或从列表内移除子表。

通过给每个子表一个位置数来实现列表。这意味着子表必须有个列来记录此位置。如果

我们称它为列position,Rails 会自动使用它。如果不这么称呼它,我们需要告诉Rails 它

的名字。对于我们的例子,跟随父表之后我们将创建一个新的子表(叫children)。

create table parents (

id int not null auto_increment,

primary key (id)

);

create table children (

id int not null auto_increment,

parent_id int not null,

name varchar(20),

position int,

constraint fk_parent foreign key (parent_id) references parents(id),

primary key (id)

);

接着,我们将创建“模型”类。注意在Parent 类中,我们基于当前的position 列的值

来确定我们children 的位置。这确保从数据库内获得的数组在正确的定单清单内。

class Parent < ActiveRecord::Base

has_many :children, :order => :position

end

class Child < ActiveRecord::Base

belongs_to :parent

acts_as_list :scope => :parent_id

end

在Child 类中,我们用传统的belongs_to 声明来建立与父表的连接。我们也有个

acts_as_list 声明。我们用一个:scope 选项来限制它,指定列表的每个父表。没有这范围操

作符,则对于children 表内的所有条目则只有一个全局的列表。

现在我们设一些测试用数据:我们为一个特定的父项创建四个子项,称为One,Two,Three

和Four。

parent = Parent.new

%w{ One Two Three Four}.each do |name|

parent.children.create(:name => name)

end

parent.save

我们要写个简单的方法来检查列表的内容。

def display_children(parent)

puts parent.children.map {|child| child.name }.join(", ")

end

在完成我们列表的测试后。注释显示了由display_children()产生的输出。

display_children(parent) #=> One, Two, Three, Four

puts parent.children[0].first? #=> true

two = parent.children[1]

puts two.lower_item.name #=> Three

puts two.higher_item.name #=> One

parent.children[0].move_lower

parent.reload

display_children(parent) #=> Two, One, Three, Four

parent.children[2].move_to_top

parent.reload

display_children(parent) #=> Three, Two, One, Four

parent.children[2].destroy

parent.reload

display_children(parent) #=> Three, Two, Four

注意我们是如何在父项中调用reload()的。各种move_method 更新数据库内子项,但因

为它们直接操作子项,父项将不会立即知道这一更改。

list 库使用术语lower 和higher 来引用元素的相对位置。Higher 意味着离列表前部近,

lower 离尾部近。顶部也与前部是一个意思,底部与尾部是一个意思。方法move_higher( ),

move_lower( ), move_to_bottom( ), 和move_to_top( ) 在list 内循环移动一个特定项

目,并自动调整与其它元素的位置。

higher_item( ) 和lower_item( ) 返回相对于当前元素的下一个和前一个元素的位

置, first?( ) 和last?( ) 在list 内的当前元素位于前部或尾部时返回true 。

新近创建的子项被自动地添加到列表的尾部。当一个子行被删除时,list 内的子项会被

移除并用gap 填充。

[color=red]Acts As Tree[/color]

“活动记录”提供对组织一个表内的行到一个层次,或树,结构中的支持。这对创建具

有子项的项目很有用处,并且这些子项还可以有它自己的子项。分类目录列表通常有这种结

构,如对许可证,目录清单等等的描述。

这种类似树的结构可通过向表中添加一个单独的列(缺省称为parent_id)来做到。这个

列是个引用自身表的外键,链接子行到它们的父行。图15.1 演示了它。

要显示出树是如何工作的,让我们创建个简单的分类表,每个顶层分类有子分类,每个

子分类可以添加子分类层。注意外键指回到自身表中。

create table categories (

id int not null auto_increment,

name varchar(100) not null,

parent_id int,

constraint fk_category foreign key (parent_id) references

categories(id),

primary key (id)

);

相应的“模型”使用带有种类名字acts_as_tree 的方法来指出这种关系。:order 参数

意味着当我们查找特定节点的子项时,我们会看到它们按它们名字列被重新排列。

class Category < ActiveRecord::Base

acts_as_tree :order => "name"

end

通常你得有一些最终用户功能来创建和管理分类层次。这儿,我们只使用创建它的代码。

注意我们如何使用子项属性来管理任何节点的子项。

root = Category.create(:name => "Books")

fiction = root.children.create(:name => "Fiction")

non_fiction = root.children.create(:name => "Non Fiction")

non_fiction.children.create(:name => "Computers")

non_fiction.children.create(:name => "Science")

non_fiction.children.create(:name => "Art History")

fiction.children.create(:name => "Mystery")

fiction.children.create(:name => "Romance")

fiction.children.create(:name => "Science Fiction")

现在我们做好了所准备工作,我们可以试用树结构了。我们使用我们用于list 代码的

display_children()方法。

display_children(root) # Fiction, Non Fiction

sub_category = root.children.first

puts sub_category.children.size #=> 3

display_children(sub_category) #=> Mystery, Romance, Science Fiction

non_fiction = root.children.find(:first, :conditions => "name = 'Non

Fiction'")

display_children(non_fiction) #=> Art History, Computers, Science

puts non_fiction.parent.name #=> Books

我们用于操纵子项的各种方法看起来很熟悉:它们与提供给has_many 的是一样的。事实

上,如果我们看看acts_as_tree 的实现,我们将看到这做的所有事情都是构建在belongs_to

和has_many 属性上,每个都指回自身表。就像我们写的一样。

class Category < ActiveRecord::Base

belongs_to :parent,

:class_name => "Category"

has_many :children,

:class_name => "Category",

:foreign_key => "parent_id",

:order => "name",

:dependent => true

end

如果你需要优化子项大小的性能,你可以构建一个counter 缓存(就好像你在用

has_many)。添加选项:counter_cache => true 给acts_as_tree 声明,添加列

children_count 给你的表。

继续阅读