- 浏览: 232906 次
- 性别:
- 来自: 我也来自火星?
文章分类
最新评论
-
chengUFO:
Test tes = c.newInstance();执行以上 ...
自定义ClassLoader -
lliiqiang:
资料太少了,伪造客户端和事先标准以外数据为攻击,其它的是bug ...
Openlaszlo调用JavaRPC和JAVA类通信 -
tianshaojie:
楼主,为什么我安装你的方法建立工程后,访问就出错,我用的是ta ...
Tapestry4入门 -
panshunchang:
发帖过程这么辛苦,还要回答一大堆问题,受不了了
[常用代码整理]JAVA反射 -
活靶子:
生成一个join的SQL语句
SELECT items.* F ...
Better looking URLs with friendly_id
在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的官方解释:
上面是引用自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'))
最后的显示效果:
评论
1、单纯的nested tree无法取直系子节点,还是要借助parent,不过这不是什么大问题
2、插入及更新效率低,而且为了保证更新不出问题,会进行锁表,这样在更新频繁的场合并发能力就差
在构建树的时候,都会通过树枝节点,然后再找到该树挂的树叶节点,对于第1点,并没有什么大问题。
第2点,个人感觉效率并非很低,而且,对于节点的操作,并不会非常频繁。
有Category 和Article 两个Model
关联如下:
Category hasMany Categories
Category hasMany Articles
Category要怎样的树形结构(如 比邻树?父子树?),才能最优化的实现获取某个Category下的所有Articles(包括子Category里的)呢?
1、单纯的nested tree无法取直系子节点,还是要借助parent,不过这不是什么大问题
2、插入及更新效率低,而且为了保证更新不出问题,会进行锁表,这样在更新频繁的场合并发能力就差
不过在数据库的设计上的lft和rgt的设计有些疑问,以下说说我的想法,如有不对的地方谢谢批评指正。
RoR的这个设计对于大数据量tree结构是最好的,比如一些常见的查询都可以避免循环或者递归抓取了:
--查询某个parent的所有子节点 SELECT * WHERE lft IS BETWEEN parent.lft AND parent.rgt --统计某个节点的子节点数目 (right - left - 1)/2 --获取某个节点的所有父节点 SELECT * WHERE node.lft IS BETWEEN lft AND rgt
而且通过在lft, rgt上设置索引能够达到最好的查询效率。
RoR是通过callback来实现新加/删除/移动节点后,更新对应的所有节点lft/rgt值,这个更新也只是一句update语句而已,不需要循环调用。在Java中我们也可以这样设计,比如利用Hibernate的event机制。
你说的加level也是一种常见方法,但是like查询效率很低,不是合适大数据量的一种设计,而且需求如果要求节点是可以变更父子关系的,那么更新level也是一件麻烦的事情。记得很早以前JavaEye讨论过相关的tree数据库设计,文章中总结了多种方法,对于优缺点都分析得很清楚,我再找找看。
不过在数据库的设计上的lft和rgt的设计有些疑问,以下说说我的想法,如有不对的地方谢谢批评指正。
(事先申明,不是很熟悉Rails的acts_as_nested_set,对它的实现原理不了解。对于我下面的疑问,也许acts_as_nested_set已经有了很好的解决方案,我也只不过提提建议罢了。)
(因为我是做Java的,所以例子都是实际开发的Java代码,SSH的架构。好在rails区里以前的Java开发者众多,所以我的代码大家应该都能看的懂吧。)
lft和rgt这两个字段看上去是用来定义一个类型边界的。如果需要查找某个特定类型及其子类的话,则先查找该类型的lft和rgt,然后再 (lft > ? and rgt < ?) 获得其子类。
这种设计在数据结构完备的情况下能准确的统计出所有的子类。但是我要动态的增加Child子类,或者改变Child的隶属关系的时候,就需要对数据库表中所有数据的lft和rgt做出调整。比如我要在Child 1中增加一个Child 1.3。相应的 Root、Child 1、Child 2、Child 2.1、Child 2.2 的lft和rgt都要做相应的变化。
也许acts_as_nested_set可以通过先 delete from category; 后 insert into values(?,?,?,?); 的方式进行全类的维护,但是如果别的类有对Category的引用(即外键)。这样的隶属关系不是会产生混乱了吗?况且如果数据库真的建立了外键的话,也不允许 delete from category; 操作的。
所以我的考虑是用一个level字段代替lft和rgt字段,level字段维护着Category实例的层级关系。
例如,将root的level定义为1(这个在数据库或程序中可配),那么Child 1的level就为1|${id}(其中'|'为Level分层标记,${id}表示当前数据Id或其它可唯一标识的字段值),假定为1|2,同理Child 1.1的level就是1|2|3。
类结构如下所示:使用的是annotation的hibernate
@Entity public class Category implements Serializable { /** Level分层标记 */ public static final String LEVEL_SPLIT = "|"; @Id @GeneratedValue private Integer id; /** 名称 */ private String name; /** level */ private String level; /** 删除标记 */ private Boolean delFlag; /** 下级的类别 */ @OneToMany(fetch = FetchType.LAZY, mappedBy="parent") @OrderBy("id") private List<Category> children = new ArrayList<Category>(); /** 上级的类别 */ @ManyToOne @JoinColumn(name = "category_id") private Category parent; // 省略所有 getter/setter 方法... }
维护后数据库中的数据如下:
查找某个特定类型及其子类,只需要获得当前类型的level值,然后查找 (level like 'xxxx%')即可。如下所示:
Rails中需要重写Category中类似find的方法。
public List<Category> getCategoryList(Integer startId) { Category root = crudDao.get(Category.class, startId); // crudDao继承HibernateDaoSupport,并封装了HibernateTemplate的操作,下同 String hql = "from Category where level like ? order by id desc"; return crudDao.query(hql, root.getLevel + Category.LEVEL_SPLIT + "%"); }
新增或修改Category的方法。则需要先查找当前父类,再重新维护当前类的level属性值即可。如下所示:
Rails中需要重写Category中的add_child方法
public void saveCategory(Category category) { Category parent = category.getParent(); // 判断Category的父类有没有设置 if (parent == null || parent.getId() == null || parent.getId() == 0) { throw new ParentNotFoundException(Category.class); } // 重新设置父类关系 parent = crudDao.get(Category.class, parent.getId()); // crudDao继承HibernateDaoSupport,并封装了HibernateTemplate的操作,下同 category.setParent(parent); if (category.getId() == null || category.getId() == 0) { crudDao.save(category); category.setLevel(parent.getLevel() + Category.LEVEL_SPLIT + category.getId()); crudDao.update(category); } else { Category oldCategory = crudDao.get(Category.class, category.getId()); BeanUtils.copyProperties(category, oldCategory, new String[]{"id", "children"}); oldCategory.setLevel(parent.getLevel() + Category.LEVEL_SPLIT + oldCategory.getId()); crudDao.update(oldCategory); } }
使用Rails的ActiveRecord,以上代码可以写的更简练
页面显示,我使用的是纯javascript的dtree。代码如下所示:
<body class="dtree"> <p><a href="javascript:categoryTree.openAll();">open all</a> | <a href="javascript:categoryTree.closeAll();">close all</a></p> <script type="text/javascript"> var dtreeImgPath = "${ctx}/script/dtree/img/"; categoryTree = new dTree('categoryTree'); <c:forEach var="category" items="${list}"> <c:choose> <c:when test="${category.parent == null or category.parent == null or category.parent.id == 0}"> categoryTree.add(${category.id},-1,'${category.name }',"javascript:doSelect('${category.id}','${category.name }')"); </c:when> <c:otherwise> categoryTree.add(${category.id},${category.parent.id},'${category.name }',"javascript:doSelect('${category.id}','${category.name }')"); </c:otherwise> </c:choose> </c:forEach> document.write(categoryTree); </script> </body>
显示的效果如下:
create table nested_objects (
id int(11) unsigned not null auto_increment,
tree_id varchar(32), --作为treeid
parent_tree_id varchar(32),
lft int(11),
rgt int(11),
name varchar(32),
primary key (id)
);
是不是就没法映射了.
还有是在MYSQL里 用varchar做主键,在建一个自增int列行不行
问题1 MYSQL支持varchar做主键,但是当我在那个varchar做主键的表里,在建一个自增的int列就报错,
there can be only one auto column and it must be defined as a tree
汗!
一般不是有些公司很奇怪,不仅一个表里需要一个sn就是int自增那种,还要一个自己生成的类似于guid的列,例如商品编号之类的,如"productA002",我想把这个productA002这列设为主键,另外在来一个自增列作为sn,这样的方式在SQL里行,MySQL里不行.
我自己也不明为啥需要2个列,没想通.
问题2 activerecord act as tree问题 是否可以将你的例子里的
id int(11) unsigned not null auto_increment,
parent_id int(11),
这2列替换成我刚才说的那个productA002 也就是非主键那个列作为tree的关系列
对不起,我看了几遍,都看不懂你说什么,可不可以表达清楚一些,不好意思。
问题1 MYSQL支持varchar做主键,但是当我在那个varchar做主键的表里,在建一个自增的int列就报错,
there can be only one auto column and it must be defined as a tree
汗!
一般不是有些公司很奇怪,不仅一个表里需要一个sn就是int自增那种,还要一个自己生成的类似于guid的列,例如商品编号之类的,如"productA002",我想把这个productA002这列设为主键,另外在来一个自增列作为sn,这样的方式在SQL里行,MySQL里不行.
我自己也不明为啥需要2个列,没想通.
问题2 activerecord act as tree问题 是否可以将你的例子里的
id int(11) unsigned not null auto_increment,
parent_id int(11),
这2列替换成我刚才说的那个productA002 也就是非主键那个列作为tree的关系列
def to_json_with_leaf(options = {})
self.to_json_without_leaf(options.merge(:methods => :leaf))
end
self.to_json_without_leaf
这个方法找不到啊? 可以的话 能把源代码发我吗? yangtao309@gmail.com
什么叫没有写全?你有什么问题吗?
发表评论
-
[翻译]如何学习ruby和rails
2009-08-06 12:36 1990已经有人全部翻译:http://qichunren.iteye ... -
在netbeans6.7中使用rspec1.2.7
2009-07-10 14:20 1187将开发工具换成了netbeans6.7,rspec的版本,也变 ... -
MySQL5.1.x的驱动有问题?
2009-03-29 18:36 1207今天把MySQL换成了5.1版本的,使用rake db:cre ... -
ActiveRecord级联删除
2009-03-30 21:00 2665Rails在关联关系中,han_o ... -
install ruby on rails, sqlite3, sqlite3-ruby under ubuntu8.10
2009-04-01 14:57 1315nothing but the script I used: ... -
Rails Plugin: Easy Fckeditor
2009-04-02 22:57 1369#安装easy fckeditor插件,需要首先安装git ... -
修改Easy_Fckeditor上传图片的目录
2009-04-03 09:32 1140修改文件上传目录 easy_fckeditor默认的文件上传目 ... -
[转]Why I like Ruby #1: alias_method
2009-04-03 16:18 1037So you found yourself in the ne ... -
Rails中的namespace
2009-04-10 16:56 1310在Rails中可以通过namespace来管理controll ... -
Rails中namespace的layout
2009-04-15 10:57 1778在rails中提供了namespace的功能,但是如何实现na ... -
也用上了windows 7,不过gem用不起来
2009-05-03 17:57 1896今天早上将系统换成了windows7了,在试用下来,从发到上来 ... -
ruby动态编程
2009-04-27 14:52 2728Ruby 动态编程 在介绍ruby ... -
Rails表单
2008-03-17 22:47 1462User为和Address类是一个一对多的关系: User.r ... -
ruby操作word2
2008-03-15 18:59 21require 'win32ole' msword = WI ... -
ruby操作word
2008-03-15 18:56 23require 'win32ole' word = WIN3 ... -
ruby操作WORD文档生成HTML
2008-03-15 18:54 2674通过ruby代码,将指定的WORD文档转换为HTML: r ... -
Netbeans,无法承受之慢
2007-11-26 20:29 3609Netbenas,现在已经是Beta2了,应该说,在功能上,已 ...
相关推荐
这篇博客文章“Rails中应用Ext.tree:以中国的省市地区三级联动选择为例”提供了一个实用的示例,教我们如何利用Ext.js库中的Tree组件来实现这种功能。 首先,让我们了解Rails和Ext.js的基本概念。Rails是基于Ruby...
源码部分可能涵盖了如何在Rails项目中集成Ext JS库,以及如何利用Ext Scaffold插件生成定制的视图模板。工具部分可能涉及到其他辅助开发的工具或库,如自动完成、调试器或测试框架,以提升开发效率。 从压缩包中的...
**Ruby-GoOnRails:利用Rails生成器构建Golang应用** Ruby on Rails(简称Rails)是一种流行的Web开发框架,以其“约定优于配置”的理念和高效的开发速度受到开发者喜爱。而Go(Golang)则是一种静态类型、编译型的...
nifty-generators, 有用的Rails 生成器脚本集合 漂亮的生成器用于脚手架,布局文件,身份验证和更多的有用 Rails 生成器脚本的集合。设置 Rails 3将 gem 添加到你的。gem"nifty-generators", :group => :developm
- **丰富组件库**:Ext JS 提供了大量的用户界面组件,如 Data Grids、Tree Views、Tabs、Toolbars 和 Menus 等。 - **跨浏览器兼容性**:支持多种浏览器版本,如 Internet Explorer 6+、Firefox 1.5+、Safari 2+ 和...
`sitemap_generator` gem 是一个适用于Ruby on Rails框架的开源工具,它能够自动扫描你的Rails应用,生成包含所有路由的Sitemap。安装这个gem非常简单,只需要在Gemfile中添加以下行: ```ruby gem 'sitemap_...
简单的CLI利用Docker生成和运行Rails的环境
使用Rails生成Golang代码或管理Go应用开发go-on-rails是Rails生成器,旨在: 帮助开发和集成一些用Golang编写的API到现有的Rails应用程序,以实现高性能使用您熟悉的Rails工具开发和管理Golang应用程序项目将不太...
"Rails Erd"是一个Ruby gem,专门用于为Rails应用程序自动生成ERD,使得数据库设计和管理变得更加简单和直观。这个工具是由Voormedia开发的,版本号为0eb4577。 ERD是数据库设计的基础,它通过图形化方式展示了各个...
swagger-docs, 为 Rails api生成 swagger ui json文件,使用简单的DSL Swagger::Docs使用api为 Rails 应用生成swagger的ui json文件。 你可以向控制器类添加 swagger DSL,然后运行一个rake任务来生成json文件。 ...
字体配制文件 博文链接:https://babo.iteye.com/blog/72298
6. **Scaffolding**:Rails提供了快速生成基本CRUD(Create, Read, Update, Delete)操作的命令行工具,可以自动生成控制器、视图、样式表和测试文件,方便快速搭建原型。 7. **Testing**:Rails强调测试驱动开发,...
作者特别提到了“CRUD懶人大法Scaffold”,它是一种通过Rails自动生成代码的方式来快速搭建基本的CRUD操作,这大大简化了开发流程,使得开发者可以将更多的精力放在业务逻辑的实现上。 此外,书中还介绍了一些Rails...
总结来说,这个主题涵盖了Rails开发、RSpec测试、文档生成以及可能的文件转换技术。要深入学习这个话题,你需要熟悉Rails、RSpec的使用,理解CHM文件的结构,以及如何使用适当的工具和技术将代码行为测试和文档生成...
集合了Grape的所有Rails生成器脚本。 入门 将此行添加到 Rails 应用程序的 Gemfile 中: gem 'grape-api-generator' 然后运行bundle命令来安装它。 安装gem之后,您可以运行install生成器以生成基本的MyApp API...
rails_layout, 为各种前端框架生成 Rails 应用程序布局文件 RailsLayout gem使用这里 gem 可以设置你选择的前端框架的布局文件:Zurb基础 5.3Bootstrap 4.0Bootstrap 3.3它还将为 Bootstrap 或者基础设置设计视图。...