所以我认为我的问题归结为两个问题:
当树存储在 MySQL 中(在两个表之间)时,如何使用邻接列表模型方法在 PHP 中构建可遍历树结构,同时牢记性能?
有什么可维护的方法可以以所需的格式显示树,而不用重复遍历代码和用 if/else 和 switch 语句乱扔逻辑?
下面是更多细节:
我正在使用 Zend 框架。
我正在处理问卷。它存储在两个单独表之间的 MySQL 数据库中:questions 和 question_groups。每个表扩展适当的 Zend_Db_Table_* 类。层次结构使用邻接列表模型方法表示。
我意识到我遇到的问题很可能是因为我将树结构填充到 RDBMS 中,所以我对替代方案持开放态度。但是,我还存储了调查问卷的受访者和他们的回答,因此需要其他方法来支持这一点。
调查问卷需要以各种 HTML 格式显示:
问题是叶节点,问题组可以包含其他问题组和/或问题。总共有 100 多行要处理和显示。
目前,我有一个 View 助手,它使用递归执行所有处理以检索 question_group 的子级(一个在两个表之间执行 UNION 的查询:QuestionGroup::getChildren($id))。此外,当显示带有问题回答的调查问卷时,需要额外的两个查询来检索受访者及其对每个问题的回答。
虽然页面加载时间不是很长,但这种方法感觉不对。递归加上对几乎每个节点的多次数据库查询并没有让我内心感到很温暖和模糊。
我试过了 recursion-less和递归方法对UNION返回的全树数组构建分层数组进行遍历和展示。但是,由于组和问题存储在单独的表中,因此存在重复的节点 ID,这似乎无法解决。也许我在那里遗漏了一些东西......
目前,以上述格式显示树的逻辑相当困惑。我不想到处复制遍历逻辑。然而,随处可见的条件语句也不会产生最容易维护的代码。我已经阅读了 Visitors、Decorators 和一些 PHP SPL 迭代器,但我仍然不清楚它们如何与扩展 Zend_Db_Table、Zend_Db_Table_Rowset 和 Zend_Db_Table_Row 的类一起工作。特别是因为我还没有解决之前从数据库构建层次结构的问题。如果能够稍微轻松地添加新的显示格式(或修改现有的显示格式),那就太好了。
最佳答案
传统上,邻接表会在每行中为您提供一个 parent_id 列,将行链接到其直接父级。如果该行是树的根,则 parent_id 为 NULL。但这会导致您运行许多代价高昂的 SQL 查询。
添加另一列 root_id,这样每一行都知道它属于哪棵树。这样您就可以使用单个 SQL 查询获取给定树的所有节点。向您的 Table 类添加一个方法,以通过树的根 ID 获取 Rowset。
class QuestionGroups extends Zend_Db_Table_Abstract
{
protected $_rowClass = 'QuestionGroup';
protected $_rowsetClass = 'QuestionGroupSet';
protected function fetchTreeByRootId($root_id)
{
$rowset = $this->fetchAll($this
->select()
->where('root_id = ?', $root_id)
->order('id');
);
$rowset->initTree();
return $rowset;
}
}
编写一个扩展 Zend_Db_Table_Row 的自定义类并编写函数来检索给定行的父项及其子项的 Rowset。 Row 类应包含 protected 数据对象以引用父项和子项数组。 Row 对象还可以具有 getLevel() 函数和用于面包屑的 getAncestorsRowset() 函数。
class QuestionGroup extends Zend_Db_Table_Row_Abstract
{
protected $_children = array();
protected $_parent = null;
protected $_level = null;
public function setParent(Zend_Db_Table_Row_Abstract $parent)
{
$this->_parent = $parent;
}
public function getParent()
{
return $this->_parent;
}
public function addChild(Zend_Db_Table_Row_Abstract $child)
{
$this->_children[] = $child;
}
public function getChildren()
{
return $this->_children;
}
public function getLevel() {}
public function getAncestors() {}
}
编写一个扩展 Zend_Db_Table_Rowset 的自定义类,该类具有迭代行集中行的功能,设置父子引用,以便您随后可以将它们作为树遍历。此外,Rowset 应该有一个 getRootRow() 函数。
class QuestionGroupSet extends Zend_Db_Table_Rowset_Abstract
{
protected $_root = null;
protected function getRootRow()
{
return $this->_root;
}
public function initTree()
{
$rows = array();
$children = array();
foreach ($this as $row) {
$rows[$row->id] = $row;
if ($row->parent_id) {
$row->setParent($rows[$row->parent_id]);
$rows[$row->parent_id]->addChild($row);
} else {
$this->_root = $row;
}
}
}
}
现在您可以在行集上调用 getRootRow(),它会返回根节点。获得根节点后,您可以调用 getChildren() 并循环遍历它们。然后,您还可以对这些中间子项中的任何一个调用 getChildren(),并以您想要的任何格式递归输出树。
关于php - 有两个表的邻接表模型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1987101/
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby中使用两个参数异步运行exe吗?我已经尝试过ruby命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何rubygems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除
我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:
对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs
我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案
ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序