Count the number of records in group_by clause - ruby-on-rails

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.

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

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"]}

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! :)

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.

Related

Rails 4 search function

I have this simple search function:
class SearchController < ApplicationController
def index
if params[:search].blank?
redirect_to root_path
else
#results = Post.search(params[:search])
end
end
end
I want to implement the following functionalities but I am struggling to code:
1.How to record each of the input search terms and see whether which search terms are the most searched ones. I thought of using "first_or_create" method...
2.Give I have this title: "Peter Paul Mary", how do I split them and link_to each terms to search the search function
Please advise.
1.I think you can use two-dimensional array containing results of query for each single term.
#maching_posts = Array.new
#terms = params[:search].split
#terms.each do |term|
result = Post.where(title: term)
#maching_post << result
end
Now, you have array '#maching_post' containing results of query for each single term. Row [0] contains result of first term etc.
You can use this array to generate view. Like this:
<% #terms.each_with_index do |term, index| %>
<span>Results of <%= term %></span>
<% #maching_post[index].each do |post| %>
<%= post.title %></br>
<% end %>
<% end %>
2.To get single words from string you can use split() method.
"Peter Paul Mary".split
This method returns array ["Peter", "Paul", "Mary"]
To link each term, use
link_to 'term', controller: :search, action: :index, search: 'term'
You should use this method in loop, like
<% "Peter Paul Mary".split.each do |term| %>
<%= link_to term, controller: :search, action: :index, search: term %>
<% end %>

Rails - Unique Array in View

Having a Bit of trouble displaying unique results from my database. I have a database called "Activities". Each Activity has an associated Sport through sport_id. There may be many activities with the same sport_id.
I want to display a list of all sports linked to the activities database without displaying (for example "Football") twice.
FYI : Venues have many Facilities and Facilities have many Activities.
Controller:
#sports = Sport.all
#activities = Activity.paginate(page: params[:page])
#facilities = Facility.where(venue_id: #venue.id)
View:
<% #facilities.each do |f| %>
<% #activities.find(:all, :conditions => "facility_id == #{f.id} ").each do |a| %>
<li><%= Sport.find(a.sport_id).name %>, (<%= a.facility_id %>)</li>
<% end %>
<% end %>
This shows:
Football, (2)
Hockey, (2)
Hockey, (2)
Football, (5)
I would like to display just:
Football
Hockey
Any ideas?
A simple solution would be to reduce your array with ruby in the view using: uniq!
<% #facilities.each do |f| %>
<% #activities.find(:all, :conditions => "facility_id == #{f.id} ").uniq! { |a| a.sport_id }.each do |a| %>
<li><%= link_to Sport.find(a.sport_id).name, Sport.find(a.sport_id) %></li>
<% end %>
<% end %>
Another way may be to perform a single query on your DB since Sport what you want to narrow down
In controller:
#sports = Sport.joins(activities: [facility: :venue]).where(facilities: { venue_id: #venue.id }).distinct
In view:
<% #sports.each do |sport| %>
<li><%= link_to sport.name, sport %></li>
<% end %>
I am not sure about your DB schema so I went with what I thought you would have done, but it might needs some tweakings.
I hope I helped you.
try to use reject before each
<% #facilities.reject{your condition here}.each do |f| %>

grouping children by parent attribute

Here's what I am trying to achieve:
Group_x.name
member1.name -- member1.join_date -- etc
member2.name -- member2.join_date -- etc
...
Group_y.name
member1.name -- member1.join_date -- etc
member2.name -- member2.join_date -- etc
...
What I'm going for is really very similar to this although the implementation there doesn't work for me.
I've gotten this far in my controller:
def index
# https://stackoverflow.com/a/17835000/2128691
#user_group_ids = current_user.student_groups.map(&:id)
#students = Student.where('student_group_id IN (?)', #user_group_ids)
# https://stackoverflow.com/a/10083791/2128691
#students_by_group = #students.uniq {|s| s.student_group_id}
#title = "All students"
end
and calling the following in my view -
<% #students_by_group.all.each do |x| %>
<p>
<%= "#{x}" %>
</p>
<% end %>
gives me a list of all student objects. if i call <%= "#{x.name}" %> or <%= "#{x.created_at}" %>, etc, I get the correct information, and everything is great.
But now that I have all this information, how can I put the group.name (in my code it would be x.student_group.name) as a header for all of the students for which that group_name is true?
I think you need to use group_by on #students_by_group like this:
#students_by_group = #students_by_group.group_by { |s| s.student_group }
This would return a hash with the keys being the student group objects and the values being the students that belongs to this group, then you can do this in your view:
<% #students_by_group.each do |group, students| %>
<h3><%= group.name %></h3>
<% students.each do |x| %>
<p>
<%= "#{x}" %>
</p>
<% end %>
<% end %>
As an additional note, the group_by would fire a query for each student, so you may want to eagerly load the student group for each student like this for some performance gain:
#students = Student.where('student_group_id IN (?)', #user_group_ids).includes(:student_group)

Rails: Retrieve value from hash in array

There's a good number of related questions but their answers haven't helped me. I have a method fetch_all_sections which populates an array with this line:
all_sections << {:id => section.id, :sortlabel => section.sortlabel, :title => section.title, :depth => depth}
In a loop in a view, I would like to easily access the values by their key, like this:
<% fetch_all_sections(#standard).each do |section| %>
<%= section.id %>
<% end %>
This says no method id on section. section[:id] and #{section['id']} have similarly themed errors. I used a hash for ease of retrieval - should I use a different structure?
I'm hoping I don't need .map like section.map { |id| id[:id] } for every value.
EDIT: Here's the context. It's a little loopy (pun intended) but it does what's intended.
# Calls itself for each section recursively to fetch all possible children
def fetch_all_sections(standard, section = nil, depth = 0)
all_sections = []
if section.nil?
rootsections = standard.sections.sorted
if ! rootsections.nil?
rootsections.each_with_index do |section, i|
depth = section.sortlabel.split('.').length - 1
all_sections.push(fetch_all_sections(standard, section, depth))
end
end
else
all_sections << {:id => section.id, :sortlabel => section.sortlabel, :title => section.title, :depth => depth}
section.children.sorted.each do |section|
all_sections | fetch_all_sections(standard, section)
end
end
return all_sections
end
Try with the following:
<% fetch_all_sections(#standard).each do |section| %>
<%= section['id'] %>
<% end %>
If not working, try debugging using these methods:
<% fetch_all_sections(#standard).each do |section| %>
<%= section.inspect %>
<%= section.class %>
<% end %>
As the Question author said, this fixed:
all_sections << fetch_all_sections(standard, section, depth).first
And tell us the output of the inspect

Separate "each" to "types" in Rails view

In a State, we have Shops, with database column type in each shop entry: Grocery, Fashion and Food.
I have the following code:
<% #country.states.each_with_index do |state, i| %>
State <strong><%= i + 1 %></strong>
<% state.shops.each do |shop| %>
=====showing attributes of each shop=====
<% end %>
<% end %>
In the code above, all shops will be listed according to IDs ascending:
Shop ID1 (Type = Food)
Shop ID2 (Type = Fashion)
Shop ID3 (Type = Grocery)
Shop ID4 (Type = Fashion)
Shop ID5 (Type = Food)
Instead of outputting each, I would like to show in the preferred type order:
Fashion
Shop ID2
Shop ID4
Grocery
Shop ID3
Food
Shop ID1
Shop ID5
I tried to use the if state.shops.type == 'Fashion' statement, but doesn't work. What is the proper way to write this?
Thanks.
Use the group_by method:
<% #country.states.each_with_index do |state, i| %>
State <strong><%= i + 1 %></strong>
<% state.shops.group_by(&:type) do |type, shops| %>
<h3><%= type %></h3>
<% shops.each do |shop| %>
=====showing attributes of each shop=====
<% end %>
<% end %>
<% end %>
Following the earlier comments there doesn't seem to be any logic to the order that you want. So either you should implement an order attribute like an integer which you can use to sort by in the query. Otherwise you will have to create a block which will do static checks and do custom sorting, perhaps something like this:
#custom_order = {"Fashion" => 0, "Grocery" => 1, "Food" => 2}
<% state.shops.group_by(&:type).sort { |a, b|
#custom_order[a.first] <=> #custom_order[b.first]
} do |type, shops| %>
As you see, it gets a little messy so I would probably add an order attribute as I mentioned and then do like wuputah said but change to
state.shops(:order => "order ASC").group_by(&:type)

Resources