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
http://www.intelligententerprise.com/001020/celko.jhtml?_requestid=1266295
(don't worry about the long SQL procedures at the end), and Vadim
Tropashko's comparison of SQL tree structures at
http://www.dbazine.com/oracle/or-articles/tropashko4 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?