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

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.

Related

Rails 4 how to calculate sum for dynamic queries

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

Ruby Find last record value in has many

I'm trying to find the last Econ_Result that belongs to a Econ_Report. I want to display the last record of the Econ_Result (ordered by "release_date") for each Econ_Report on the index view. In the controller I tried to take the list of all reports and find the last result using the following:
#econ_reports = EconReport.all
if #econ_reports.econ_results.size >= 1
#last_result = #econ_report.econ_results.last.release_date
end
econ_report.econ_results.size works on the index view when I place it in for each loop. When I try to call the value of the last record I run into issues with the fact that some reports don't yet have results (a temporary issue) so I threw in the if then check in the controller which is currently failing.
Thanks in advance for the rookie help.
Since #econ_reports is a collection of EconReport objects, you can't call an instance method like .econ_results on it. Instead, you can only call it on instances within the collection:
#econ_reports.each do |econ_report|
if econ_report.econ_results.any?
last_result = econ_report.econ_results.last
end
end
However, this can be terribly inefficient for a large collection of #econ_reports: both lines with econ_report.econ_results will query the database separately, meaning that you'll query the database independently for each econ_report in the collection. This is known as the N+1 query problem.
Luckily for you, as discussed in the link, Rails has a built-in solution to optimize this code so you'll only query the database once:
<% #econ_reports.includes(:econ_results).each do |econ_report| %>
<% if econ_report.econ_results.any? %>
<% last_result = econ_report.econ_results.last %>
# do something to display last_result
<% end %>
<% end %>
If you just want the release date you might try:
#last_result = #econ_report.econ_results.order('release_date DESC').limit(1).pluck(:release_date).first
It's worth noting that a Ruby if statement generally looks like:
if condition
end
The then is almost always omitted even though it is allowed.

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.

How does the query method #group work?

I'm learning rails and have been trying to read through documentation, but I just don't get how #group
works.
The documentation says that it: "Returns an array with distinct records based on the group attribute".
How do you then retrieve the records that belong to a certain group?
Say I want to group Articles by the month in which they were created? How would I do that?
The group method is generally used with the select method to do aggregating queries. For instance, if you wanted to count your articles by month, you could do this:
by_month = Article.group(:month).select("month", "COUNT(*) as count")
In this case, COUNT is the SQL aggregate function that counts rows, and we're putting the count result into an output column called "count".
Note: This assumes you have a column called "month". Of course you can do SQL here, so you might have, e.g. MONTH(created_at) instead, or whatever makes sense in your case.
You could then do this to output the month and its associated article count:
by_month.each do |row|
puts "Month #{row.month}: #{row.count}"
end
This probably seems mysterious because your model has no column "count", but that's the way select works: It defines new output columns for the query on the fly, and ActiveRecord happily maps those for you in the resulting instance objects.
This kind of query is dramatically more efficient than loading all the records and counting them yourself because you're letting the database do the heavy data work, and that's what it's good at.
It is perfectly legal to use group without select but the result is not usually what you want. If you group your articles by month, you'll get one object in the result for each month. The columns available in each object vary by database back end, but in MySQL they will have the values from the "first" row encountered for each group. If you aren't sorting, "first" is essentially undefined.
If by "group Articles by the month in which they were created" you mean you want this kind of grouping on a web page result, then you'll have to do it yourself, e.g.:
<% last_month = nil %>
<% #articles.each do |article| %>
<% if last_month != article.month %>
<h2><%= article.month %></h2>
<% last_month = article.month %>
<% end %>
# [output the article]
<% end %>
If you do something like this, you'll need to be sure #articles is ordered by month.

Check whether a table column has any value

I have model named shirt which has a field named fabric,
In the controller I have;
#fabrics = Shirt.uniq.pluck(:fabric)
On the view I would like to display a <div>...</div> but only if the fabric column of the shirts table contains at least one value. I have tried:
<% if #fabrics != nil %>
<div>
...
</div>
<% end %>
But even when the whole column has no value, the <div> is still visible. I have also tried with
<%if #fabrics != blank %> with no success.
How can I check whether the column is not empty before rendering the div?
Try
<% unless #fabrics.blank? %>
Shirt.uniq.pluck(:fabric) returns a Relation. Therefore it will never be nil.
This Relation defines parts of a sql query. This says: Give me (all|one of the) unique values of fabric in the database. To actually run that query, you need to call a method on that relation that triggers the database call: all, first, each, any?, blank? ...
Through the lack of context, I do not know how you use your Shirt model. But I guess the query will never give you the expected answer. Because Shirt.uniq.pluck(:fabric) will always return something as long there is at least one row in that table. Imagine there is only one row in the table and it's fabric is nil, than Shirt.uniq.pluck(:fabric).blank? would determine Shirt.uniq.pluck(:fabric) to [nil]. And [nil].blank? == false
If you work on one specific shirt, use #shirt.fabric.present? If you want to know if there is at least one shirt in the db without a fabric Shirt.where(fabric: nil).any?
I recommend to read:
http://guides.rubyonrails.org/active_record_querying.html
http://api.rubyonrails.org/classes/ActiveRecord/Relation.html
Try this in controller
#fabrics = Shirt.uniq.pluck(:fabric).reject { |f| f.nil? || f.empty? }

Resources