Ask A Question

Notifications

You’re not receiving notifications from this thread.

Is there a better way to simplify this?

Alan Reid asked in Rails

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 %>
Reply

.proucts.any

Reply

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

Reply

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.

Reply

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

Reply

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

Reply

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?

Reply

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 %>
Reply

does it have to be "Products_count"?

Reply

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

Reply
Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 86,946+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.