Identifying Rails Query Results - ruby-on-rails

I have three models User, Game and Point where a user gets points for playing games. What I'm trying to do is display the users with the most points for the most popular games in a view.
I used this question to determine the most popular games. So I now have this scope in Game.rb:
scope :most_popular_games,
select("games.id, name, count(points.id) AS points_count").
joins(:points).
group("games.id").
order("points_count DESC").
limit(5)
In my controller, I have this:
#most_popular_games = Game.most_popular_games
My models:
Models
class Point < ActiveRecord::Base
belongs_to :game
belongs_to :user
end
class Game< ActiveRecord::Base
has_many :points
end
class User < ActiveRecord::Base
# no relationship for points or games
end
class GameRank < ActiveRecord::Base
belongs_to :game
belongs_to :user
end
However, what I can't figure out what to do is create a way to now total the points per user for each of these games and make it so I identify each game differently, so I segment them out on the view (ie show the results separately for each game).
I tried adding this in the code, but I wasn't sure of the best way to make each game's results be identifiable in the view:
#most_popular_games.each do |most_popular_game|
most_points_for_popular_game = GameRank.where("game_id =?", most_popular_game.id).order('total_points desc').limit(10)
end
My question is basically how do I reuse the results for "most_points_for_popular_game" - which is the users with the most points for a given game - for each of the five games (#most_popular_games = five results)?

Totally ignoring N+1 queries and additional markup:
Add the :game_ranks relationship to Game:
class Game
has_many :game_ranks
end
Add a scope to GameRank:
class GameRank
scope :top, order('total_points desc').limit(10)
end
(I'm assuming a total_points column on GameRank based on your example)
In your view:
<% #most_popular_games.each do |game| %>
<%= game.name %>
<% game.game_ranks.top.each do |rank| %>
<%= rank.user.name %>,<%= rank.total_points %>
<% end %>
<% end %>

Related

How to loop through a joined table

The models I have:
Category:
class Category < ApplicationRecord
has_many :categorizations
has_many :providers, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Provider:
class Provider < ApplicationRecord
has_many :categorizations
has_many :categories, through: :categorizations
accepts_nested_attributes_for :categorizations
end
Categorization:
class Categorization < ApplicationRecord
belongs_to :category
belongs_to :provider
has_many :games, dependent: :destroy
accepts_nested_attributes_for :games
end
Game:
class Game < ApplicationRecord
belongs_to :categorization
end
I need to display the games, that belongs to a specific provider. I tried to do it like:
<% #provider.categorizations.joins(:games).each do |game| %>
<%= game.title %>
<% end %>
It gives me an error: NoMethodError: undefined method 'title' for #<Categorization:0x007f2cf6ee49e8>. So, it loops through the Categorization. What is the best way to loop through the joined games table? Thanks.
First, you should do the request in your controller, or even better call a scope (defined in a model) from the controller.
Do not forget that Active Record is just an ORM, a tool allowing you to manipulate SQL.
With #provider.categorizations.joins(:games) you are not asking for games. You are asking for the categorizations and you do a JOIN with the games table. This joins is usually to allow to filter by games attributes.
To do what you want you should do the following :
#games = Game.joins(:categorization).where('categorization.provider_id = ?',#provider.id)
As you can see, the join do not return categorization, it allow me to use categorization as a filter.
You should always be aware of the SQL generated by Active Record. Look at the SQL query generated in your server's traces.
I'm guessing 'title' is an attribute of games and not categorization, so you either need to return an array of games, or add a select on the end to pull the title attribute into the categorization object, like so:
<% #provider.categorizations.joins(:games).select('dba.games.title').each do |game| %>
<%= game.title %>
<% end %>
Just to add- you shouldn't really be doing this in the view file. I'd go as far as not even doing this in the controller. I tend to encapsulate this sort of logic into a service class, which is instantiated in the controller to return a set of results. The controller should only be passing the result set on, which is then presented by the view.
class Provider < ActiveRecrord::Base
# this could be a scope instead, or in a seperate class which
# the provider model delegates to- whatever floats you boat
def get_games
# you could use pluck instead, which would return an array of titles
categorizations.joins(:games).select('dba.games.title')
end
end
class ProviderController < ApplicationController
def show
provider = Provide.find(params[:id])
#games = provider.get_games
end
end
<% #games.each do |game| %>
<%= game.title %>
<% end %>

How to Display count of subscribers for particular speaker

I want to display count of subscribers for particular speaker in my index page. I didnt do anything in my route because i am simply looping in my index page. I have working listenerspeakers association. In my pages controller i have
class PagesController < ApplicationController
def home
#messages = Message.all.order("created_at DESC")
#speakers = Speaker.all
#sub = ListenersSpeakers.all
end
end
in my view i try to loop and insert this code but it doesnt work.
<% #sub.each do |sub| %>
<%= #sub.speaker.count %> Subscribers</span>
<% end %>
my model
class ListenersSpeakers < ActiveRecord::Base
belongs_to :listener
belongs_to :speaker
end
Update
my Speaker model has
has_many :listeners_speakers, class_name: 'ListenersSpeakers'
So I assume that in the Speaker model, it has_many :listener_speakers like:
class Speaker < ActiveRecord::Base
has_many :listener_speakers
end
You can then use
#sub.speaker.listener_speakers.count
in the view to get the number of listeners.
Not related to the question, but you may also want to consider adding a:
has_many :listeners, through: :listener_speakers
as that may eventually prove useful.

Selection of elements based on other elements

class Teacher < ActiveRecord::Base
has_many :students
end
class Class <ActiveRecord::Base
has_many :students
end
class Student <ActiveRecord::Base
belongs_to :teacher
belongs_to :class
end
I want to create a list of teachers, and below their names: table with classes from which this teacher has students and number of this students. More or less something like this:
Teacher XYZ:
Class 1A | 3 students
Class 3D | 2 students
How can I check if teacher has students from each class and later count only the students that belongs to both this particular teacher and class?
You can do a query to eager load the classes and students:
#teachers = Teacher.includes(students: :class)
Then I would use group_by in the view to group the students by the class.
<% #teachers.each do |teacher| %>
<%= teacher.name %>
<% teacher.students.group_by(&:class).each do |class, students| %>
<%= class.name %> | <%= students.size %>
<% end %>
<% end %>
I've assumed that a teacher and a class both have a name. There may well be a better way using something like has_many through but I can't see it at the moment.
On another note you shouldn't name your object Class it's going to cause you a lot of problems I'd have thought because it's defined in ruby already http://ruby-doc.org//core-2.2.0/Class.html. I'd call it something like SchoolClass to avoid conflicts.

Rails: Show all associated records on a has_many through association

I'm building a guestlist app and I have defined both Guest (name) and List models - guests can have many lists and lists can have many guests. Both are associated in a has_many through association (after reading that HABTM associations aren't a good idea).
Here are my models:
class Guest < ActiveRecord::Base
has_many :lists, through: :checklists
end
class List < ActiveRecord::Base
has_many :guests, through: :checklists
end
class Checklist < ActiveRecord::Base
belongs_to :list
belongs_to :guest
end
EDIT - my lists controller for show:
def show
#list = List.find(params[:id])
end
On the List show view, I want to display the all of the guest names that are tied to that list through the checklist table. I can figure out if I need a do loop or an array...this is a bit beyond my current skill.
I've tried things like the following:
<%= #list.checklist.guest.name %>
I'm clearly missing some key bit of code and concept here.
Thanks in advance.
You need to iterate over guests like this:
<% #list.guests.each do |guest| %> # For each guest in list.guests
<%= guest.name %> # print guest.name
<% end %>
It should be something like this
<% #list.guests.each do |guest| %>
<%= guest.name %>
<% end %>

is there better way to count for three level nested forms

I have a nested forms like:
class House < ActiveRecord::Base
has_many :rooms
accepts_nested_attributes_for :rooms
attr_accessible :rooms_attributes
end
class Room < ActiveRecord::Base
has_one :tv
accepts_nested_attributes_for :tv
attr_accessible :tv_attributes
end
class Tv
belongs_to :user
attr_accessible :manufacturer
validates_presence_of :user
end
Now, I want to know for house.id = 1 how many rooms and tvs totally.
In the houses_controller I gave
#houses = House.all
And it's quit simple to get the room count for each house like
<% for house in #houses %>
<%= house.rooms.count %>
<% end -%>
My question is how to get tvs count? I am using this now
<%= house.rooms.map {|room| room.tvs.count}.sum %>
It works, but I am not sure this is good or not.
Is there any better way to get it?
I'd put a method in the model, trying to avoid code in the views.
class House
...
def tvs
rooms.inject(0) {|r, t| t + r.tvs }
end
end
class Room
...
def tvs
tv ? 1 : 0 # it's has_one association right now
end
end
Also, if in your controller your are loading all House's objects, and after that you are going to need the Rooms objects, you should load the houses like:
House.find :all, :include => { :rooms => :tv }
This way you are going to do 1 query, with your approach there will be 1 + N_rooms + N_tvs queries

Resources