Nested set Nested set behavior for AR models
#1
Posted 11 June 2010 - 02:56 PM
http://www.yiiframew...tedsetbehavior/
Can be used with http://www.yiiframew...edtreeactions/.
#2
Posted 13 June 2010 - 11:09 AM
I got a DB error when trying to save my first comment: "root does not have a default value"
Adding
$owner->{$this->root}=0; at line 552 fixed the issue.
#3
Posted 13 June 2010 - 08:33 PM
#6
Posted 15 June 2010 - 04:48 AM
The returned array is a list of child nodes.
Each child has it's children as a list of related models in the descendants related models and so on.
The benefit is that this allows the application to use recursive code to traverse the descendants.
/**
* Returns descendants of the current node as a tree.
* The returned array is a list of child nodes.
* Each child has it's children as a list of related models in the
* [i]descendants[/i] related models and so on.
* @param integer The depth of descendants to fetch
* @return array Descendants of the current node
*/
public function getDescendants($depth = null) {
$descendants = $this->descendants($depth)->findAll();
$_descendants = array();
while ($descendants)
$_descendants[] = $this->descendants2tree($descendants);
return $_descendants;
}
/**
* Recursive function to add descendants as related records
* @param array Remaining descendants
* @return CModel Branch with descendants as related records
*/
private function descendants2tree(&$descendants) {
$branch = array_shift($descendants);
while ($descendants) {
if ($descendants[0]->{$this->left} < $branch->{$this->right})
$branch->addRelatedRecord('descendants', $this->descendants2tree($descendants), true);
else
break;
}
return $branch;
}
Usage
$descendants = $node->getDescendants($depth);
or if all descendants are required - i.e. not depth limited
$descendants = $node->descendants;
Now - for example - in a view (called "descendants") you can do something like this to render the whole descendant tree in a nested list:
<ul>
<?php foreach ($descendants as $descendant): ?>
<li>
<div><?php echo $descendant->content; ?></div>
<?php $this->renderPartial('descendants', array('descendants'=>$descendant->descendants)); ?>
</li>
<?php endforeach; ?>
<ul>
#7
Posted 15 June 2010 - 04:56 AM
#8
Posted 23 June 2010 - 03:23 PM
changelog
— Unit tests added (creocoder)
— Incorrect usage of save() and delete() instead of behavior's saveNode() and deleteNode() is now detected (creocoder)
Yeti
Please try to reproduce your issue with DB error.
getDescendants looks like presentational method. I think it should not be included into behavior itself. Maybe a widget will fit.
Level starting at zero isn't better in any way plus most implementations are using level started at one so it will be easier to migrate when needed.
#9
Posted 24 June 2010 - 12:57 PM
samdark, on 23 June 2010 - 03:23 PM, said:
Please try to reproduce your issue with DB error.
to reproduce just remove the change above then call the saveNode() method with $manyRoots true.
Tracing through what is going on is:
- The schema supplied declares root as NOT NULL.
- the makeRoot() method does not (as supplied) set root to any value before inserting the record, i.e. root for the record is NULL when it is saved.
To prevent the error root needs to be set to something prior to saving the record (zero made sense to me for a new root node and the behavior I think is the right place to do it), or the schema needs to allow root to be NULL; of the two setting root to a value for me seems the better solution.
samdark, on 23 June 2010 - 03:23 PM, said:
We might be getting into semantics; getDescendants returns the data in a particular format which of itself it is not presentational. Though as the example shows the view may become simpler; kind of depends what you are doing in the view I guess.
Whether it goes in your behavior is of course your call; for those that want to use it perhaps a widget or a class extending ENestedSetBehavior.
samdark, on 23 June 2010 - 03:23 PM, said:
Said it was minor
#11
Posted 15 July 2010 - 07:03 PM
samdark, on 24 June 2010 - 05:17 PM, said:
hey, guys!
first of all, Samdark, thanks for this extension!
I have a little doubt: how do you create a CRUD for this kind of table?
I mean, when you crud the model, all you get is a form where you have to manually fill the root, left, right and level fields...this is not that easy imho and i'm sure you guys have solved this more elegantly
could you please share your experience on how to crud this model?
any help is very appreciated
regards!
Junior
df9.com.br
Linux Registered User #364954
GNU/Linux: together we're ready!
#12
Posted 16 July 2010 - 03:27 AM
Creocoder implemented it. I'm just tested it and wrote documentation.
To manage tree (taxonomy) I'm personally using this one: http://www.yiiframew...tedtreeactions/
#13
Posted 16 July 2010 - 06:44 AM
samdark, on 16 July 2010 - 03:27 AM, said:
Creocoder implemented it. I'm just tested it and wrote documentation.
To manage tree (taxonomy) I'm personally using this one: http://www.yiiframew...tedtreeactions/
thank you very much, Samdark, i'm testing this right now..facing some troubles in rendering but have already posted in the EJNestedTreeActions forum topic
regards!!
Junior
df9.com.br
Linux Registered User #364954
GNU/Linux: together we're ready!
#14
Posted 18 July 2010 - 11:56 PM
$root=Categories::model()->roots()->findByPk(1);
$cat = new Categories();
$cat->name = 'Cool';
$root->append($cat);
$root->save();
// Does nothing too :(
$cat->save();
Or..
$cat = new Categories();
$cat->name = 'Cool';
$cat->appendTo($root);
$cat->save();
But having no luck nothing get's saved
Also please add to the documentation what values a root needs left: 1, right:0, root: (primary id) Else it would throw some sql errors...
#15
Posted 19 July 2010 - 01:31 AM
And I notice the root needst left = 1 / rgt = 2 else it will move the root to child
Any reason why there is not a addRoot method ?
Also it be nice to have some convience method to have the tree return in an multi dimensional associative array... As far as I can see now descendants()->findAll() just returns all records in a flat array.
#16
Posted 20 July 2010 - 11:39 PM
like..
SELECT parent.name FROM nested_category AS node, nested_category AS parent WHERE node.lft BETWEEN parent.lft AND parent.rgt AND node.name = 'FLASH' ORDER BY parent.lft;
Also I see now that my first error was because validation is called before the makeRoot function.. maybe better to call this after the makeRoot so the model can still have validation rules. And not have to call validate = false
#17
Posted 21 July 2010 - 02:38 AM
/**
* Returns the path of the node
* @return array
*/
public function getPath()
{
$owner = $this->getOwner();
$alias = $owner->getTableAlias();
$pk = $owner->primaryKey;
$tbl_select = "";
$total = count($owner->tableSchema->columns);
$i = 0;
foreach($owner->tableSchema->columns as $column => $value){
$i++;
$tbl_select .= "parent.".$column;
if($i < $total){
$tbl_select .= ", ";
}
}
$sql = "SELECT $tbl_select
FROM ".$owner->tableSchema->name." AS node,
".$owner->tableSchema->name." AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
AND node.".$owner->tableSchema->primaryKey." = ".$pk."
AND parent.".$this->level." > 0 ";
if($this->hasManyRoots){
$sql .= "AND parent.".$this->root." = ".$owner->{$this->root}." ";
}
$sql .= " ORDER BY parent.lft;";
$command = Yii::app()->db->createCommand($sql);
$result = $command->queryAll();
return $result;
}
#18
Posted 21 July 2010 - 03:08 AM
Or is the transaction enough?
#19
Posted 21 July 2010 - 03:30 AM
#20
Posted 26 July 2010 - 11:56 PM
And why if I want to use the move methods there are restrictions that the node cannot be a descendant? If I want to move a child to top cannot ?

Help













