Skip to main content

Is there a better way to simplify this?

Rails • Asked by Alan Reid

Hi again,
I want to know if there is a better way to get a list of categories which only have products.

I have tried to do @categories = Category.where(category_type: 'product').order(name: :asc).products.any but that seems to error and i am not sure why.

My Category has_many :products and my product belongs_to :category so i assumed this would work.

In my controller i am am getting a list of categories.

def index
  @categories = Category.where(category_type: 'product').order(name: :asc)
end

Then populating them on the page like as per below... now this work but its dirty and ends up with loads of calls to the DB which i don't personally like haha.

<% @categories.each do |category| %>
  <% if category.products.any? %>
    <div class="pad1 list-row">
      <div class="space-left4 pad1x row-details">
        <!-- Details -->
        <div class="details-name"><%= category.name %></div>
        <div class="details-sub">Products: <%= category.products.count %></div>
      </div>
      <div class="row-actions"></div>
    </div>
  <% end %>
<% end %>


That's a typo on here where I typed it out. In the code it says .products.any. Updated the post, thanks for spotting


One thing you could do is to set up a counter cache on Category so that you can store the number of products on it. Then when you query you can query for Category.where("products_count > 0") to improve your query and remove that if statement in the view.


Wouldn't there be an includes(:product) somewhere to avoid too many request to the DB?


Yup, once you've gathered your categories that have products efficiently, you can preload them with includes as well.


So i would set up the counter cache like so?

belongs_to :category counter_cache: true

The i could use the Category.where("products_count > 0") query. The i assume with those results i could get the products using 'includes(:product)' like we did before?


For the most part, yes. You also need to add a products_count integer column to categories, default it to 0, and then update the records in the database to have the correct amount. Then your counter_cache will update that column and your query of Category.where("products_count > 0") will work.

Your view just references the products count, so you can actually print out the new column value without loading any products making it even more efficient.

<% @categories.each do |category| %>
  <div class="pad1 list-row">
    <div class="space-left4 pad1x row-details">
      <div class="details-name"><%= category.name %></div>
      <div class="details-sub">Products: <%= category.products_count %></div>
    </div>
    <div class="row-actions"></div>
  </div>
<% end %>

does it have to be "Products_count"?


You can customize it, but that's the inferred default name.


Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 27,623+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.