Richlewis
Richlewis

Reputation: 15384

Count the number of records in group_by clause

I have the following categories grouped together:

@categories = Category.all.group_by { |c| c.name }

In my view I am displaying the category names like so:

<% @categories.each do |c, v| %>
  <li><%= link_to c, blog_path(:name => c) %></li>
<% end %>

Which gives this for example:

Ruby
Ruby On Rails
CSS

What I want to achieve is next to each category name have the total number of posts with that category name, so:

Ruby(2)
Ruby On Rails(10)

So I have tried:

@categories = Category.joins(:posts).all.group_by { |c| c.name }

Which results in only the categories with a post object being displayed (previously all categories would display, regardless of whether they had a post object) and in my view I tried:

<% @categories.each do |c, v| %>
  <li><%= link_to c, blog_path(:name => c) %><%= c.count %></li>
<% end %>

This is outputting nothing. I'd like to find how to approach this before I confuse the matter.

Upvotes: 1

Views: 765

Answers (4)

Mike Campbell
Mike Campbell

Reputation: 7978

I'm not sure why you're using group_by. However, you don't need a join because you don't have any condition on posts. Other examples suggest eager loading posts, but that seems overkill to initialise all the post objects in memory to just get the count of them. You'd need to do your own benchmarks though. Consider a counter_cache like another answerer suggested.

@categories = Category.all

and in the view:

<% @categories.each do |c| %>
  <li><%= link_to c.name, blog_path(:name => c.name) %> (<%= c.posts.count %> posts)</li>
<% end %>

Further explanation

group_by returns a hash with the key as the unique return value from the block, and the value are all items of the original array for which the block evaluates to that key. Taking your example of Category.all (the scope is converted to an array before group_by so that's how we'll represent it here):

cats = [
         #<Category name: "foo" ... >,
         #<Category name: "bar" ... >,
         #<Category name: "baz" ... >
       ]

These three categories have unique names, so using .group_by { |c| c.name } does nothing but create a pointless hash with the keys as the name, and each value as an array with one Category object like:

{
  "foo" => [#<Category name: "foo" ... >],
  "bar" => [#<Category name: "bar" ... >],
  "baz" => [#<Category name: "baz" ... >]
}

Here's an example of where you might use group_by to some effect:

languages = ["Ada", "C++", "CLU", "Eiffel", "Lua", "Lisp",
             "Perl", "Python", "Smalltalk"]
languages_grouped_by_first_letter = languages.group_by { |s| s[0] }     
=> {"A"=>["Ada"], "C"=>["C++", "CLU"], "E"=>["Eiffel"], "L"=>["Lua", "Lisp"], "P"=>["Perl", "Python"], "S"=>["Smalltalk"]}

Upvotes: 1

Pavan
Pavan

Reputation: 33542

What you are looking for is called counter_cache.

In our Post model,set counter_cahe => true

class Post < ActiveRecord::Base

belongs_to category,:counter_cache => true

end

Add posts_count column to categories table and do like this

@category_groups = Category.find(:all)

<% @category_groups.each do |c| %>
  <li><%= link_to name, blog_path(:name => c) %>(<%= posts_count)</li>
<% end %>

Look this Railscast For implementing this.

Upvotes: 0

Max Williams
Max Williams

Reputation: 32955

It's confusing to call the grouped categories @categories because that makes it sound like a collection of Category objects, when it's actually a Hash. Using descriptive names, including in your loop, makes your code much clearer.

Try this:

@category_groups = Category.includes(:posts).all.group_by { |c| c.name }

and the view

<% @category_groups.each do |name, categories| %>
  <li><%= link_to name, blog_path(:name => name) %> (<%= categories.map{|category| category.posts.size}.sum %> posts)</li>
<% end %>

Upvotes: 5

Himesh
Himesh

Reputation: 646

Use Following code

@categories = Category.all.group_by { |c| c.name }

Write a Hepler to find post_count

def post_count(category_ids)
  Post.where(:category_id => category_ids)
end

In View:

<% @categories.each do |c, v| %>
  <li><%= link_to c, blog_path(:name => c) %>(<%= post_count(v.map(&:id)) %>)</li>
<% end %>

Hope this helps! :)

Upvotes: 0

Related Questions