Make method work with activerecord grouped objects - ruby-on-rails

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.

Related

Rails 5 Multiple Sums and Join

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.

How to avoid hitting database in the view

I get that one should not ping the database in the view... but wondering about the right solution. In one of my views, I need to pull info on an #order, it's child items, and also Amount, another model, based on each child item. Something like this:
<% #order.items.each do |item| %>
<td><%= item.name %></td>
<td><%= Refund.where(item_id:item.id).first.amount %></td>
<td><%= Amount.where(item_id: item.id).first.amount %></td>
<% end %>
For the sake of avoiding the db hits in the view, the only solution I've thought of is to create a huge hash of all the relevant data in the controller, which is then accessed from the view. So it would be something like this:
# controller (writing quickly, code may not be totally right, hopefully you get gist
data = Hash.new
data["items"] = []
#order.items.each do |item|
item_hash = {
"name" => item.name,
"amount" => Amount.where(item_id: item.id).first.amount,
"refund" => Refund.where(item_id:item.id).first.amount
}
data["items"] << item_hash
end
# view code
<% data["items"].each do |item| %>
<td><%= item["name"] %></td>
<td><%= item["refund"] %></td>
<td><%= item["amount"] %></td>
<% end %>
And I know SO hates this type of question... but I really need to know... is that the best solution? Or are there are best practices? The reason I ask is because it seems very clean in the view, but very bulky in the controller, and also it gets quite unwieldy when you have a much more complex set of nested tables, which is what I actually have (i.e., the data hash would be quite funky to put together)
First of I would use associations between item and the 2 other classes, so that you can do
item.refund
item.amount
Instead of Refund.where(...). You could further define methods such as
def refund_amount
refund.amount
end
And similarly for the other one (and hopefully come up with a better name than amount_amount.
This keeps both your view and controller clean but it won't be any faster. So far all of the approaches involve running 2 database queries per item which is the real issue as far as I'm concerned - whether those excess queries happen in the view or the controller is of lesser concern.
However you can avoid this with Active Record's include mechanism:
Item.include(:amount,:refund).where("your conditions here")
Will load the named associations in bulk rather than loaded them one at a time as each item is accessed.

Why is this so slow to load? Is there any technique to make this faster?

I'm using the gem called "Mailboxer" ( https://github.com/ging/mailboxer )
This enables messaging system within Rails app.
With this gem, I'm showing 10 received messages for each page.
I'm using Kaminari for pagination here.
But, it is kinda too slow with my codes.
It's issuing more than 25 sql at once :(
How can I make this faster? It takes more than 1500ms to show just 1 page.
Here are my codes
What's wrong with this? Is there any technique to make this faster?
controller
#number_of_messages_to_display = 10
#messages = current_user.mailbox.inbox.page(params[:page]).per(#number_of_messages_to_display)
#messages_count = current_user.mailbox.inbox.count
view(messages/index.html.erb)
<%= #messages_count.to_s %> messages in your received message box.
<table>
<% #messages.each do |m| %>
<tr>
<td><%= check_box_tag "id[]",m.id %></td>
<td><%= if m.is_read?(current_user) then "Read" else "Un-read" %></td>
<td><%= profile_link(m.recipients.first) if m.recipients.first != current_user %></td>
<td><%= link_to m.subject, show_messages_path(:id => m) %></td>
<td><%= today_datetime(m.last_message.created_at) %></td>
</tr>
<% end %>
</table>
view(helpers/application_helper.rb)
def profile_link(user)
if user
nickname = user.user_profile.try(:nickname)
username = user.try(:username)
link_to nickname, show_user_path(username)
else
"Un-known"
end
end
def today_datetime(date_time)
date_time.to_date == Date.current.to_date ? "<span class='text-info'>#{date_time.to_s(:us)}</span>".html_safe : date_time.to_s(:us)
end
routes.rb
get 'messages/:id' => 'messages#show', :as => :show_messages
get "users/:id" => 'users#show', :as => :show_user
models/user.rb
def to_param
"#{username}"
end
Classic example of the N + 1 problem.
You retrieve #messages = current_user.mailbox.inbox.page, which will retrieve records from the messages table.
In the view, you loop through them and check each message's recipients list (a has_many relationship, probably based on the receipts table, as can be seen here). So, for each message, you end up sending another query to the database.
You can correct this by retrieving the recipients together with the messages (and the last_message association as well, since you're using it):
#messages = current_user.mailbox.inbox.
includes(:receipt, :last_message).page
Also, you may have a different problem compounding this, as 25 queries should execute pretty quickly on a modern computer. I'd recommend using something like the RailsPanel tool to track down where the time is being spent.

update a cell of a table

I have these lines in views/admins/index.html.erb
I tried to update my User table in the cell of the role_ids after pressing on update:
if worker.role_ids is [1], it will change to [2].
if worker.role_ids is [2], it will change to [1].
<td><%= worker.email %></td>
<td><%= worker.role_ids %></td>
<td><%= link_to 'edit', edit_admin_path(worker) %></td>
I can define edit and _form, but there is no elegant way?
You can do it via Ajax (I think that it is what you are looking for). You can call a remote action, passing the id of the object, and calling a method to update to the corresponding value. For example:
View
<%= link_to 'Change Role', change_admin_role_path(worker.id), :remote => true %>
Controller
def change_admin_role
#worker = User.find(params[:id])
#worker.change_role
end
Remember to add the route, also create the method change_role in the User model that will evaluate the actual value and make the change, and create a change_admin_role.js.coffee (or change_admin_role.js.erb) to make the changes over the view via Ajax.

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