I am working on a Rails 3 application, and am having some trouble with my user/edit view. In this application, Users can subscribe to Categories. Users and Categories have a
has_and_belongs_to_many relationship through a join table (
In my user/edit view, I need to display a list of checkboxes for the various categories so the user can edit his subscriptions. I am using the following code to achieve this:
<div id="category-checklist"> <% Category.roots.order('name ASC').each do |category| %> <ul> <li class="parent <%= category.name %>"> <%= check_box_tag "user[category_ids]", category.id, @user.categories.include?(category) %> <span><%= category.name %></span> </li> <ul id="children-<%= category.id %>" class="children-list"> <% category.children.order('name ASC').each do |child| %> <li class="child <%= child.name %>"> <%= check_box_tag "user[category_ids]", child.id, @user.categories.include?(child) %> <span><%= child.name %></span> </li> <% end %> </ul> </ul> <% end %> </div>
Category model uses the ancestry gem for hierarchy, which is there the
children methods come from. Roots returns all the parent categories, and children returns all of the sub-categories for a given parent.
The really problematic part of this code seems to be
@user.categories.include?(child) which is running 81 separate queries (one for each child category). This line is used to check if the category is currently subscribed to by the user -- if so, the checkbox is marked checked. Is there a better/faster way to accomplish this?
For reference, here is the join table's schema definition - I tried to put an index on the foreign keys thinking it would speed things up, but this is still very slow compared to the rest of my app.
create_table "categories_users", :id => false, :force => true do |t| t.integer "category_id" t.integer "user_id" end add_index "categories_users", ["category_id"], :name => "index_categories_users_on_category_id" add_index "categories_users", ["user_id"], :name => "index_categories_users_on_user_id"