Rails 4 how to calculate sum for dynamic queries - ruby-on-rails

Having a challenge to calculate sum for dynamically built request.
I'm using each method to get value for each element and it work seamless.
<% params[:car].map{|n| n.first}.each do |p|%>
<%= #salon.price.send("price_" + p) %>
<% end %>
But then I'm trying to get sum for the same dynamically ("price_" + p) built queries it's failing.
<%= #salon.price.where("price_" + params[:car].map{|n| n.first}.to_s).all %>
Tried multiple solutions and no luck

You have where but haven't given it an actual where-like clause do you mean #salon.price.sum() instead? Otherwise what are you trying to filter on (where is for filtering, sum is for summation).
So what you seem to want to do is:
for all the prices for a given salon
sum up the columns price_0..price_n
right?
Now it'd be easy to construct a query to sum up the values for a single column
For that you'd try something like this:
<%= #salon.price.sum("price_0") %>
This uses the Rails sum method that works on any Active Record association.
And if you had a single price object and wanted to sum up all the price_X columns for that single price, you'd use something like this:
<%= params[:car].map{|n| price.send("price_" + n.first.to_s) }.sum %>
This turns the numbers in params[:car] into an array of the column-values for the given price-object... then sums them at the end using the sum method that comes from the Array class (AKA Array#sum)
so to combine those two, you'd probably need something like this:
<%= #salon.prices.all.sum{|price| params[:car].map{|n| price.send("price_" + n.first.to_s) }.sum } %>
Yes, that's a block inside a block.
Now it's possible that the Active Record version of sum can interfere with Array#sum and not like the above (which looks more like how you'd do the Array#sum). Rails' sum expects you to pass it the name of a single column like in the first example, rather than a block, like in the second example. So sometimes you then need to use map (which turns your set of values into an array) and then you can use Array#sum at the end like this:
<%= #salon.prices.all.map{|price| params[:car].map{|n| price.send("price_" + n.first.to_s) }.sum }.sum %>
So that's a block (whose values are summed) inside another block (whose values are summed)
EDIT:
after discussion it seems you only have a single price record... and multiple columns on that single record... this changes things and makes them much simpler. You can just use this:
<%= params[:car].map{|n| #salon.price.send("price_" + n.first.to_s) }.sum %>

You use sum method for the array
sum = params[:car].map{|n| n.first}.sum

I'm guessing you're trying to get the sum of columns named price_n in the Price table where n is the value of params[:car].map(:&first). So I think the simple solution is:
<% params[:car].map(&first).each do |n| %>
<% sum += #salon.price.send("price_#{n.to_s") %>
<% end %>
<%= sum %>
But seeing the logic in the view is not a rails best practice, so it's better if we move the entire logic in your helper method. So in the view, just display this code:
<%= total_of_all_prices(params[:car], #salon.price) %>
Then in your helper method add this method
def total_of_all_prices(car_params, price_object)
sum = 0
car_params.map(&:first).each do |n|
sum += price_object.send("price_#{n.to_s}")
end
sum
end

Related

Change Class based on active record db query with link_to in loop in rails?

Requesting expert help.
I have an applications table which has_many metrics.
At some point of time, metrics will have following kind of records.
{capture_time:"08:00:10.1234",metric_name:"log",metric_value:"OK",application_id:1}
{capture_time:"09:00:10.1234",metric_name:"process",metric_value:"KO",application_id:1}
{capture_time:"10:00:10.1234",metric_name:"process",metric_value:"OK",application_id:1}
{capture_time:"08:00:10.1234",metric_name:"log",metric_value:"OK",application_id:2}
{capture_time:"09:00:10.1234",metric_name:"process",metric_value:"OK",application_id:2}
{capture_time:"10:00:10.1234",metric_name:"process",metric_value:"KO",application_id:2}
I have a bigger loop for applications and for each application , I create buttons for each metric for that application
<% applic.metric.uniq{|p|p.metric_name}.each do |m| %>
<%= link_to m.metric_name, metrics_path(m.application_id,metric_name: m.metric_name) , :class=>"btn btn-success",:role=>"button" %>
<% end %>
On clicking any button it shows me records only for that metrics. For e.g. if I click on process, i see all records of that metric, which is 2 records in my case.
So till here its fine. What I am looking help for is two folds:
How to determine latest metrics(based in capture time) for that application that is KO and then use that to change the class in link_to inside the loop. Something like this:
<% applic.metric.uniq{|p|p.metric_name}.each do |m| %>
<%= link_to m.metric_name, metrics_path(m.application_id,metric_name: m.metric_name),:class=>"btn btn-success",:role=>"button" %>
<% end %>
Class => btn-danger if latest record for this metric was KO else btn-success
Then I would want to use the combined statuses of the Metrices and change the Class for the whole Application1 box.
For e.g if any one of Process, Log, Errorcounts is failed , which means any of the latest matrices of any of 3 category is KO, then the whole Application1 box should have a class as "btn-danger"
like this:
UPDATE 1 : Many Thanks to #sammms
I tried the suggestion and created following, but it still does not solve my problem.
class Metric < ApplicationRecord
belongs_to :application
def isFailed(metric_value=nil)
metric_value == 'KO'
end
end
<% applic.metric.uniq{|p|p.metric_name}.each do |metric| %>
<%= link_to metric.metric_name, application_dashboard_metrics_path(appid:metric.application_id,metric_name: metric.metric_name),
{:class=>"btn #{metric.isFailed(metric.metric_value)? 'btn-danger':'btn-success' }",:role=>"button"} %>
<% end %>
the string interpolation works, as in it changes the class based on metric value. But the problem is in uniq bit here.
applic.metric.uniq{|p|p.metric_name}.each
since I am looping through only the unique metric_name, it could be the one with metric_value as OK. And hence when it loops, it actually does not find any KO for that metric.
If I don't use uniq in the loop, then I see one button for each record. something like below,
This is not what I wanted.
I wanted to have a button only once, per metric_name, then change the class based on the collective status of all the records for that metric.
So, when I have 100 record for process metric, I don't want 100 buttons, I want only one process button, but the class should be based on if the latest metric_value is KO.
UPDATE 2:
I solved the problem with this:
def isFailed(metric_name=nil)
p metric_name
#metric_value == 'KO'
Metric.where(metric_name:metric_name).order("capture_time DESC").first.metric_value == "KO"
end
Now I need to figure out the part 2.
Since you are inside of an ERB block <%=... %> you can write vanilla ruby and it will be evaluated. That means you can used interpolation inside the string you pass to class, e.g.
class="#{'btn-danger' if metric.ko?}"
where ko? is a method defined in Metric that will return the boolean used to evaluate your condition. eg...
class Metric
def ko?
metric_value == 'KO'
end
end
For the second part, you could use the same logic. Create a CSS class that makes your box look the way you want it to look, then conditionally add it using string interpolation in the ERB class definition. Then just define a method on the Class (I think it sounds like you would want the application class in this case so you can evaluate it's associated metrics) and use that to return a boolean.

Adding map and/or reject to a reduce in rails view

I am calculating an ROI value that averages over a category.
<% #categories.each do |category| %>
<h3><strong><%= category.name %><strong></h3>
<% #category_products = category.products_by_warehouse_id(params[:id]) %>
<!-- add map/reject below -->
<%#ROI = (#category_products.reduce(0.0) {|acc, item| acc + (item.heritable_sale_price.to_f * item.product_selections.length) / item.purchase_price.to_f }) %>
<p> Category ROI: <%= number_with_precision((#ROI / #category_products.length.to_f), precision:2) %></p>
.....(close tags)......
The value throws NaN when financial data is missing. For individual values, this is fine; however, it does the same for averages with missing data as well.
How can I add map/reject into my call to throw out nil values, and get the average of what's available?
#category_products.length.to_f would also have to skip over empty elements in the array if I go this route as well, to keep the sum and length consistent. Something like .where(purchase_price: [!nil, ""]).size may work.
So, in order to make sure, that none of the methods being accessed within the reduce block, to the item object are going to return nil, which hence, would throw a NoMethodError, you could firstly check them at the moment of creating your query. A where.not would do that. But keeping in mind that it'll leave aside each record in the database which doesn't satisfy the query criteria.
For that, then:
where.not(
heritable_sale_price: nil,
product_selections: nil,
purchase_price: nil
)
For that, you can analyze the option on setting a default value for each of those columns, so this helps you avoiding the previous query, and having to rescue on each case where there's no value for them. You can see the Rails Migration docs.

Querying multiple tables in one .each loop

What I am hoping to do is create a timeline of user events pulled from multiple differnt tables that are assosiated with the user.
For example
#user.notifications
#user.followings
#user.enrollments
I don't know if there is a way to loop through this information sorted by created_at.
like
<% (#user.notifications) + (#user.followings) + (#user.enrollments).each do |n, f, e| %>
Any guidence would be super appreciated!
You are correctly adding the arrays together (you don't actually need the parans around each element, but you need to wrap the entire adding statement in parans) so now you just need to sort them.
Try this:
<% (#user.notifications + #user.followings + #user.enrollments).sort_by(&:created_at).each do |event| %>

Grab Random record from SQL Database and display it

I'm on Rails 4 using SQLite, and I have a database of 75,000 quotes. I would like to grab a random record (quote) from this database and display it for users to see. I've looked around but no method I have searched around here has worked yet. Here's code that grabs the first record and displays it:
<% Quote.limit(1).all.each do |quote| %>
<%= quote.quote %> :: <%= quote.author %> :: <%= quote.genre %>
I have also tried the following method, but this returns a long alphanumeric value rather than the quote.
offset = rand(Quote.count)
rand_record = Quote.offset(offset).first
Thanks!
Do not order by random() in your SQL call. It's extremely inefficient as your database has to call the random function for each row, assign it a value, then sort on it. As the number of rows grows this becomes very slow.
Your second method should work and is a much better solution. I tried it with my own database and it works. rand_record will be a Quote object -- not the quote string itself. I would do this:
In your controller action:
random_offset = rand(Quote.count - 1)
#random_quote = Quote.offset(random_offset).first
In your view:
<%= #random_quote.quote %> :: <%= #random_quote.author %> :: <%= #random_quote.genre %>
The reason we subtract one from the count is that if we choose an offset that is the same as the number of Quote records then our call to fetch will be nil as the query's offset is one too many.

Rails: working with calculated values

I have two calculated values subtotal and shipment charges and a third one total which is the sum of the first two. I want to display all three of them in the View. How can I do this. What I am doing right now does not seem good.
Subtotal: <%= #cart.subtotal %>
Shipment: <%= #cart.shipment_charges %>
Total: <%= #cart.subtotal + #cart.shipment_charges %>
The last is calling the methods again to get the final total. What is the best practice for this?
Also storing the results like this in the view also looks bad
<%- subtotal = #cart.subtotal %>
<%- shipment_charges = #cart.shipment_charges %>
...
Total: <%= subtotal + shipment_charges %>
Please help
Since you are dealing with invoices, don't you want to save those values?
If not, you should be using virtual attributes:
class Invoice < ActiveRecord::Base
def total
subtotal + shipment_charges
end
end
Check this link for a detailed example.
good practice is prepare your instance variable in controller
there you can make your sum as well
I'm not convinced that what you are doing is bad practice. Generally you should try and keep business logic out of your views, but what you are doing here is simply presentation of data.
Unless you are planning on sending the Total back to the database, it's not really a big deal.
If you are using the subtotal, shipment_charges and total elsewhere you could declare them as a calculated field on your model.
e.g.
Class Cart ...
def total_cost
self.subtotal + self.shipment_charges
end
end
That way you can just call #cart.total_cost in your view

Resources