Rails 5 Multiple Sums and Join - ruby-on-rails

This question is related to this previously asked question.
My DB columns for model Taxline: ID, RECEIPT, TAXID, BASE, AMOUNT
With entries:
1,1,001,30$,3$
2,1,001,50$,5$
3,2,001,20$,2$
And then a second table with columns: TICKETID, TICKETNUMBER
My controller
class TaxlinesController < ApplicationController
def index
#taxlines = Taxline.group(:RECEIPT).sum(:AMOUNT)
end
end
My view
<% #taxlines.each do |receipt, amount| %>
<td><%= receipt %></td>
<td><%= amount %></td>
<% end %>
This works great to show a ticket for each row with corresponding total amount.
Question 1. What is the proper way to also show in view sum of BASE? I tried .sum(:AMOUNT, :BASE) and .sum(:AMOUNT).sum(:BASE) but they both don't work.
Question 2. If now I call in view for instance <%= taxline.TAXID %> I get an error.
To solve this I tried to add in view <% #taxlines.each do |receipt, amount, taxid| %> and <td><%= taxid %></td>. And in controller #taxlines = Taxline.group(:RECEIPT).sum(:AMOUNT).select(:TAXID). But it shows a blank column.
Question 3. I want to show TICKETNAME value from TICKETS table.I have already set in Ticketline Model belongs_to :ticket. I assume that after solving question 1 I will be able to do ticketline.ticket.TICKETNAME.Right?

Question 1:
Try this
#taxlines = Taxline.group(:RECEIPT).select("SUM(AMOUNT) AS AMOUNT, SUM(BASE) AS BASE")
Question2:
To access TAXID you need to add this in group column.
#taxlines = Taxline.group(:RECEIPT, :TAXID).select("SUM(AMOUNT) AS AMOUNT, SUM(BASE) AS BASE, TAXID")
Question 3:
To access the other table variables, you need to add join statement to the query and then you should group.
#taxlines = Taxline.joins(:ticket).group(:RECEIPT, :TAXID).select("SUM(AMOUNT) AS AMOUNT, SUM(BASE) AS BASE, TAXID, TICKETNAME")
In the view side, try the below one.
<% #taxlines.each do |taxline| %>
<td><%= taxline.AMOUNT %></td>
<td><%= taxline.BASE %></td>
<td><%= taxline.TAXID %></td>
<td><%= taxline.TICKETNAME %></td>
<% end %>
P.S: Not tried this.

Related

Ruby - how to join output from a Rails command?

There are multiple calculations I'd like to be able to perform on my database, all centred around a user id, but I don't know how to recombine them after.
Let's say I have three tables for User, Purchases, and DiscountUsage. I want to find the last date a user made a purchase and the total number of discounts used. I would run two separate commands:
User.joins(:purchases).group("users.id").select("users.id", "MAX(purchases.purchase_date)")
User.joins(:discount_usages).group("users.id").select("users.id", "COUNT(*)")
I want my final output to be one table though, joined on users.id, but the output from select isn't the right data type to work on with Rails functions. How can I represent that the users.id values from both calls are the same and thus join them based on those columns?
I assume a User may not have any purchases and not all purchases use discount codes.
However, you want a full listing of each user with their last purchase date and total discount usages over all purchases. You may need to use a right join.
Something like:
query = User.select('users.id AS user_id', 'MAX(purchases.purchase_date) AS last_purchase_date', 'COUNT(discount_usages.id) AS total_discount_usages')
.joins('LEFT JOIN purchases ON purchases.user_id = users.id')
.joins('LEFT JOIN discount_usages ON discount_usages.user_id = purchases.user_id')
.group('users.id')
.to_sql
Then in order to grab the fields you could use:
rows = ApplicationRecord.connection.select_all(query).to_hash
https://guides.rubyonrails.org/active_record_querying.html#select-all
This will give you an array of hashes with keys: 'user_id', 'last_purchase_date', 'total_discount_usages'.
...
<% rows.each do |row| %>
<% row.symbolize_keys! %>
<tr>
<td><%= row[:user_id] %></td>
<td><%= row[:last_purchase_date] %></td>
<td><%= row[:total_discount_usages] %></td>
<tr>
...
You can select aggregates from joined tables and access them in the model by using aliases:
#users = User.joins(:purchases, :discount_usages)
.select(
"users.id",
"MAX(purchases.purchase_date) AS latest_purchase",
"COUNT(discount_usages.*) AS discount_usages_count"
)
.group("users.id")
If you want to hydrate the other attributes of the records select users.* instead of just users.id.
<table>
<thead>
<tr>
<th>Id</th>
<th>Latest Purchase</th>
<th>Discount usages</th>
</tr>
</thead>
<tbody>
<% #users.each do |user| %>
<tr>
<td><%= user.id %></td>
<td><%= user.latest_purchase %></td>
<td><%= user.discount_usages_count %></td>
</tr>
<% end %>
</tbody>
</table>
I want my final output to be one table though, joined on users.id, but
the output from select isn't the right data type to work on with Rails
functions.
Not sure quite what you mean here. This example will return a collection of user records just like a normal query.
If you want a "raw" result with just an array of arrays use .pluck instead of .select.

Index view loading very slowly

I have a model Schools and a model PerformanceStats.
PerformanceStat
belongs_to :school
School
has_one :performance_stat
the index page for PerformanceStat shows all 2,000 performance stats, and also the school.name, school.score, and school.city, and I need access to the school.id and school.slug.
Controller:
def index
#performance_stats=PerformanceStat.all
end
My view code:
<tbody>
<% #performance_stats.each do |stat| %>
<% school = School.find(stat.school_id)%>
<tr>
<td><%= link_to school.name, school_path(city: school.city.parameterize.truncate(80, omission: ''), slug: school.slug) %></td>
<td><%= number_with_precision(school.score, precision: 2)%></td>
then the view goes on to display the performance stats.
This view load very slowly....10-20 seconds. How can I speed things up? I've tried PerformanceStats.scoped, and plucking school stats and selecting from an array, but these don't seem to help. Is there a way for me to access the school attributes without finding a School for every PerformanceStat? I believe the School.find bit is slowing things down considerably.
I have indexes on :school_id in PerformanceStat, and :score, :slug in the School model.
UPDATE:
The suggestion in the selected answer to add a cache resulted in this line of code in the index action of the SchoolsController:
fresh_when etag: #performance_stats
The load time dropped to 18ms. This solution works great for me because the content of the index action does not change often. This data gets updated once a year. This link has other suggested cache solutions for data that changes frequently.
PerformanceStat.all is a heavy query if you've a lot of data in this table and it'll be finding school for each performance stat.
What I can understand from your code is that you're facing (N + 1) problem over here.
NOTE: you should not fire queries from your views or helpers and let the controller do all the action.
For instance in your code:
<% #performance_stats.each do |stat| %>
<% school = School.find(stat.school_id)%> <- #THIS IS WRONG & LET THE ASSOCIATIONS DO ALL THE ACTION ON ITS OWN
<tr>
<td><%= link_to school.name, school_path(city: school.city.parameterize.truncate(80, omission: ''), slug: school.slug) %></td>
<td><%= number_with_precision(school.score, precision: 2)%></td>
you can use includes, PerformanceStat.includes(:school) it will fetch all the schools for each PerformanceStat.
your controller code should be:
#performance_stats = PerformanceStat.includes(:school)
instead of : #performance_stats = PerformanceStat.all
and your view code will now be:
<% #performance_stats.each do |stat| %>
<% school = stat.school %> #make sure all stats have a school assigned to them otherwise you can put a check below whether the school is nil or not
<tr>
<td><%= link_to school.name, school_path(city: school.city.parameterize.truncate(80, omission: ''), slug: school.slug) %></td>
<td><%= number_with_precision(school.score, precision: 2)%></td>
Quite a few things here. First of all change your controller method to this one, otherwise you will run into n+1 queries
def index
#performance_stats=PerformanceStat.includes(:school)
end
Since you have eagerly loaded the school, now you can access it directly in your view as
<% stat.school %>
Secondly loading almost 2000 records in one go is not optimal at all, it's gonna take a while to load all records. For this you must add pagination by using following gems
kaminari
will_paginate

Make method work with activerecord grouped objects

I have a bunch of objects, and I grouped them by the day they happened.
scope :grouped_by_user_with_total_time, lambda {
group(:user_id, :day).select('user_id, SUM(time_worked) AS time_total, day, editable, approvable, accepted, comments')
}
I also have some methods that change editable, approvable, and accepted. But now since they are grouped, I get this error when trying to approve grouped objects.
Couldn't find TimeLog without an ID
My approve method:
def approve
#time_logs = []
t = TimeLog.find(params[:time_logs])
if t.instance_of?(Array)
#time_logs = t
else
#time_logs << t
end
end
What do I have to change so that the methods can work on all of the hourlogs that are grouped together?
<% #time_logss.each do |timelog| %>
<% if timelog.user_id != current_user.id %>
<tr>
<!--<td><%# check_box_tag 'accept' %></td>-->
<td><%= timelog.id_name %></td>
<td><%= timelog.day.strftime("%B %d, %Y") %></td>
<td><%= timelog.time_total %></td>
<td><%= timelog.state %></td>
<% if timelog.state == "Submitted" %>
<td><%= link_to "Approve", approve_time_sheets_path(time_sheets: timelog), method: :put %></td>
You are trying to combine an aggregate function with the need to access unique data. When you use .select(''), you are telling AR which fields to populate into the object (even attributes that do not exist directly in the database).
If you add in time_logs.*, you'll lose the aggregate on time_worked, since you'll then be forced to add id to the group clause.
What you need to do have 1 instance var with the time_logs and one with the aggregate data:
#time_logs = TimeLog.all
#time_totals = TimeLog.grouped_by_user_with_total_time
Then, in the view, you can pluck out the correct total for each time_log. Although, I'm not clear on how you will be able to relate the specific time_log with it's aggregate equivalent.

Displaying query results in rails

So in my model a user has many roles through assignments and a role can have many users through assignments. I've managed to get the function for creating new assignments up and running but I want to display all the roles a user can perform on the user index page (which lists all the users).
My code for the users stub view thus far is:
<td><%= user.email %></td>
<td><%= user.phone %></td>
<td><%= user.roles.select("description")%></td>
The problem here is that instead of displaying the description for the role the output is:
Any pointers would be much appreciated. Thank you in advance!
Map the relation into an array of the strings, so that:
<td><%= user.roles.select("description")%></td>
becomes:
<td><%= user.roles.select("description").map &:description %></td>
or:
<td><%= user.roles.select("description").map(&:description).join(", ") %></td>
to join it into a single comma-separated string.
Here it is
user.roles.select("description").map(&:description).join(", ")

Passing a single record from an ActiveRecord model object into a param (or how to pass multiple fields in a param)?

Rails 2.3.5
I have a view displaying 'employee' records in a table where each table row haas a check_box_tag to select that (row) employee record (the table is inside a form_tag). The checkbox is passing an array of employee numbers to a method but I also need it to pass some of the other information from the record (first_name, last_name, etc) in the params.
Orignally this looked like (just passing an param with an array of employee numbers)
<% #employee_search.each do |e| %>
<td><%= check_box_tag 'selected_subordinates[]', e.employee_number %></td>
<td><%= e.employee_number %></td>
<td><%= e.first_name %></td>
<td><%= e.last_name %></td>
...
<% end %>
I'm not sure this was right, but I thought I should pass the entire record ('e') in the param:
<% #employee_search.each do |e %>
<td><%= check_box_tag 'selected_subordinates[]', e %></td>
<td><%= e.employee_number %></td>
<td><%= e.first_name %></td>
<td><%= e.last_name %></td>
...
<% end %>
The param array now looks like:
"selected_subordinates"=>["#<Employee:0xa946970>", "#<Employee:0xa946910>", "#<Employee:0xa9468b0>"]
I thought at this point I would be fine and just itterate through the objects in the param array referring to the record fields, but got an undefined method error:
params[:selected_subordinates].each do |s|
puts s.last_name
end
undefined method `last_name' for "#<Employee:0xa946970>":String
I started wondering if for some reason the entire model object was passed instead of just one record from the object. But, trying [0].last_name resulted in a different error.
params[:selected_subordinates].each do |s|
puts s.last_name
end
undefined method `last_name' for 35:Fixnum
Maybe I should have been using the fields I need to build an array for the param - so the param would be an array of arrays? I haven't had any luck so far trying to search for example of what to do when you need to setup a param array made of arrays, or pass a single model object record (and refer to it).
Thank You - Much Appreciated!
When you used e as the param, Rails was converting e to a String and passing that (you can't pass an object in an HTML form, right? Just values). When you saw "#<Employee:0xa946970>" in your params hash, it wasn't an Employee object, but instead a String with the contents of #<Employee:0xa946970> (which is what you get if you called .to_s on an Employee object).
Passing the ID gets you on the right track, but once you have the ID, you should look up the Employee with that ID from the database.
params[:selected_subordinates].each do |s|
employee = Employee.find(s)
puts employee.last_name
end
Of course, this loads them one at a time, so if you have a lot of checkboxes you could end up generating a large number of queries. You can also use the find method to find multiple objects based on an array of IDs:
employees = Employee.find(params[:selected_subordinates])
employees.each do |e|
puts e.last_name
end

Resources