# Using acts\_as\_tree and belongs\_to together

**URL:** <https://www.ruby-forum.com/t/using-acts-as-tree-and-belongs-to-together/49503>\
**Category:** Rails\
**Created:** [December 5, 2005, 2:49pm UTC](https://www.ruby-forum.com/t/using-acts-as-tree-and-belongs-to-together/49503 "2005-12-05T14:49:00Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![nmkolev](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/n/48db29/32.png) [@nmkolev](https://www.ruby-forum.com/u/nmkolev)\
**Post date:** [December 5, 2005, 2:49pm UTC](https://www.ruby-forum.com/t/using-acts-as-tree-and-belongs-to-together/49503/1 "2005-12-05T14:49:00Z")

</div>

Hi all,

I sat down to rework one of my first rails apps which was a very,  
very simple store. The organisation of the products in the store was  
rather flat:

class Category \< AR:B  
has\_many :products  
end

class Product \< AR:B  
belongs\_to :category  
end

I want to do it with arbitrary category nesting this time–a category  
can have a subcategory, this on the other hand can have other  
subcategories and so on. Only categories that have no other  
subcategories can hold products. So far, so good:

class Category \< AR:B  
acts\_as\_tree  
has\_many :products  
end

class Product \< AR:B  
belongs\_to :category  
end

But how do I prevent users from entering products in categories that  
have subcategories? Also, how do I present the user with an interface  
for selecting the parent category in the new-product form? Nested  
select elements perhaps (one select with all the ‘root’ categories,  
selecting a root category triggers the appearance of another select  
element with its subcategories and so on until the last selected  
category has no more children)?

Many thanks in advance for all pointers!

Nickolay

---

<div class="post-metadata">

**Author:** ![nmkolev](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/n/48db29/32.png) [@nmkolev](https://www.ruby-forum.com/u/nmkolev)\
**Post date:** [December 6, 2005, 3:41am UTC](https://www.ruby-forum.com/t/using-acts-as-tree-and-belongs-to-together/49503/2 "2005-12-06T03:41:07Z")

</div>

Nickolay K. wrote:

> class Product \< AR:B  
> belongs\_to :category  
> end

I’d suggest either having a separate LeafCategory model (which shares  
Category model’s ids (no auto\_increment), to which the product belongs,  
and which itself belongs to the Category model), or alternatively have  
a field in the Category model designating that category as a leaf  
category.

> But how do I prevent users from entering products in categories that  
> have subcategories? Also, how do I present the user with an interface  
> for selecting the parent category in the new-product form? Nested  
> select elements perhaps (one select with all the ‘root’ categories,  
> selecting a root category triggers the appearance of another select  
> element with its subcategories and so on until the last selected  
> category has no more children)?

I’ve written a combination of a helper and an AJAXified component  
that implements chained selects for category selection. You just  
put a call to the helper in a form in your view:

```
category_selector( object, method, options = {}, html_options = {} )

```

e.g.

\<%= category\_selector( :product, :category\_id,  
{:model =\> :product\_category, :all =\> true, :on\_change =\>  
{:controller =\> ‘store’, :action =\> ‘category\_change’}},  
:class =\> ‘catsel’ ) %\>

The :all option specifies whether the selected category (is put in a  
hidden field)  
must be a leaf category, or whether non-leaf (“all categories”/“all  
subcategories”)  
can be selected.

It seems to be working fine, but I wasn’t planning to release it until I  
had  
tested it further and implemented an indication that communication with  
the  
server was in progress.

I also wanted to have a different mode of operation, specified in the  
helper options, in which the whole category tree is loaded into  
JavaScript  
so the appearance and disappearance of select levels is virtually  
instantaneous  
after an initial load time. You could even get the helper to  
automatically  
choose the best method based on the size of the category tree and the  
speed  
and latency of the link.

–  
We develop, watch us RoR, in numbers too big to ignore.

---

<div class="post-metadata">

**Author:** ![nmkolev](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/n/48db29/32.png) [@nmkolev](https://www.ruby-forum.com/u/nmkolev)\
**Post date:** [December 6, 2005, 4:13am UTC](https://www.ruby-forum.com/t/using-acts-as-tree-and-belongs-to-together/49503/3 "2005-12-06T04:13:37Z")

</div>

Nickolay K. wrote:

> But how do I prevent users from entering products in categories that  
> have subcategories?

Of course the simplest method is to validate incoming category\_ids with  
Category.find(category\_id).children.empty?

–  
We develop, watch us RoR, in numbers too big to ignore.

---

<div class="post-metadata">

**Author:** ![nmkolev](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/n/48db29/32.png) [@nmkolev](https://www.ruby-forum.com/u/nmkolev)\
**Post date:** [December 6, 2005, 9:30am UTC](https://www.ruby-forum.com/t/using-acts-as-tree-and-belongs-to-together/49503/4 "2005-12-06T09:30:30Z")

</div>

> > But how do I prevent users from entering products in categories  
> > that have subcategories?
> 
> Of course the simplest method is to validate incoming category\_ids  
> with  
> Category.find(category\_id).children.empty?

I was thinking:

class Category \< AR:B  
…  
def is\_leaf?  
0 == children.size  
end  
end

class Product \< AR:B  
…  
private  
def validate  
errors.add(:category\_id, ‘Category schould be a leaf’) unless  
Category.find(@params[:category\_id]).is\_leaf?  
end  
end

This (or something along those lines) would prevent Product object  
from being saved if they do not belong to a leaf category. But I find  
this should be enforced in the edit/create form as well. I am very  
interested in the category\_selector helper if you care to post it.  
Perhaps we could iron out some stuff together. 🙂

Cheers,  
– Nicky
