Rails loop refactor - ruby-on-rails

I have created a loop, to calculate a total rating of a record. To do this I am first looping through all the child records (ratings), extracting the rating from each row, adding it to the total and then outputting the total.
<% total = 0 %>
<% for ratings in #post.ratings %>
<% total = (total + ratings.rating) %>
<% end %>
<%= total %>
My question is, simply, Is this the rails way?
It achieves the desired result, although needs 5 lines to do so. I am worried I am bring old habits from other languages into my rails project, and I am hoping someone could clarify if there is an easier way.

The following, preferably in the controller, will do it succinctly:
#rating = #post.ratings.sum { &:rating }
If that seems cryptic, you might prefer
#rating = #post.ratings.inject(0) { |sum, p| sum + p.rating }
Note, however, that this will fail if any of the ratings are null, so you might want:
#rating = #post.ratings.inject(0) { |sum, p| sum + (p.rating || 0) }

You should generally keep logic out of your views. I would put that code in a helper or a controller, and the call a method to calculate the total

Put the following in your controller, then you just need to use #rating in your view:
total = 0
#rating = #post.ratings.each { |r| total += r.rating }
Or you could move it into the Post model and do something like:
def self.total_rating
total = 0
ratings.each { |r| total += r.rating }
total
end
and then simply call #post.total_rating

Related

doing away with loop result in rails

I am working on a kind of order where I have multiple amount in different currencies in just one attribute. So am trying to make sum on different currencies inside that attribute using other attributes in the table, which works fine but outputs the result as a count of all the rows instead of just showing the sum of the random values calculated.
orders_controller.rb
module Admin
module Statistic
class OrdersController < BaseController
def show
#orders_grid = ::Statistic::OrdersGrid.new(params[:statistic_orders_grid]) do |value|
value.page(params[:page]).per(20)
end
#assets = #orders_grid.assets
##fee_groups = {:fee => #assets.sum(:fee)}
#fee_groups = {
:fee => #assets.sum{|t|
olaoa = t.type
market_string = t.currency
base_currency = market_string.slice(0..2)
quote_currency = market_string.slice(3..5)
if olaoa == 'OrderBid' and base_currency == 'btc'
"#{ t.fee.to_s + ' ' + base_currency.upcase }"
elsif olaoa == 'OrderAsk' and quote_currency == 'ngn'
"#{ t.fee.to_s + ' ' + quote_currency.upcase }"
end
}
}
#orders_filter = true
#orders_group = true
end
end
end
end
summary.htm.slim
.panel.panel-default
.panel-heading
h4.panel-title.row
a data-parent="#filter-accordion" data-toggle="collapse" href="#summary"
span.col-xs-8.text-muted = t('admin.statistic.summary')
span.col-xs-4.text-right.text-muted = t('admin.statistic.click-to-expand')
#summary.panel-collapse.collapse
.panel-body
.datagrid-groups
- if !#orders_group
- if groups
- groups.each do |key, val|
.datagrid.group.row
span.col-xs-2.title = t("admin.statistic.#{controller_name}.#{controller.action_name}.#{key}")
span.col-xs-10.value = val
- if #orders_group
/ Summary Count Loop
- if groups
- groups.each do |key, val|
.datagrid.group.row
span.col-xs-2.title = t("admin.statistic.#{controller_name}.#{controller.action_name}.#{key}")
span.col-xs-10.value = pluralize(val, 'Order')
/ Summary Fees Loop. This is the Fee loop causing problem if am rigth and I dont know how to fix this.
- if #fee_groups
- #fee_groups.each do |key, val|
.datagrid.group.row
span.col-xs-2.title = t("admin.statistic.#{controller_name}.#{controller.action_name}.#{key}")
span.col-xs-10.value = val
The result of the code
So as you can see it renders 0.0BTC 5 times because the filter only has 5 orders. How do i deal with this. I want just the sum of all BTCs to show in the result instead of showing it 5 times.
Any help will be appreciated.
Because you sum strings in you're #fee_groups query that will results in putting strings next to each other instead of a total amount.
If you call it like this
#fee_groups = { fee: #assets.sum{|t| t.fee}}
You will get the total sum of you're assets.

Reiterate over collection

I have an unknown number of categories.
I want to pick one post from each category, and when there are no more categories I want to start from the beginning until I've reached a fixed number posts.
This is what I have, how could I rerun this iteration until I have my desired amount of posts?
desired_amount = 40
categories.each_with_index do |category, index|
post = category.posts.order(position: :asc)[index]
# do something with the post
return if desired_amount == (index + 1)
end
Personally, I would much prefer something like this:
posts = categories.cycle.take(desired_amount).each_with_index.map do |cat,ind|
cat.posts.order(position: :asc)[ind / categories.count]
end
That would give you the first post in each category, followed by the second post in each category, etc, until you had the number of posts you wanted. The one caveat is that if any category didn't have enough posts, your final array would have some empty spots in it (i.e. nils).
Maybe try something like this?
all_posts = []
#include posts to prevent constant querying the db
categories_with_posts = categories.includes(:posts)
until all_posts.size == 40
categories_with_posts.each do |category|
#pick a random post from current category posts
post = category.posts.order(position: :asc).sample
# add the post to collection if post is not nil
all_posts << post if post
# do something with the post
break if all_posts.size == 40
end
end
You could define an array of post before starting to loop:
desired_amount = 40
posts_array = []
unless posts_array.count == desired_amount
categories.each_with_index do |category, index|
post = category.posts.order(position: :asc)[index]
posts_array << post
return if desired_amount == (index + 1)
end
end

Rails - Reducing queries using includes

This is my controller:
#trainings = Training.includes(:courses).order(:id, 'courses.order_id ASC')
In my view, for each training I need to loop for 3 times and check if has a course with column order_id with these values:
#trainings.each do |training|
for course_order in (1..3) do
this_course = training.courses.find_by(order_id: course_order)
# this_course.image(:resized) #i can print the paperclip image
end
end
The code above executes so many queries, so I tried using select method:
#trainings.each do |training|
for course_order in (1..3) do
this_course = training.courses.select { |course| course.order_id = course_order }
# this_course.image(:resized) #I cannot print paperclip image, because the result is an Array, so doesn't know the method "image"
end
end
So I have just one query, but I cannot call the image method from my Model, because the result is an Array object.
I know I can use:
training.courses.each do |course|
# course.image(:resized)
end
and I will get just one query, but I must loop 3 times, so when I dont have the row I can print a placeholder image, like:
Example image http://www.onrails.com.br/order_id.jpg
Your
this_course = training.courses.find_by(order_id: course_order)
returns first record that matches conditions, but
this_course = training.courses.select { |course| course.order_id == course_order }
selects all courses that match condition. If you want to find only the first one, use .detect instead of .select:
this_course = training.courses.detect { |course| course.order_id == course_order }
Also you have a typo. = is for assignment, == is for comparing.

Returning multilpe values from model method in view

I have the following method in my model:
def self.set_bad_recommedation_size(rating_set)
bad = Rating.where(rating_set: rating_set).where(label: 'Bad').count
total = Rating.where(rating_set: rating_set).count
percentage_bad = (bad.to_f/total.to_f * 100)
return bad, total, percentage_bad
end
How do I call the variable bad, total, percentage_bad in my view.
What I want:
<%= "#{Model.set_bad_recommedation_size(rating_set).bad}/#{Model.set_bad_recommedation_size(rating_set).total"%>
You're better off doing:
<% bad, total, percentage_bad = Model.set_bad_recommedation_size(rating_set) %>
<%= "#{bad}/#{total}" %>
That way you're not calling the method multiple times.
I would add an intermediate helper so that your view reads better
<%= bad_recommendation_ratio(result_set) %>
Application.helper
def bad_recommendation_ratio(result_set)
bad, total = Model.set_bad_recommedation_size(rating_set)
"#{bad}/#{total}"
end

Split #blogs into three divs using size of description field as weight

I have a collection of Blog items.
#blogs = Blog.find(:all)
Each blog has a description textfield with some text. What I would like to do is splitting the #blogs objects into 3 divs, but with roughly the same characters in each column.
<div id="left">
#blog1 (653 characters)
</div>
<div id="center">
#blog2 (200 characters)
#blog5 (451 characters)
</div>
<div id="right">
#blog3 (157 characters)
#blog4 (358 characters)
#blog6 (155 characters)
</div>
I can't figure out how to do that without getting really complicated and probably inefficient.
So far I have thought about converting the description field (size) to % of total characters in the #blogs collection, but how do I match/split the elements, so that I get closest to 33% in each column - like a super simple tetris game :)
Any thoughts?
Here's a quick hack that isn't perfect, but might get you pretty close. The algorithm is simple:
Sort items by size.
Partition items into N bins.
Resort each bin by date (or other field, per your desired presentation order)
Here's a quick proof of concept:
#!/usr/bin/env ruby
# mock out some simple Blog class for this example
class Blog
attr_accessor :size, :date
def initialize
#size = rand(700) + 100
#date = Time.now + rand(1000)
end
end
# create some mocked data for this example
#blogs = Array.new(10) { Blog.new }
# sort by size
sorted = #blogs.sort_by { |b| b.size }
# bin into NumBins
NumBins = 3
bins = Array.new(NumBins) { Array.new }
#blogs.each_slice(NumBins) do |b|
b.each_with_index { |x,i| bins[i] << x }
end
# sort each bin by date
bins.each do |bloglist|
bloglist.sort_by! { |b| b.date }
end
# output
bins.each_with_index do |bloglist,column|
puts
puts "Column Number: #{column+1}"
bloglist.each do |b|
puts "Blog: Size = #{b.size}, Date = #{b.date}"
end
total = bloglist.inject(0) { |sum,b| sum + b.size }
puts "TOTAL SIZE: #{total}"
end
For more ideas, look up the multiprocessor scheduling problem.

Resources