Reputation: 15384
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
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
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
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
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