How can I sort on a calculated value? - ruby-on-rails

I'm currently building an NFL pick'em league site. I have a Users model, a Games model, and join table which captures each user's individual picks. The games model has a "result" attribute which either consists of "W" for win, "L" for loss, "P" for push (tie).
I am running into issues building a standings page. I currently have two methods in my Users model:
def correct_games
self.games.where(result: "W").count
end
def total_games
self.games.where('result != ?', "P").count
end
The correct_games method counts the user's picks that were correct. The total_games methods counts the number of total games (not counting games that resulted in a push).
Then in my view I currently have for each user: <%= number_to_percentage(current_user.correct_games.to_f / current_user.total_games) %>
This division gives me that user's win percentage (# correct/total picks). For my standings table, I obivously want a descending order on win percentage. The issue is the only solutions to sorting seem to be using the .order method which usually requires some attribute to already be in the database which you can then call in the controller.
I've also tried adding this win percentage attribute to the database, but I can't seem to figure out a callback that will update the User's score whenever the game results are updated.
Any solutions to either sorthing on an attribute that is calculated in the view or a way to add this win percentage to the users model?
Thanks in advance!

Why not just do the calculation in the model instead of in the view? Add another method like this:
This code goes in your User model:
def percentage_correct
((self.correct_games.to_f / self.total_games) * 100).to_i
end
def self.sorted_by_percentage_correct
User.all.sort_by(&:percentage_correct).reverse
end
This is how you use it in your view:
<% User.sorted_by_percentage_correct.each do |u| %>
<div><%= u.name %> has pick percentage of <%= u.percentage_correct %>%</div>
<% end %>

Related

Rails, where filter on an already joined table

I have a table :periods that has a column :hours
I also have a table :simulations that belongs to :period (so it has period_id as a column)
I join the 2 table
#simulations=Simulation.join(:period) #Controller
Now in my view I have to loop through my simulations but filter based on the "hours" column, which exists on the period table.
So I tried things like
In my view I have to loop through simulations:
<% #simulations.where( :period => {:hour => count})each do |sim| %>
or
<% #simulations.periods.where(:hour => count )each do |sim| %>
Again, I want to filter my simulations based on data from the period table. And neither of these approaches work. I have also tried using includes and eager_loads neither of which works.
Am I attempting something that is not possible under rails?
Also I am using postgres
Try using this query, in your controller
#simulations = Simulation.where(periods: {hours: count}) # All simulations that belongs to periods with hours = count
Notice, the 's' in 'periods' and adjust the 's' in 'hours' as per your column name.
In views, directly use simulations variable:
<%= #simulations.each do |sim| %>
You are making same query twice of joining periods and simulations. Also try to avoid any query on Model in views or in controller. Instead, you should make a method in Model and use it to get the results from above query.

Check if value in one table is present in another

So this is probably easy but I haven't been able to find the right method for it. I have 2 models. One called monitor, and one called follower.
In 'monitor' I have a column called owner_id.
In 'follower' I have a column called follower_id.
What I would like to do is check if any of these match up (I'd like to get a count, not a boolean output). Both belongs_to Users.
How do I go about doing this?
Situation
I am trying to calculate a conversion rate from Twitter. Where the follower and the follower ids are the users who is following your account.
On the monitor I let you monitor keyword and interactions, where I save the owner_id (the person you're communicating with).
Now I count all the conversations you have had. Then I want to see how many of those that have turned into following your company.
I have a model called campaigns where you can monitor certain keywords.
<% #campaigns.each do |campaign| %>
<%= campaign.keyword %>
<% end %>
The model looks like this:
class Campaign < ActiveRecord::Base
has_many :alerts
end
now what I want to do is track the conversion rate for that specific campaign.
( CODE HERE* / campaign.alerts.count ) * 100
where CODE HERE* should be the count of how many that exists between :
campaign.alerts.map(&:owner_id)
and
current_user.followers.map(&:follower_id)
so what you are trying to do is just to compare two big arrays of ids (campagin.alerts.map(&:owner_id) and current_user.followers.map(&:follower_id)) and count how many ids are the same, you can use count and count everything that your block expression evaluates as true, and save that on a variable that you will use on your division, something like this:
result = (campagin.alerts.map(&:owner_id).count do |id|
current_user.followers.map(&:follower_id)).include?(id)
end
( result / campaign.alerts.count ) * 100
There are many other ways like using Array#all or other methods, maybe you can look at them here and see what fits you most:
http://ruby-doc.org/core-2.2.0/Array.html#method-i-2D

Calculate average conditionally based on an attribute in a rake task (Rails)

I have a requirement where I need to calculate the average of units sold for a product based on the company they were sold at.
The scenario is we're importing data from a legacy database, and when importing I'd like to perform some calculations based on the difference between units sold for new item and the average of the existing item's, when they were sold at the same company.
The model is called Product and has attributes of:
name
interest (How many units were sold)
company (What company they
were sold at)
Now previously, I am able to calculate the average of each company on the model like so:
def self.average_interest(company)
where(company: company).average(:interest)
end
But now I am trying to do the calculation on a rake task.
Here's what I came up with and it's not working:
#company = u.Company
#u.Company is the field name from the legacy database
def average_interest
Product.average(:interest, :conditions => ['company = ?', #company])
end
Any ideas?
Thanks!
EDIT:
Have updated it from '#company' to #company however the value being returned is still incorrect
In your rake task you can pass in the variable, so something like this:
def average_interest(company)
Product.average(:interest, :conditions => ['company = ?', company])
end
unentered_legacy_companies.each do |u|
average_interest(u)
end
after playing around, looks like it was only a slight adjustment from the original code that was needed.
Just had to add the model (Product) to the query in the code:
def average_interest(company)
Product.where(company: company).average(:interest)
end
And then I am storing it in a variable like so:
#company_average = average_interest(u.Company)

Rails sort users by method

I'm trying to rank my user's in order of an integer. The integer I'm getting is in my User Model.
def rating_number
Impression.where("impressionable_id = ?", self).count
end
This gives each User on the site a number (in integer form). Now, on the homepage, I want to show an ordered list that places these user's in order with the user with the highest number first and lowest number second. How can I accomplish this in the controller???
#users = User....???
Any help would be appreciated!
UPDATE
Using this in the controller
#users = User.all.map(&:rating_number)
and this for the view
<% #users.each do |user| %>
<li><%= user %></li>
<% end %>
shows the user's count. Unfortunately, the variable user is acting as the integer not the user, so attaching user.name doesn't work. Also, the list isn't in order based on the integer..
The advice here is still all kinds of wrong; all other answers will perform terribly. Trying to do this via a nested select count(*) is almost as bad an idea as using User.all and sorting in memory.
The correct way to do this if you want it to work on a reasonably large data set is to use counter caches and stop trying to order by the count of a related record.
Add a rating_number column to the users table, and make sure it has an index defined on it
Add a counter cache to your belongs_to:
class Impression < ActiveRecord::Base
belongs_to :user, counter_cache: :rating_number
end
Now creating/deleting impressions will modify the associated user's rating_number.
Order your results by rating_number, dead simple:
User.order(:rating_number)
The advice here is just all kinds of wrong. First of model your associations correctly. Secondly you dont ever want to do User.all and then sort it in-memory based on anything. How do you think it will perform with lots of records?
What you want to do is query your user rows and sort them based on a subquery that counts impressions for that user.
User.order("(SELECT COUNT(impressions.id) FROM impressions WHERE impressionable_id = users.id) DESC")
While this is not terribly efficient, it is still much more efficient than operating with data sets in memory. The next step is to cache the impressions count on the user itself (a la counter cache), and then use that for sorting.
It just pains me that doing User.all is the first suggestion...
If impressions is a column in your users table, you can do
User.order('impressions desc')
Edit
Since it's not a column in your users table, you can do this:
User.all.each(&:rating_number).sort {|x,y| y <=> x }
Edit
Sorry, you want this:
User.all.sort { |x, y| y.rating_number <=> x.rating_number }

ActiveRecord and SELECT AS SQL statements

I am developing in Rails an app where I would like to rank a list of users based on their current points. The table looks like this: user_id:string, points:integer.
Since I can't figure out how to do this "The Rails Way", I've written the following SQL code:
self.find_by_sql ['SELECT t1.user_id, t1.points, COUNT(t2.points) as user_rank FROM registrations as t1, registrations as t2 WHERE t1.points <= t2.points OR (t1.points = t2.points AND t1.user_id = t2.user_id) GROUP BY t1.user_id, t1.points ORDER BY t1.points DESC, t1.user_id DESC']
The thing is this: the only way to access the aliased column "user_rank" is by doing ranking[0].user_rank, which brinks me lots of headaches if I wanted to easily display the resulting table.
Is there a better option?
how about:
#ranked_users = User.all :order => 'users.points'
then in your view you can say
<% #ranked_users.each_with_index do |user, index| %>
<%= "User ##{index}, #{user.name} with #{user.points} points %>
<% end %>
if for some reason you need to keep that numeric index in the database, you'll need to add an after_save callback to update the full list of users whenever the # of points anyone has changes. You might look into using the acts_as_list plugin to help out with that, or that might be total overkill.
Try adding user_rank to your model.
class User < ActiveRecord::Base
def rank
#determine rank based on self.points (switch statement returning a rank name?)
end
end
Then you can access it with #user.rank.
What if you did:
SELECT t1.user_id, COUNT(t1.points)
FROM registrations t1
GROUP BY t1.user_id
ORDER BY COUNT(t1.points) DESC
If you want to get all rails-y, then do
cool_users = self.find_by_sql ['(sql above)']
cool_users.each do |cool_user|
puts "#{cool_user[0]} scores #{cool_user[1]}"
end

Resources