I have the following call to sum a users points for challenges:
#points = current_user.challenges.sum(:xp)
On the challenges model there is an accomplished boolean. I want to sum the points for only the challenges belonging to the user that have the accomplished boolean true. How do I do that exactly in this call?
current_user.challenges is a relation so you can add more where calls to filter the results and then sum that:
#points = current_user.challenges.where(accomplished: true).sum(:xp)
# --------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^
Related
I have a controller which looks like this:
def index
collection = Chart.all
render json: collection, meta: {averages: collection.averages }
end
So as you can see, what I'm trying to do is a regular GET to an Index method, which has a meta object, which returns "averages".
Chart belongs to an employee , and an employee has many areas. Chart also has many chart_axis_values. Each chart_axis_value has a .value (float) and a .name(string).
chart_axis_values are things like "motivation", "satisfaction", "positivity" and "collaboration". Each of them has a value(float), which we will use to calculate the average of each chart_axis_value in all Charts.
So, as you can see, an employee has a Chart, in which he has these 'axes', and each axis has a value.
Say I have 5 Charts, from 5 different employees, and each employee belongs to a different 'Area' in the company (HHRR, Marketing, Technology, etc...). When I make a GET request to get Index of Charts, I need to receive all 5 charts, each of them with their axes, and each axis has it's name and value. And in the "meta" object, I need to receive the average value for each chart_axis_value. So I will count how many times an axis appears(with it's name), and I will accumulate the values of each axis, and when I finish the GET request, I will calculate total_count/total_sum for each axis. All this will be returned in the JSON format, so I will be using a hash.
All this problem is solved, I already have the averages for each axis, but now I have a new problem.
Each employee has_many (zero or more) Areas, and when I do my get request, instead of getting all charts from all employees, I can filter by one or more Areas and only get the Charts of those employees that belong to a specific Area(or more, if I filter by more than one Area).
So what I want to do now is I want to receive, averages values for each Area. So I will be doing the same "counting" of times an axis appeared, and the same accumulation of the value of this axis, but I will be doing this for each area, so I can calculate the average for each Area.
Here's what I have so far.
## /models/chart.rb
class Chart < ApplicationRecord
belongs_to :employee
has_many :chart_axis_values
def self.averages ## Method for the meta in the controller
# first we generate a hash with the total sum and total objects
values = all.reduce({}) do |values, chart|
# we iterate over each axis
chart.chart_axis_values.each do |axis|
# ignore nils
unless axis.value.nil?
# first we define hash to keep count and total sum
values[axis.slug] ||= {count: 0, total: 0.0}
# We count and accumulate for each axis, so later on we calculate the average
values[axis.slug][:count] += 1
values[axis.slug][:total] += axis.value
end
end
values
end
# once we have the total we transform the hash calculating averages
values.each_with_object({}) do |(axis, values), averages|
averages[axis] = values[:total]/values[:count]
end
end
end
This way, I get the averages for each axis. Now I'm trying to do the same, but once for each Area, and I'm having lots of trouble on how to solve this. I was hoping someone here would look at my problem and maybe suggest a solution, or come up with something I can use and understand. I think my problem is very specific, so I understand if don't have the time to help me, but it's worth the try! As of now, I think I have to do something like this:
## /models/chart.rb
class Chart < ApplicationRecord
belongs_to :employee
has_many :chart_axis_values
def self.averages ## Method for the meta in the controller
# first we generate a hash with the total sum and total objects
values = all.reduce({}) do |values, chart|
# we iterate over each axis
chart.chart_axis_values.each do |axis|
# ignore nils
unless axis.value.nil?
chart.employee.areas.each do |area|
# first we define hash to keep count and total sum
values[area.name] ||= {axis.name=>{count: 0, total: 0.0}}
if values[area.name][axis.name].nil?
values[area.name].merge({axis.name=>{count: 0, total: 0.0}})
end
# We count and accumulate for each axis, so later on we calculate the average
values[area.name][axis.name][:count] += 1
values[area.name][axis.name][:total] += axis.value
end
end
end
values
end
# once we have the total we transform the hash calculating averages
values.each_with_object({}) do |(axis, values), averages|
averages[axis] = values[:total]/values[:count]
end
end
end
And that's as far as I got without turning the code into Frankenstein and making it stop making sense.
I've spent days on this problem, and I just can't seem to get it to work, I'm getting so frustrated, any help or suggestion will be appreciated, thank you.
I'm try to save some precious millisecond in a query.
I have a stats table and I'm calculating the average value of some records grouped by a key
Activity.group(:company_id).average(:completed_courses)
Everything is ok, but I need also the average on other columns of the same table (:readed_news, :logins, etc)
Is there a way to get all the averages with a single query?
I'm using Postgres
You can write select.
Activity.group(:company_id)
.select('AVG(completed_courses) as avg_completed_courses,
AVG(readed_news) as avg_readed_news,
AVG(logins) as avg_logins')
Also, you can write method for generating select expression:
def select_exp(attrs)
attrs.sum { |attr| ("AVG(#{attr}) as avg_#{attr},") }.chop
end
Activity.group(:company_id)
.select select_exp(%w(completed_courses readed_news logins))
You can use single field on average method but you can do this
activities = Activity.group(:company_id)
completed_courses = activities.average(:completed_courses)
readed_news = activities.average(:readed_news)
etc..
Hope this will help you.
Use scopes or class methods on you model:
class Activity < ActiveRecord::Base
self.average_company
group(:company_id).average(:completed_courses)
end
self.average_readed_news
group(:readed_news).average(:completed_courses)
end
end
then call:
Activity.average_company
Actvitity.average_readed_news
You could then try to merge scopes
Activity.average_company.merge(Activity.average_readed_news)
Look at the documentation here:
http://guides.rubyonrails.org/active_record_querying.html
in my application lots of objects are already preloaded by Rails.
Now I like to update some of these object attributes. Unfortunately some of these objects are related to the same object in my database. When I add a value to myObject.balance (myObject.balance += value), the attribute balance in differentButSameObject has still the same value.
One solution could be reloading the object. I would prefer to update the value like this:
UPDATE myTable SET balance = balance + 10 WHERE id = 1.
Is this possible?
You could use ActiveRecords update_all statement:
Object.where(:id => 1).update_all("balance = balance + 1")
You can add the following code to your model. It uses the update_counters which performs a proper query (that does COALESCE, for instance):
def inc(counter, by = 1)
raise "Cannot update column of a non-persisted model" unless persisted?
self.class.update_counters(id, counter => by)
increment(counter, by)
end
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 %>
I have a simple golf app, and for each player I have :total_score and :current_place attributes. I want Rails to define the :current_place attribute of each player based on whether that player's :total_score is higher or lower than the other players.
In other words, if there are two players, and one has a :total_score => 5and the other has :total_score => 4 I'd like Rails to make the first player's :current_place => 1, and the other player's :current_place => 2, and so on. I basically want it to define the :current_place values based on the descending of the :total_score column.
You could achieve that with an observer that when a player is saved launches and sql query and then updates the attribute(using update_attribute so it doesn't trigger callbacks). But that would be a not very performant solution and could have race condition problems too.
Another solution would be to have this same query materialized in some place and make the current_place function of the model access the results. This could be easily cacheable too
Code:
class Player < ActiveRecord::Base
def current_place
self.class.current_place(self.id) if self.id
end
def self.all_places
rank = {}
pos = 0
self.order_by('total_score desc').each{|p| rank[p.id] = (pos += 1)}
rank
end
def self.current_place(id)
all_places[id]
end
end
I didn't test it but I guess that something like this should work. To achive the cached part you only need to wrap all_places in a Rails.cache.fetch block and expire it every time a player total score is updated