Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

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 (categories_users).

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>

The Category model uses the ancestry gem for hierarchy, which is there the roots and 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"

Thanks for any help!

share|improve this question
add comment

1 Answer

up vote 2 down vote accepted

When you load the user, you can include the categories using eager loading.

@user = User.includes(:categories).find(params[:id])

or

@user = User.find(params[:id])
@categories = @user.categories.all
share|improve this answer
    
Thanks - you know, I had it setup this way in my users controller and I didn't realize that Devise moves everything to use their registrations_controller. I added "@categories = @user.categories.all" to the Devise controller's edit method and page load dropped exponentially! Thanks for pointing me in the right direction. –  Jim Aug 2 '12 at 15:52
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.