A subcategory is always a child of a category. Lets set up your
associations
as such. It will use only one db column instead of two. Each item will
belong
to a subcategory; each subcategory will belong to a category. It will
look
something like this:
Category
/
Subcategories
/ |
Items Items Items
We will use ActiveRecord’s :through association. Your models will look
like
this:
Class Category
has_many :subcategories
has_many :items, :through => :subcategory
Class Subcategory
has_many :items
Class Item
belongs_to :subcategory
When you do this, Category will associate with an Item by tunneling
through
Subcategory. These helpful methods will now be available:
Item.category
Item.subcategory
Subcategory.category
Category.items
Category.subcategories
Subcategory.items
Now, we need to do your drop down list (called a “select” in Rails)
magic. I’m
not much of a Javascript buff, so this is a dirty little hack that
should
work. The idea is to have the “subcategory” select contained in a DIV.
The
contents on the DIV will originate from an action that only deals with
creating that select, likewise it will render a partial containing only
the
select’s markup. We’ll have an observer watch the “category” select, and
send
whatever it changes to to the “subcategory” select handler, which will
in
turn re-render the select with the appropriate contents. Your
controller,
model, view, and partial should look like this:
Item Controller:
def subcategory_handler
@subcategories = Category.find(params[:category_id]).subcategories
render :partial => “subcat_select”, :locals => {:s =>
@subcategories}
end
put this in the method “new”:
@category = Category.find :all
@subcategories = @category[0].subcategories
Item model:
add this virtual attribute, so that we don’t get NoMethod errors:
attr_accessor :category_id
Partial _subcat_select.html.erb
<%= select “item”, “subcategory_id”, s.collect {|s| [ s.name, s.id ]
} -%>
View for Item#new:
(I assume that the values in your select options are the category and
subcategory IDs.)
Category
<%= select “item”, “category_id”, @category.collect {|c| [ c.name,
c.id ] }, { }, { :id => “category” } -%>
…
Subcategory
<%= render :partial => “subcat_select”, :locals => {:s =>
@subcategories} -%>
…
(This must be called AFTER your select fields are defined!)
<%= observe_field :category, :url => { :action
=> :subcategory_handler }, :with => :category_id, :update
=> “subcategory_container” %>
That’s a pretty rough hack, but I tested it on Rails 2.1.0 and it works.
It’ll
take some fine tuning on your part. There might be a completely
different way
of handling such a thing, this is only one option to explore.
Hope it makes sense.