Descending into associations

Hi,

I have a model called "ProductAttribute":

class ProductAttribute < ActiveRecord::Base   belongs_to :product   has_many :children, :foreign_key => :parent_id, :class_name => 'ProductAttribute', :dependent => :destroy, :order => :position   belongs_to :parent, :class_name => 'ProductAttribute' if :parent_id   acts_as_list :scope => :parent end

As you can see, my goal is to have an attribute with a number of children and a parent, as long as it's not a root attribute. I think I got it right, but if you see any errors in this, please do tell me.

Now I want to list an entire product, with all its attributes, in a view, which is where I got stuck. I want these associations to show hierarchically, so that a child is in fact a child element ( for example a list item ) of its parent. I can get all attributes using the product_id, so I have defined a collection called @attributes in my controller. But how to proceed in the view?

My guess is that I need a partial which loads itself, but how to pass along the subcollection (a child -can- have another child which can have another child and so on). It's important that it sorts every subcollection (e.g. a few children of a certain child) by position: do I have that covered in my model by :order => :position?

I really hope you can help.

Thank you!

Jaap Haagmans

Thank you very much!

> Now I want to list an entire product, with all its attributes, in a > view, which is where I got stuck. I want these associations to show > hierarchically, so that a child is in fact a child element ( for > example a list item ) of its parent. I can get all attributes using > the product_id, so I have defined a collection called @attributes in > my controller. But how to proceed in the view?

Use awesome_nested_set for this. It's much better for arbitrary tree structures than the simple adjacency list that you're currently using.

I'll look into it. How would this look in my views?

> My guess is that I need a partial which loads itself, but how to pass > along the subcollection (a child -can- have another child which can > have. another child and so on).

With a nested-set model such as I am recommending, you can get all the descendants with a single query. Without it...well...good luck. :slight_smile:

Got it :wink:

> It's important that it sorts every > subcollection (e.g. a few children of a certain child) by position: do > I have that covered in my model by :order => :position?

Maybe. Or you could use acts_as_list.

Id did, as you can see in my model. Is this enough?

> I really hope you can help.

> Thank you!

> Jaap Haagmans

Best, -- Marnen Laibow-Koserhttp://www.marnen.org mar...@marnen.org -- Posted viahttp://www.ruby-forum.com/.

Thanks again.

jhaagmans wrote: [...]

Use awesome_nested_set for this. �It's much better for arbitrary tree structures than the simple adjacency list that you're currently using.

I'll look into it. How would this look in my views?

Any way you like. awesome_nested_set only deals with the data structure and makes it easier to retrieve the records from the DB. How you render them in the view once retrieved is up to you.

[...]

Maybe. �Or you could use acts_as_list.

Id did, as you can see in my model.

Sorry, must have missed that.

Is this enough?

I think so.

Best,

Thank you again!

jhaagmans wrote:

[...]

>> Use awesome_nested_set for this. It's much better for arbitrary tree >> structures than the simple adjacency list that you're currently using.

> I'll look into it. How would this look in my views?

Any way you like. awesome_nested_set only deals with the data structure and makes it easier to retrieve the records from the DB. How you render them in the view once retrieved is up to you.

I'm asking this because I don't get it. I've looked at the readme at GitHub and installed the plugin. I understand how to implement it into my model (I already had the parent_id and now I've added lft and rgt), I think I can limit my model to just the acts_as_nested_set and make it belong to product, but how to load it up? Can I do @product_attributes = ProductAttributes.find(:all, :conditions => { :product_id => @product.id } and use this collection somehow (how?) in my view? Or will I need the root element and descend from there? The documentation is just way too limited for me to understand. And how will the view part work? I need to actually have a root element including a <li> tag for a child which can have an <ul> tag -only- if it has children of its own. This way I can, later on, make certain parts of the list sortable.

I hope someone can help me with this.

I might have got it using this example:

http://www.idolhands.com/ruby-on-rails/drag-and-drop-with-nested-sets-in-rails/comment-page-1/

I will have to mold this example quite a bit to be able to make children sortable within their parents and be able to drag & drop new elements to certain parents and make them sortable again. acts_as_tree might also be just enough, when including a position column. I have to look into that.

jhaagmans wrote: [...]

I'm asking this because I don't get it.

What don't you get?

[...]

I think I can limit my model to just the acts_as_nested_set and make it belong to product, but how to load it up? Can I do @product_attributes = ProductAttributes.find(:all, :conditions => { :product_id => @product.id } and use this collection somehow (how?) in my view?

Well, yes. And as far as how you use the collection...it's just an array of records like any other. I think you already know how to use it in the view. :slight_smile:

Or will I need the root element and descend from there? The documentation is just way too limited for me to understand.

Have you read the InstanceMethods and ClassMethods rdoc? They describe the extra finder methods that the class provides. You may need to build the rdoc yourself or use rdoc.info , since I don't think the rdoc is on the Web.

And how will the view part work? I need to actually have a root element including a <li> tag for a child which can have an <ul> tag -only- if it has children of its own.

You'll have to do that yourself, using the parent_id, lft, and rgt fields to determine the list nesting. Again, awesome_nested_set does absolutely nothing with the view.

Best,

Thank you Marnen, I've made it work using acts_as_tree, which can do everything I was looking for in combination with acts_as_list. I'm unsure as to what the advantages of using acts_as_nested_set over acts_as_tree are. In fact, my controller and my views are a lot easier to understand and just as powerful.

Thank you again, you pointed me in the right direction.

Kind regards, Jaap Haagmans

Thank you Marnen, I've made it work using acts_as_tree, which can do everything I was looking for in combination with acts_as_list.

NO. STOP. BAD IDEA.

Now that I've got your attention, please take a moment and read over this thread, particularly the part where I explained that nested sets can do what acts_as_tree cannot: get all descendants (no matter how deep) with a single database query. There is no way to do that at all with acts_as_tree.

I'm unsure as to what the advantages of using acts_as_nested_set over acts_as_tree are.

Your database access will be *much* more efficient. There is no reason to even consider acts_as_tree for most projects, because it is so inefficient. I know it *looks* simpler than awesome_nested_set, but beyond the simplest queries, it's inefficient and needlessly complex.

I highly recommend reading some articles (such as those by Joe Celko) on the nested-set structure. It's not all that difficult to understand, and it has many advantages over the adjacency list that acts_as_tree provides.

There's an introductory article by Joe Celko at

(don't worry about the long SQL procedures at the end), and Vadim Tropashko's comparison of SQL tree structures at DBAzine.com: Trees in SQL: Nested Sets and Materialized Path may also be worth reading. Executive summary: don't use an adjacency list for anything nontrivial.

In fact, my controller and my views are a lot easier to understand and just as powerful.

No. awesome_nested_set should not make your controller or views any harder to understand. If anything, it should make them *easier* to understand, because you do not have to do lots of database queries to retrieve the records you need.

Thank you again, you pointed me in the right direction.

You're welcome. But acts_as_tree is not the right direction.

> Thank you Marnen, I've made it work using acts_as_tree, which can do > everything I was looking for in combination with acts_as_list.

NO. STOP. BAD IDEA.

Now that I've got your attention, please take a moment and read over this thread, particularly the part where I explained that nested sets can do what acts_as_tree cannot: get all descendants (no matter how deep) with a single database query. There is no way to do that at all with acts_as_tree.

Now look at the example I gave here earlier in this thread. I've used it and it generates exactly as many queries as my acts_as_tree (which is based on his example). Not one less. So, what's that guy doing wrong? I can't figure it out and yes, I've looked into the rdoc.

> I'm > unsure as to what the advantages of using acts_as_nested_set over > acts_as_tree are.

Your database access will be *much* more efficient. There is no reason to even consider acts_as_tree for most projects, because it is so inefficient. I know it *looks* simpler than awesome_nested_set, but beyond the simplest queries, it's inefficient and needlessly complex.

Okay, but again: why? The same story as the previous one? If I can do all this with one query, I would like to know how. And I would like to know how I can get the sortable functionality to work with acts_as_nested_set. You said I should use acts_as_list, but doesn't that make the lft and rgt columns obsolete? And then again, what's the advantage of using acts_as_nested_set?

I highly recommend reading some articles (such as those by Joe Celko) on the nested-set structure. It's not all that difficult to understand, and it has many advantages over the adjacency list that acts_as_tree provides.

There's an introductory article by Joe Celko athttp://www.intelligententerprise.com/001020/celko.jhtml?_requestid=12… (don't worry about the long SQL procedures at the end), and Vadim Tropashko's comparison of SQL tree structures athttp://www.dbazine.com/oracle/or-articles/tropashko4may also be worth reading. Executive summary: don't use an adjacency list for anything nontrivial.

I'll look into it, but chances are I'm not getting this to work with the scarce information available on the net. And I really have to move on, I have so many other things to do before my next deadline.

> In fact, my controller and my views are a lot easier > to understand and just as powerful.

No. awesome_nested_set should not make your controller or views any harder to understand. If anything, it should make them *easier* to understand, because you do not have to do lots of database queries to retrieve the records you need.

Then I've caught a bad example. The problem is: it's the -only- real example I could find! And I really need examples to figure these kinds of things out!

> > Thank you Marnen, I've made it work using acts_as_tree, which can do > > everything I was looking for in combination with acts_as_list.

> NO. STOP. BAD IDEA.

> Now that I've got your attention, please take a moment and read over > this thread, particularly the part where I explained that nested sets > can do what acts_as_tree cannot: get all descendants (no matter how > deep) with a single database query. There is no way to do that at all > with acts_as_tree.

Now look at the example I gave here earlier in this thread. I've used it and it generates exactly as many queries as my acts_as_tree (which is based on his example). Not one less. So, what's that guy doing wrong? I can't figure it out and yes, I've looked into the rdoc.

I finally looked at the example you provided, and I can see why you're confused. That's a *horrible* example. This guy is using awesome_nested_set as if it were acts_as_tree: only going one level into the tree with each query (which is why he's calling @category.children each time). Doing it that way, you're absolutely right that there's no real advantage over acts_as_tree

But that's not how awesome_nested_set should be used for maximum benefit. What you should be doing is something like @root.descendants . That will -- with a single query -- load *all* descendants of @root, to infinite depth, into memory. Then you can play with them in memory without touching the database again until you need to save something.

[...]

Okay, but again: why? The same story as the previous one? If I can do all this with one query, I would like to know how.

See above. Try methods like descendants, watch the SQL, and observe the real power of the data stricture.

And I would like to know how I can get the sortable functionality to work with acts_as_nested_set. You said I should use acts_as_list, but doesn't that make the lft and rgt columns obsolete?

Not really. acts_as_list (at least, the way I use it with awesome_nested_set) only deals with ordering among siblings. lft and rgt, however, contain information that also deals with parent/child relationships.

However, you may not need acts_as_list, since unlike acts_as_tree, siblings in nested-set trees *do* have an implied ordering.

And then again, what's the advantage of using acts_as_nested_set?

It describes the tree structure. acts_as_list explicitly does not.

[...]

I'll look into it, but chances are I'm not getting this to work with the scarce information available on the net.

It's not even that difficult. Just spend your time on understanding the concept rather than looking for examples. (That said, if I find a good example, I'll let you know.)

[...]

> No. awesome_nested_set should not make your controller or views any > harder to understand. If anything, it should make them *easier* to > understand, because you do not have to do lots of database queries to > retrieve the records you need.

Then I've caught a bad example. The problem is: it's the -only- real example I could find! And I really need examples to figure these kinds of things out!

As I said above, you've caught a *terrible* example. Drop it and start from scratch. Look at the rdoc again, in particular the README and the InstanceMethods page. Don't be afraid to check out the source either; it's very clearly written. You'll be using the children and descendants methods a lot; these do what you'd expect from their names.

Best,

Marnen. you're fantastic :slight_smile:

I'm switching back to acts_as_nested_set now (getting quite fast at this switching business :wink: ) and I hope I got it right this time.

My model:

class ProductAttribute < ActiveRecord::Base   belongs_to :product   acts_as_nested_set :scope => :product_id end

Did I get this right? When I have a belongs_to association, is the scope of the nested set indeed the product_id? The rdoc is very vague in this. I might not need a scope at all, what do you recommend?

My controller now does the following:

def show   @product = Product.find(params[:id])   @root = @product.product_attributes.root   @attributes = @root.descendants end

The views are based on, simplified versions of the examples above. Basically, I show the tree for the root element through a partial, in which I go through the first layer of descendants using @attributes.each (is this the right way to go?), showing another partial which lists the child element and, if it has children, calls itself to show its children as well. This way I can use HTML lists to display the structure as it is.

If that's right, I'd like some input on how to create my repositioning method. I know how to do it using acts_as_list, but I'd like my tables to be consistent and use the lft and rgt column like I should. I know I can move them left and right easily, but I'd like to know how I can generate lft and rgt values from sortable_element, as that's what I will be using mostly.

I'm not sure how to proceed. I've got it to work the way I said earlier, but it's making seperate queries for every attribute.

@attributes is generated by: @attributes = @root.self_and_descendants

First, I want to get the root item and descend from there, so in attribute_tree:

<%= render :partial => 'attribute_row', :locals => {:attribute => @attributes.root} -%>

And then I cycle through its children. But for every child request it generates a query. So, what am I doing wrong? How to get the query results in the memory and retrieve my objects from there?