Rails - find the team with the most superheroes - ruby-on-rails

I want to find the team with the most superheroes. I did some googling, but still cannot figure out what to do as I think there is a lot of SQL involved. Basically, I want the view file to display: "The team with the most superheroes is [team name] and the number of superheroes in this team is [number]." Your help would much be appreciated as I am only a beginner in Rails.
This is the best I could come up with in my view file:
<% current_user.teams.each do |team| %>
<%= team.superheroes.count%>
<% end %>
user.rb
class User < ApplicationRecord
has_many :superheroes
has_many :teams, through: :superheroes
end
superhero.rb
class Superhero < ApplicationRecord
belongs_to :user
has_many :superhero_teams
has_many :teams, through: :superhero_teams
end
team.rb
class Team < ApplicationRecord
has_many :superhero_teams
has_many :superheroes, through: :superhero_teams
end

teams = Team.left_joins(:superheroes)
.group(:id) # group by superhero id
.select(
# teams.*
Teams.arel_table[Arel.star],
# COUNT(superheroes.*) AS superheroes_count
Superhero.arel_table[Arel.star].count.as('superheroes_count')
)
.order(superheroes_count: :desc)
teams.each do |team|
puts "#{team.name} has #{team.superheroes_count} members"
end
This selects a count of the number of joined rows and orders the results by it. An alternative is to add a counter-cache to the association which makes for more effective read queries at the price of an extra UPDATE query when creating the records.

Can you try this?
Superhero.joins(:teams).group('superheros.id').order("count(superheros.id) DESC")
It will arrange the no of superhero in each team, with the largest count at top.
So the first record of the query will be the answer you are looking for.
You Can also re-check the result by looking at the counts
Superhero.joins(:teams).group('superheros.id').order("count(superheros.id) DESC").count("superheros.id")

Related

Finding siblings through a joins table in Rails

I have models User, Group, and Membership with the following structure:
class User < ApplicationRecord
has_many :memberships
has_many :groups, through: :memberships
end
class Group < ApplicationRecord
has_many :memberships
has_many :users, through: :memberships
end
class Membership < ApplicationRecord
belongs_to :user
belongs_to :group
end
Basically, Membership is the joins table for User and Group.
Given a User user, how can I find its sibling users who all belong to at least one of the same group? That is, something like
user.groups.users # assuming such a line were possible
I'd like to do it in a single query and solely in Active Record, but I'm OK with two queries if that's faster or much more readable. (The DB language is PSQL if that helps as well.)
There are some ways to combine JOINs and sub-selects to get exactly one DB query, try this one:
User
.joins(:memberships)
.where.not(id: user.id)
.where(
memberships: {
group_id: user.memberships.select(:group_id)})
P.S. Don't forget about indexes on all *_id columns to get fast queries.
P.P.S. One more way: 2 sub-selects, 1 DB query. Test which one suits your requirements better:
user_groups_rel = user
.memberships
.select(:group_id)
groups_users_rel = Membership
.select(:user_id)
.where(group_id: user_groups_rel)
User
.where.not(id: user.id)
.where(id: groups_users_rel)
Try the following by using includes:
groups = User.groups.includes(:users)
groups.each do |group|
puts "user = #{group.user}"
end

How to group records, by a relation name?

I'm using Rails 4.2, Chartkick & GroupDate gems, To display statistics about purchased orders.
order.rb
class Order < ActiveRecord::Base
...
has_many :ice_creams
...
end
ice_cream.rb
class IceCream < ActiveRecord::Base
...
has_and_belongs_to_many :flavors
has_many :added_extras
has_many :extras, :through => :added_extras
belongs_to :order
...
end
extra.rb
class Extra < ActiveRecord::Base
...
has_many :added_extras
has_many :extras, :through => :added_extras
...
end
My intention was to display a chart showing the following:
Count of overall purchased Orders or Ice Creames grouped by Extra's Name.
In another expression, The last chart is intended to display the most popular Extras customers consume.
graphs.html.erb
<%= line_chart IceCreams.all.joins(:added_extras).group(:extra_id).count %>
The chart actually works but I cant' get it to display the Extra name, Or even group by Extra name.
Anyway my question is...
How to be able to display the Extra name instead of id?
You can join Extra table through AddedExtra table. Try below:
IceCreams.joins(added_extras: :extra).group('extras.name').count

Retrieve only associated records

I need to find out the records which are at least associated to any one of the records.
I've this relationship:
Class Category < ActiveRecord::Base
has_many :services
end
Class Service < ActiveRecord::Base
has_many :team_leaders, through: :allocations
has_many :workers, through: :allocations
end
I need to find out only those services which has at least one worker or one teamleader associated to it. How to do it?
I don't know how you could do this without writing some SQL, but this is how I would have done it:
Service.includes(:team_leaders, :workers).where('team_leaders.id is not null OR workers.id is not null').references(:team_leaders, :workers).all
Edit: adding .references (see comments below)
c = Category.first
c.services.each do |service|
if Allocation.exists?(:service_id => service.id)
puts service.name
puts service.service_name
end
end
It will list only those services which has associated workers and team_leaders.
Try this query
Service.joins(:team_leaders, :workers).where('team_leaders.id is not null or workers.id is not null')

Reaching 'tickets' that belongs to 'projects' that a user owns

In the project management app I'm working on, I'm currently working on a page for managing tickets, which I want should contain of the following:
- The tickets that the user has created
- The tickets that belongs to projects that the user has created
The tricky part is to use the right code in the controller, which is where I need help.
'#users_tickets'works fine, as well as '#owned_projects'. However, the last thing which is creating an array that contains of the tickets that belongs projects that the user owns, is something I need help me (yes, I understand that my poor try with an each loop is totally the wrong way to go here).
How can I achieve what I want?
Tickets controller:
1. def manage
2. #users_tickets = Ticket.where(:user_id => current_user.id)
3. #owned_projects = Project.where(:user_id => current_user)
4.
5. #owned_projects.each do |project|
6. #tickets_for_owned_projects = Ticket.where(:project_id => project.id)
7. end
8. end
Tables:
tickets table:
project_id
ticket_status_id
user_id
title
description
start_date
end_date
projects table:
user_id
title
description
start_date
end_date
If you're using a has_many association, then it should as simple as
class User < ActiveRecord::Base
has_many :projects
has_many :tickets
has_many :project_tickets, through: :projects, class_name: 'Ticket', source: :tickets
#...
end
class Project < ActiveRecord::Base
has_many :tickets
#...
end
# in tickets controller
def manage
#tickets = current_user.tickets
#tickets_for_owned_projects = current_user.project_tickets
end
UPD: The approach above should work. I'm literally falling asleep right now and can't define what is wrong here. Would really appreciate if someone looked into it.
Here's another way around though.
class User < ActiveRecord::Base
has_many :projects
has_many :tickets
def project_tickets
result = []
self.projects.each do |project|
result << project.tickets
end
result.flatten
end
#...
end

How do I sum a many to many value?

Each User can have many Resources, and each of those Resources has many Votes, and each of those votes have a value attribute that I want to sum all that particular users resources.
If I were to type this in a syntactically incorrect way I want something like...
#user.resources.votes.sum(&:value), but that obviously won't work.
I believe I need to use collect but I am not sure?
This is the closest I got but it prints them out, heh
<%= #user.resources.collect { |r| r.votes.sum(&:value) } %>
I'd recommend setting up a has_many :through relationship between the User and Vote objects. Set the models up like this:
class User < ActiveRecord::Base
has_many :resources
has_many :votes, :through => :resources
end
class Resource < ActiveRecord::Base
belongs_to :user
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :resource
end
Once this is done you can simply call user.votes and do whatever you want with that collection.
For more info on has_many :through relations, see this guide: http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
How can you tell who voted having a Vote instance? Your Vote model has to have voter_id field and additional association:
# in Vote.rb
belongs_to :voter, class_name: 'User', foreign_key: 'voter_id'
And in your User model:
# in User.rb
has_may :submited_votes, class_name: 'Vote', foreign_key: 'voter_id'
So, #user.votes (as David Underwood proposed) will give you #user resources' votes. And #user.submited_votes will give you votes submitted by the #user.
Using just User <- Resource <- Vote relation won't allow you to separate some user's votes made by him and votes made for its resources.
For a total sum this should work or something real close.
sum = 0
#user.resources.each do |r|
r.votes.each do |v|
sum += v.value
end
end
This might work for you:
#user.resources.map {|r| r.votes.sum(:value)}.sum
How many records do you have, there is a way to push this to the database level I believe, I would have to check, but if it is only a few records then doing this in ruby would probably be ok
Try this code
#user.resources.map(&:votes).flatten.map(&:value).sum

Resources