Using acts_as_tree and belongs_to together


#1

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


#2

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.


#3

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.


#4

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

Cheers,
– Nicky