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

jhaagmans wrote:

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.

I think you did that part right.

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.

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:

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.

I really hope you can help.

Thank you!

Jaap H.

Best,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

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?

[email protected]

Posted viahttp://www.ruby-forum.com/.

Thanks again.

Thank you again!

On 18 aug, 23:38, Marnen Laibow-Koser <rails-mailing-l…@andreas-
s.net> wrote:

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

  • tag for a child which can have an
      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:
    […]

    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,

    Marnen Laibow-Koser
    http://www.marnen.org
    [email protected]

    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

  • tag for a child which can have an
      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,

    Marnen Laibow-Koser
    http://www.marnen.org
    [email protected]

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

    On Aug 19, 1:10 pm, jhaagmans [email protected] wrote:

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

    On Aug 19, 3:31 pm, jhaagmans [email protected] wrote:

    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 Laibow-Koser
    http://www.marnen.org
    [email protected]

    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.

    On 19 aug, 19:35, Marnen Laibow-Koser [email protected] wrote:

    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?

    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!

    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?