All, I''m trying to work out a couple of details regarding the use of the better_nested_sets plugin. I have made progress in converting my code from acts_as_tree, but need help solving a couple of problems. 1. When I try to get the children of the root object, I''m able to count them but don''t get any return values for children, all_children, etc.>> prj = Project.find_by_id(1)>> prj.children_count=> 3>> prj.children=> []>> prj.all_children=> [] 2. I would like to have multiple roots so that I''m not managing one huge tree. I have been trying to use scope to build multiple roots, but haven''t had much luck. In the end, I''m assuming that my table should look something like this: id | parent_id | lft | rgt | name ------------------------------------------------------- 1 | | | | ruby 2 | | | | rails 3 | | 1 | 6 | computers 4 | 3 | 2 | 3 | apple 5 | 3 | 4 | 5 | dell 6 | | 1 | 8 | rails plug-ins 7 | 6 | 2 | 3 | better_nested_set 8 | | | | frameworks 9 | 6 | 4 | 5 | restful_authentication 10| 6 | 6 | 7 | authorization 11| | | | databases Or This: id | parent_id | lft | rgt | name ------------------------------------------------------- 1 | | 1 | 2 | ruby 2 | | 1 | 2 | rails 3 | | 1 | 6 | computers 4 | 3 | 2 | 3 | apple 5 | 3 | 4 | 5 | dell 6 | | 1 | 8 | rails plug-ins 7 | 6 | 2 | 3 | better_nested_set 8 | | 1 | 2 | frameworks 9 | 6 | 4 | 5 | restful_authentication 10| 6 | 6 | 7 | authorization 11| | 1 | 2 | databases Here is my code, hopefully this will help identify the problem(s): Migration: class CreateProjects < ActiveRecord::Migration def self.up create_table :projects do |t| t.column :parent_id, :bigint t.column :lft, :integer t.column :rgt, :integer t.column :name, :string end end def self.down drop_table :projects end end Model: class Project < ActiveRecord::Base acts_as_nested_set :order => "name", :scope => :parent_id end Thanks! --Nick
Hi Nick, I was wondering how you were getting on-- glad to hear you haven''t given up yet!> 1. When I try to get the children of the root object, I''m able to > count them but don''t get any return values for children, all_children, > etc.See the comments about scope below.> 2. I would like to have multiple roots so that I''m not managing one > huge tree. I have been trying to use scope to build multiple roots, > but haven''t had much luck. In the end, I''m assuming that my table > should look something like this: >It should look like this (your second example), except note the ''root_id'' column, which will serve as the scope:> > id | root_id | parent_id | lft | rgt | name > ----------------------------------------------------------------- > 1 | 1 | | 1 | 2 | ruby > 2 | 2 | | 1 | 2 | rails > 3 | 3 | | 1 | 6 | computers > 4 | 3 | 3 | 2 | 3 | apple > 5 | 3 | 3 | 4 | 5 | dell > 6 | 6 | | 1 | 8 | rails plug-ins > 7 | 6 | 6 | 2 | 3 | better_nested_set > 8 | 8 | | 1 | 2 | frameworks > 9 | 6 | 6 | 4 | 5 | restful_authentication > 10| 6 | 6 | 6 | 7 | authorization > 11| 11 | | 1 | 2 | databases > >You don''t need to use root_id as your scope, but it is convenient.> acts_as_nested_set :order => "name", :scope => :parent_idAha! This won''t work, and is likely causing your problems. As I mentioned in a previous email, all items in a tree must share the same scope value. Just change it to '':scope => :root_id'', make sure the root_id values get set for your records, and you should be on your way. Going through this with you is informative for me, because it makes me aware of the parts of the API that are cryptic and confusing. Cheers, Krishna
Hi Krishna,> I was wondering how you were getting on-- glad to hear you haven''t given up yet!I took a couple of days off for Christmas which took me away from having fun with Ruby/ROR. It''s important for me to try and get as many things right with this application as I can so that I have less pain down the road. I think that using nested sets + multiple roots is a very good solution for my application and is worth the effort.> > 1. When I try to get the children of the root object, I''m able to > > count them but don''t get any return values for children, all_children, > > etc. > > See the comments about scope below.Changing the scope did fix this problem.> > 2. I would like to have multiple roots so that I''m not managing one > > huge tree. I have been trying to use scope to build multiple roots, > > but haven''t had much luck. In the end, I''m assuming that my table > > should look something like this: > > > > It should look like this (your second example), except note the > ''root_id'' column, which will serve as the scope: > > > > id | root_id | parent_id | lft | rgt | name > > ----------------------------------------------------------------- > > 1 | 1 | | 1 | 2 | ruby > > 2 | 2 | | 1 | 2 | rails > > 3 | 3 | | 1 | 6 | computers > > 4 | 3 | 3 | 2 | 3 | apple > > 5 | 3 | 3 | 4 | 5 | dell > > 6 | 6 | | 1 | 8 | rails plug-ins > > 7 | 6 | 6 | 2 | 3 | better_nested_set > > 8 | 8 | | 1 | 2 | frameworks > > 9 | 6 | 6 | 4 | 5 | restful_authentication > > 10| 6 | 6 | 6 | 7 | authorization > > 11| 11 | | 1 | 2 | databases > > > > > > You don''t need to use root_id as your scope, but it is convenient. > > > acts_as_nested_set :order => "name", :scope => :parent_id > > Aha! This won''t work, and is likely causing your problems.This was causing the problems, and in hindsight it makes sense that this would break things.>As I mentioned in a previous email, all items in a tree must share the same > scope value. Just change it to '':scope => :root_id'', make sure the > root_id values get set for your records, and you should be on your > way.I added the root_id column to my table and changed scope to '':scope => :root_id'', but I''m not sure how to set the root_id values. From the docs, I was thinking that the root_id value would be set when a new record was created without children, and that any children would automatically set their root_id value to it''s parents root_id value. However, after making these changes when I create new records no root_id values are set, and all the records are still acting as one tree.>From Docs:"root - root item of the tree (the one that has a nil parent; should have left_column = 1 too)" How are root_id values set?> Going through this with you is informative for me, because it makes me > aware of the parts of the API that are cryptic and confusing.This has been/is a fun growth experience for me, and I hope that others will find the information in these threads helpful when trying to solve there problems. I think that adding some more examples to the documentation, and possibly an example application may help new users work through things. I would be happy help where I can. Thanks! --Nick
Hi Nick,> I took a couple of days off for Christmas which took me away fromShame on you! ;)> I added the root_id column to my table and changed scope to '':scope > => :root_id'', but I''m not sure how to set the root_id values. From > the docs, I was thinking that the root_id value would be set when a> new record was created without children, and that any children would> automatically set their root_id value to it''s parents root_id value. > However, after making these changes when I create new records no > root_id values are set, and all the records are still acting as one > tree.At the moment it is up to the programmer. Whenever you create objects just do something like this: def create_subproject @project = Project.new(params[:project]) if @project.save if params[:parent_id].empty? # this project has no parent, so it is the root of a new tree @project.update_attribute("root_id", @project.id) else @parent = (params[:parent_id]) @project.update_attribute("root_id", @parent.root_id) @project.move_to_child_of @parent end flash[:notice] = ''Project was successfully created.'' redirect_to :action => ''list'' else render :action => ''new'' end end We could automatically set it, but only if we limited what can be used as a scope. Any thoughts on this, JCM?> This has been/is a fun growth experience for me, and I hope that > others will find the information in these threads helpful when trying > to solve there problems. I think that adding some more examples to > the documentation, and possibly an example application may help new > users work through things. I would be happy help where I can.Yeah, I think more examples would be good. I''m in the midst of a bunch of changes to the nested set API, but after things settle out I''ll look at that. best, Krishna
Krishna, I''m starting to make some real progress. I now have multiple trees, but I''m getting this error when I try to create a child: ### Error Begin ##### Impossible move, target node cannot be inside moved tree. #{RAILS_ROOT}/vendor/plugins/betternestedset/lib/better_nested_set.rb:438:in `move_to'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb:59:in `transaction'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/transactions.rb:95:in `transaction'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/transactions.rb:121:in `transaction'' #{RAILS_ROOT}/vendor/plugins/betternestedset/lib/better_nested_set.rb:429:in `move_to'' #{RAILS_ROOT}/vendor/plugins/betternestedset/lib/better_nested_set.rb:421:in `move_to_child_of'' #{RAILS_ROOT}/app/controllers/project_controller.rb:68:in `create'' ### Error End #### Here is my current create method: def create @project = Project.new(params[:project]) @parent = (params[:parent_id]) @root = (params[:root_id]) #breakpoint() if @project.save if not @parent # this project has no parent, so it is the root of a new tree @project.update_attribute("root_id", @project.id) else @project.update_attribute("root_id", @root) @project.move_to_child_of @parent end flash[:notice] = ''Project was successfully created.'' redirect_to :action => ''list'' else render :action => ''new'' end end Thanks! --Nick
> def create > @project = Project.new(params[:project]) > @parent = (params[:parent_id]) > @root = (params[:root_id]) > #breakpoint() > if @project.save > if not @parentoff the top of my head, if params[:parent_id] is "", it will still evaluate to true. try @parent.empty? instead.> # this project has no parent, so it is the root of a new tree > @project.update_attribute("root_id", @project.id) > else > @project.update_attribute("root_id", @root) > @project.move_to_child_of @parent > end > flash[:notice] = ''Project was successfully created.'' > redirect_to :action => ''list'' > else > render :action => ''new'' > end > end > > > Thanks! > --Nick > _______________________________________________ > Betternestedset-talk mailing list > Betternestedset-talk at rubyforge.org > http://rubyforge.org/mailman/listinfo/betternestedset-talk >
Hi Krishna,> > if @project.save > > if not @parent > > off the top of my head, if params[:parent_id] is "", it will still > evaluate to true.When I try: if @parent.empty? instead of: if not @parent I get this exception: You have a nil object when you didn''t expect it! You might have expected an instance of Array. The error occurred while evaluating nil.empty? When I use "if not @parent" I''m taken through the control structure to the else statement. else @project.update_attribute("root_id", @root) @project.move_to_child_of @parent end I set some breakpoints right before @project.move_to_child_0f @parent, and everything seemed fine. When I step to that line I get this exception: "Impossible move, target node cannot be inside moved tree." #{RAILS_ROOT}/vendor/plugins/betternestedset/lib/better_nested_set.rb:438:in `move_to'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb:59:in `transaction'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/transactions.rb:95:in `transaction'' #{RAILS_ROOT}/vendor/rails/activerecord/lib/active_record/transactions.rb:121:in `transaction'' #{RAILS_ROOT}/vendor/plugins/betternestedset/lib/better_nested_set.rb:429:in `move_to'' #{RAILS_ROOT}/vendor/plugins/betternestedset/lib/better_nested_set.rb:421:in `move_to_child_of'' #{RAILS_ROOT}/app/controllers/project_controller.rb:65:in `create'' /usr/bin/mongrel_rails:18 Essentially, I''m able to crate root projects all day long with out issue. When I try to make a record a child, I get the exception noted above. However, a record is still created with all of the expected root attributes. 1) Create a root project with no exceptions: id | root_id | parent_id| lft | rgt | name 1 | 1 | | 1 | 2 | Root Project 2) I then try to add a sub project by passing it a parent_id value. Then get the can''t move exception. 2 | 1 | | 1 | 2 | Sub Project Notice that the root ID (1) is correct, but there is no parent, which should also be 1. Here is the plug-in code that handles this exception: # detect impossible move if ((cur_left <= target_left) && (target_left <= cur_right)) or ((cur_left <= target_right) && target_right <= cur_right)) raise ActiveRecord::ActiveRecordError, "Impossible move, target node cannot be inside moved tree." end Looking at the exception logic, It would seem that all the conditions are meet to throw the exception. I''m not sure how to change the behavior of the create method such that this exception would be avoided. Many Thanks! --Nick
Hi Nick,> 1) Create a root project with no exceptions: > > id | root_id | parent_id| lft | rgt | name > 1 | 1 | | 1 | 2 | Root Project > > 2) I then try to add a sub project by passing it a parent_id value. > Then get the can''t move exception. > > 2 | 1 | | 1 | 2 | Sub ProjectAh! The previous create method I sent you was wrong. The problem was that it set the root_id for sub-projects after they had been created, but by that time the lft/rgt values were wrong (they should have been 3 and 4). Try this: def create @project = Project.new(params[:project]) @parent = (params[:parent_id]) @root = (params[:root_id]) if @parent # this must be done before save, otherwise a new tree would be created # (because root_id would be NULL, which would be different than existing trees) @project.root_id = @root end if @project.save if not @parent # this project has no parent, so it is the root of a new tree @project.update_attribute("root_id", @project.id) else @project.move_to_child_of @parent end flash[:notice] = ''Project was successfully created.'' redirect_to :action => ''list'' else render :action => ''new'' end end
Krishna, The last set of changes seems to be working perfectly. Thanks for helping me work through this! I have been doing the happy dance for about twenty minutes :)> We could automatically set it, but only if we limited what can be used > as a scope. Any thoughts on this, JCM?What if you did something like this ? acts_as_nested_set :root_scope => :root_id I noticed your announcement regarding the changes to the plug-in. Would you recommend moving to the new branch, or hold off for now? Thanks Again!!!! -Nick
Hi Nick, Glad to hear things are working.> acts_as_nested_set :root_scope => :root_idThe problem is that if you have multiple trees, each with multiple roots, the code wouldn''t know if a new record without a parent should be saved with a scope that would make it part of an existing tree or the root of a new tree. If we discarded the multiple root option, we could make it work.> I noticed your announcement regarding the changes to the plug-in. > Would you recommend moving to the new branch, or hold off for now?If you have things working the way you want, there isn''t really any reason to switch. Cheers, k
Hi, Le 28 d?c. 06, ? 20:46, Krishna Dole a ?crit :> At the moment it is up to the programmer. Whenever you create objects > just do something like this: > > def create_subproject > @project = Project.new(params[:project]) > if @project.save > if params[:parent_id].empty? > # this project has no parent, so it is the root of a new tree > @project.update_attribute("root_id", @project.id) > else > @parent = (params[:parent_id]) > @project.update_attribute("root_id", @parent.root_id) > @project.move_to_child_of @parent > end > flash[:notice] = ''Project was successfully created.'' > redirect_to :action => ''list'' > else > render :action => ''new'' > end > end > > We could automatically set it, but only if we limited what can be used > as a scope. Any thoughts on this, JCM?Is there a simple way to achieve this ? like testing :scope ? or an option :inherit_scope_on_creation to explicitely ask to copy scope from parent on creation ? Jean-Christophe Michel -- symetrie.com Better Nested Set for rails: http://opensource.symetrie.com/trac/better_nested_set
> Is there a simple way to achieve this ? like testing :scope ? > or an option :inherit_scope_on_creation to explicitely ask to copy > scope from parent on creation ?I''m not sure what the best solution is. I''ve opened a ticket for it: http://opensource.symetrie.com/trac/better_nested_set/ticket/25
> Glad to hear things are working. > > > acts_as_nested_set :root_scope => :root_id > > The problem is that if you have multiple trees, each with multiple > roots, the code wouldn''t know if a new record without a parent should > be saved with a scope that would make it part of an existing tree or > the root of a new tree. If we discarded the multiple root option, we > could make it work. >It sounds like that idea would be going in the wrong direction. :)> > I noticed your announcement regarding the changes to the plug-in. > > Would you recommend moving to the new branch, or hold off for now? > > If you have things working the way you want, there isn''t really any > reason to switch.I''m very happy with the way things are working at this point. I have even noticed an improvement in performance with my test data after moving from acts_as_tree. Thanks! --Nick
Hi Nick,> > The problem is that if you have multiple trees, each with multiple > > roots, the code wouldn''t know if a new record without a parent should > > be saved with a scope that would make it part of an existing tree or > > the root of a new tree. If we discarded the multiple root option, we > > could make it work. > > > It sounds like that idea would be going in the wrong direction. :)Just to clarify, I wasn''t suggesting we discard the option of having multiple trees. Rather, we currently allow individual trees to have more than one root (these multiple roots are siblings). This is often confusing for people, but convenient in some situations.> I''m very happy with the way things are working at this point. I have > even noticed an improvement in performance with my test data after > moving from acts_as_tree.Hey, that''s great. Krishna