Yii Framework Forum: Nested set - Yii Framework Forum

Jump to content

  • (4 Pages)
  • +
  • 1
  • 2
  • 3
  • Last »
  • You cannot start a new topic
  • You cannot reply to this topic

Nested set Nested set behavior for AR models Rate Topic: ***** 4 Votes

#1 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 3,616
  • Joined: 17-January 09
  • Location:Russia

Posted 11 June 2010 - 02:56 PM

This extension allows AR models to work with nested sets tree.

http://www.yiiframew...tedsetbehavior/

Can be used with http://www.yiiframew...edtreeactions/.
Yii 1.1 Application Development Cookbook

Enjoying Yii? Star us at github: 1.1 and 2.0.
3

#2 User is offline   Yeti 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 97
  • Joined: 30-May 09

Posted 13 June 2010 - 11:09 AM

Tried using this for nested comments, so $manyRoots == true.

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.
0

#3 User is offline   jonah 

  • Advanced Member
  • Yii
  • Group: Yii Dev Team
  • Posts: 733
  • Joined: 27-November 08
  • Location:California (US)

Posted 13 June 2010 - 08:33 PM

Maybe this could go into Zii: http://code.google.c...ues/detail?id=2
http://php-thoughts.cubedwater.com - my bloggings about Yii
0

#4 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 3,616
  • Joined: 17-January 09
  • Location:Russia

Posted 14 June 2010 - 10:29 AM

Yeti
What are your schema and the code you are using?
Yii 1.1 Application Development Cookbook

Enjoying Yii? Star us at github: 1.1 and 2.0.
0

#5 User is offline   Yeti 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 97
  • Joined: 30-May 09

Posted 15 June 2010 - 04:19 AM

View Postsamdark, on 14 June 2010 - 10:29 AM, said:

Yeti
What are your schema and the code you are using?


The schema is as provided in the download.

$comment = new Comment();
// If form submitted
$comment->saveNode();
1

#6 User is offline   Yeti 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 97
  • Joined: 30-May 09

Posted 15 June 2010 - 04:48 AM

When added to the behavior, the following methods return the descendants of a node as a tree of related nodes;

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>

0

#7 User is offline   Yeti 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 97
  • Joined: 30-May 09

Posted 15 June 2010 - 04:56 AM

And finally (for today :) ); very minor, but I found it made more sense in my code to have the level starting at zero rather one. This way when using foreach($nodes as $level=>$node) at the top of the tree $level == $node->level.
0

#8 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 3,616
  • Joined: 17-January 09
  • Location:Russia

Posted 23 June 2010 - 03:23 PM

There is a new version in SVN. Will be released as archive soon.

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.
Yii 1.1 Application Development Cookbook

Enjoying Yii? Star us at github: 1.1 and 2.0.
0

#9 User is offline   Yeti 

  • Junior Member
  • Pip
  • Yii
  • Group: Members
  • Posts: 97
  • Joined: 30-May 09

Posted 24 June 2010 - 12:57 PM

View Postsamdark, on 23 June 2010 - 03:23 PM, said:

Yeti
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.


View Postsamdark, on 23 June 2010 - 03:23 PM, said:

getDescendants looks like presentational method. I think it should not be included into behavior itself. Maybe a widget will fit.


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.


View Postsamdark, on 23 June 2010 - 03:23 PM, said:

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.


Said it was minor :)
0

#10 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 3,616
  • Joined: 17-January 09
  • Location:Russia

Posted 24 June 2010 - 05:17 PM

Fixed SQL schema.
Yii 1.1 Application Development Cookbook

Enjoying Yii? Star us at github: 1.1 and 2.0.
0

#11 User is offline   Junior - df9 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 416
  • Joined: 24-May 09
  • Location:Brazil

Posted 15 July 2010 - 07:03 PM

View Postsamdark, on 24 June 2010 - 05:17 PM, said:

Fixed SQL schema.



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!
0

#12 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 3,616
  • Joined: 17-January 09
  • Location:Russia

Posted 16 July 2010 - 03:27 AM

scoob.junior
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/
Yii 1.1 Application Development Cookbook

Enjoying Yii? Star us at github: 1.1 and 2.0.
0

#13 User is offline   Junior - df9 

  • Advanced Member
  • PipPipPip
  • Yii
  • Group: Members
  • Posts: 416
  • Joined: 24-May 09
  • Location:Brazil

Posted 16 July 2010 - 06:44 AM

View Postsamdark, on 16 July 2010 - 03:27 AM, said:

scoob.junior
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!
0

#14 User is offline   mech7 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 220
  • Joined: 26-March 09

Posted 18 July 2010 - 11:56 PM

I am trying somoething very simple like..

$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 :( Could you please add some documentation how to actually add / modify / delete nodes :)

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...
0

#15 User is offline   mech7 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 220
  • Joined: 26-March 09

Posted 19 July 2010 - 01:31 AM

Ah sorry... I had validation in my model on lft / rgt / lvl.. But the behavior runs after validation.

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.
0

#16 User is offline   mech7 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 220
  • Joined: 26-March 09

Posted 20 July 2010 - 11:39 PM

Also how to get the the path? Like I select a category but want to get the entire path all the way up, usefull for breadcrumbs and filtering in categories.

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
0

#17 User is offline   mech7 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 220
  • Joined: 26-March 09

Posted 21 July 2010 - 02:38 AM

Ok i needed this badly so i added my own function.. but if anybody can show how to do with activerecord plz do :)

    /**
     * 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;
    }

0

#18 User is offline   mech7 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 220
  • Joined: 26-March 09

Posted 21 July 2010 - 03:08 AM

One more thing... I think tables now are not being locked when making modifications?
Or is the transaction enough?
0

#19 User is offline   samdark 

  • Having fun
  • Yii
  • Group: Yii Dev Team
  • Posts: 3,616
  • Joined: 17-January 09
  • Location:Russia

Posted 21 July 2010 - 03:30 AM

Transactions should be enough but if you'll get a problem with data, let me know.
Yii 1.1 Application Development Cookbook

Enjoying Yii? Star us at github: 1.1 and 2.0.
0

#20 User is offline   mech7 

  • Standard Member
  • PipPip
  • Yii
  • Group: Members
  • Posts: 220
  • Joined: 26-March 09

Posted 26 July 2010 - 11:56 PM

Btw how to move the node to a new parent? I only see there is moveBefore / moveAsFirst and append does not seem to work :(

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 ?
0

Share this topic:


  • (4 Pages)
  • +
  • 1
  • 2
  • 3
  • Last »
  • You cannot start a new topic
  • You cannot reply to this topic

1 User(s) are reading this topic
0 members, 1 guests, 0 anonymous users