Calculation Logic In Rails App - ruby-on-rails

I have an rails app wherein I need to pick up data from user through UI store them in a DB which I am able to do without any hiccups. However I need to retrieve the stored data from DB and perform some calculations over them and then plot them using a graph library.
I read that calculation logic generally belongs to the model, hence my model file code is listed as below. But now I am wondering as to how to pass my resultant arrays to an view and get it plotted. Basically I am not getting the interconnection right. Any help on this would be much appreciated.
class Shot < ActiveRecord::Base
def self.calculate(param)
#fetch_all_three_shots
#Performs Query aginst DB and returns the same data structure depicted in 'shots' array
#shots = Shot.all.collect{|a| [a.c1,a.c2,a.c3,a.c4,a.c5,a.c6,a.c7,a.c8,a.c9,a.c10,a.c11,a.c12]}
#shots = [[1.55, 1.58, 1.59, 1.58, 1.53, 1.57, 1.57, 1.55, 1.57, 1.6, 1.62, 1.6],
# [1.54, 1.55, 1.58, 1.57, 1.49, 1.56, 1.55, 1.55, 1.54, 1.59, 1.61, 1.6],
# [1.55, 1.57, 1.59, 1.57, 1.51, 1.56, 1.56, 1.55, 1.56, 1.59, 1.62, 1.59]]
shots = fetch_all_three_shots
#Declaration of Array for all Data Sets Goes here...
cavity_arr = Array.new
avgpartwt_arr = Array.new
fillseq_arr = Array.new
imbalance_arr = Array.new
max_imbalance_arr = Array.new
intermediate_arr = Array.new
fillmeanwt_arr = Array.new
statistics_shotwise_arr = Array.new
statistics_bof_arr = Array.new
#Formulation of Cavity Array Goes Here...
cavity_arr = (1 .. 12).to_a
#Looping for Calculation of Average Part Weight Goes Here...
for i in (0 .. (shots[0].size - 1))
avgpartwt_arr.push((shots[0][i] + shots[1][i] + shots[2][i]) / 3)
end
#Rank Calculation Logic Goes Here....
fillseq_arr = avgpartwt_arr.map{|value| avgpartwt_arr.select{|item| item > value}.size + 1}
#Looping for Calculation of Percentage Imbalance Goes Here...
for i in (0 .. (avgpartwt_arr.size - 1))
imbalance_arr.push(((avgpartwt_arr.max - avgpartwt_arr[i]) / avgpartwt_arr.max) * 100)
end
#Looping for Maximum Imbalance Array Goes Here...
for i in (0 .. (imbalance_arr.size - 1))
if imbalance_arr[i] != imbalance_arr.max
max_imbalance_arr.push(0)
else
max_imbalance_arr.push(cavity_arr[i])
end
end
#Formulation of Intermediate Array Goes Here...
intermediate_arr = [((avgpartwt_arr.mean).round(3))] * 12
#Formulation of Fill Weight Mean Array Goes Here...
for i in (0 .. (avgpartwt_arr.size - 1))
fillmeanwt_arr.push(((intermediate_arr[i] - avgpartwt_arr[i]) / intermediate_arr[i]) * 100)
end
#Looping for Calculation of Shotwise Statistical Parameters Across Shots Goes Here...
for i in (0 .. (shots.size - 1))
statistics_shotwise_arr.push( [shots[i].max, shots[i].min, shots[i].range, shots[i].mean, shots[i].standard_deviation] )
end
#Formulation of Balance of Fill Parameters Goes Here...
cavmaximbalance = max_imbalance_arr.max
maximbalance = imbalance_arr.max
avgimbalance = imbalance_arr.mean
rangedrop = (((statistics_shotwise_arr[0][2] - statistics_shotwise_arr[2][2]) / statistics_shotwise_arr[0][2]) * 100)
end
scope :fetch_all_three_shots , -> {all.collect{|a| [a.c1, a.c2, a.c3, a.c4, a.c5 ,a.c6, a.c7, a.c8, a.c9, a.c10, a.c11, a.c12]}}
end

You could try the following:
class SomeController < ApplicationController
def some_action
#some_variable = Shot.calculate(params)
render :show_action
end
end
#some_variable, which is the return value of the Shot.calculate(params), is available in the view.

Related

Rails Helper to display rating in stars

Using Rails 6. Here's a piece that I wrote just to display number of stars. Obviously I am disgusted by my own code. How would you refactor?
# application_helper.rb
module ApplicationHelper
def show_star_rating(rating)
zero_star_icon_name = "star"
full_star_icon_name = "star_fill"
half_star_icon_name = "star_lefthalf_fill"
total_stars = []
round_by_half = (rating * 2).round / 2.0
(round_by_half.to_i).times { total_stars << full_star_icon_name }
if round_by_half - round_by_half.to_i == 0.5
total_stars << half_star_icon_name
end
if total_stars.size != 5
(5 - total_stars.size).times { total_stars << zero_star_icon_name }
end
total_stars
end
end
# show.html.erb
<% show_star_rating(agent_review.rating).each do |star| %>
<i class="f7-icons"><%= star %></i>
<% end %>
You can make use of the Array.new, passing in the maximum number of stars you want to show, and defaulting all the stars to empty. Then, you can fill in the number of full stars you need. Then, finally, thanks to Numeric's divmod returning either 0 or 1 for the number of half stars you need, you make one more pass and fill in the number of half stars you need:
module StarHelper
EMPTY_STAR_ICON = 'star'.freeze
FULL_STAR_ICON = 'star_fill'.freeze
HALF_STAR_ICON = 'star_lefthalf_fill'.freeze
def full_and_half_star_count(rating)
(rating * 2).round.divmod(2)
end
def stars(rating, max_stars: 5)
full_stars, half_stars = full_and_half_star_count(rating)
Array.new(max_stars, EMPTY_STAR_ICON).
fill(FULL_STAR_ICON, 0, full_stars).
fill(HALF_STAR_ICON, full_stars, half_stars)
end
end
The way I would implement show_star_rating:
def show_star_rating(rating)
zero_star_icon_name = "star"
full_star_icon_name = "star_fill"
half_star_icon_name = "star_lefthalf_fill"
rating_round_point5 = (rating * 2).round / 2.0
(1..5).map do |i|
next(full_star_icon_name) if i <= rating_round_point5
next(half_star_icon_name) if rating_round_point5 + 0.5 == i
zero_star_icon_name
end
end

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.

Repeating same block of code over an ActiveRecord Object's attributes in Ruby (Rails)

I have a basic function which takes in different values for weight and reps for a barbell lift, and converts it to an estimated one rep max, which gets stored in my database. Here is what the code currently looks like:
if deadlift_reps != 1
self.deadlift = deadlift_wt * deadlift_reps * 0.0333 + deadlift_wt
else
self.deadlift = deadlift_wt
end
if squat_reps != 1
self.squat = squat_wt * squat_reps * 0.0333 + squat_wt
else
self.squat = squat_wt
end
if benchpress_reps != 1
self.benchpress = benchpress_wt * benchpress_reps * 0.0333 + benchpress_wt
else
self.benchpress = benchpress_wt
end
if overheadpress_reps != 1
self.overheadpress = overheadpress_wt * overheadpress_reps * 0.0333 + overheadpress_wt
else
self.overheadpress = overheadpress_wt
end
I am obviously repeating myself a lot in this code block, but cannot figure out how to break it out and interpolate the method names. I've tried using an array with
lifts = [deadlift, squat, benchpress, overheadpress]
But when I try using each do on this to string interpolate, I am getting an error that lifts doesn't have this method.
Can anyone offer me any advice on how to proceed?
edit:
The loop I was trying to do was:
lifts = [deadlift, squat, benchpress, overheadpress]
lifts each do |lift|
if '#{lift}_reps' !=1
self.#{lift} = '#{lift}_wt' * '#{lift}_reps' * 0.0333 + '#{lift}'_wt
else
self.#{lift} = '#{lift}_wt'
end
end
I can see why this isn't working, but, using a similar idea, how would I make it work?
I would create a private method to calculate every piece of:
if deadlift_reps != 1
...
end
and assign the values to different fields (deadlift, squat, benchpress, overheadpress) using the assign_attributes Active record methods.
Here is the code, I didn't test it, but it should work:
[["deadlift", 20, 8],
["squat", 10, 12],
["benchpress", 30, 6],
["overheadpress", 20, 10]]).each do |exercize, wt, reps|
set_exercize(exercize, wt, reps)
end
private
def set_exercize(exercize, wt, reps)
value = reps != 1 ? wt * reps * 0.0333 + wt : wt
self.assign_attributes(exercize => value)
end
I hope it can help you.

Choosing which form field to submit

I have a CommercialDocument model which have a discount_amount attribute and a discount_amount_with_tax virtual attribute.
Here is how I defined this in my model :
def discount_amount_with_tax
discount_amount * (1 + tax.rate / 100)
end
def discount_amount_with_tax=(amount)
self.discount_amount = amount.to_f / (1 + tax.rate / 100)
end
In my form, a user can fill in both discount_amount and discount_amount_tax :
= f.label :discount_amount
= f.text_field :discount_amount
= f.text_field :discount_amount_with_tax
I want to give the priority to the discount_amount_with_tax field, which means that discount_amount must not be taken into account unless the other field is empty.
My problem is that if I put nothing in the discount_amount_with_tax field, and let's say 10 in discount_amount, then discount_amount will be equal to 0, which is clearly not what I want.
How can I fix this ?
Any help would be greatly appreciated.
"".to_i
# => 0
A blank string converts to a zero integer. Therefore:
def discount_amount_with_tax=(amount)
self.discount_amount = amount.to_f / (1 + tax.rate / 100)
end
# same as...
def discount_amount_with_tax=(0)
self.discount_amount = 0 / (...)
end
# 0 / anything except zero = 0
# self.discount_amount = 0 no matter what
During mass-assignment, discount_amount_with_tax= is called. A blank form input is passed as an empty string, which Active Record then converts to an integer (zero). discount_amount_with_tax= sets discount_amount to zero regardless of discount_amount's previous value.
Easy way around this is to use a conditional:
def discount_amount_with_tax=(amount)
self.discount_amount = (amount.to_f / (1 + tax.rate / 100)) if amount > 0
end
Mind you, this is the easy way, not the ideal way. A better solution is to write custom setter logic in the controller in lieu of mass-assignment; basically to manually set these attributes in the controller.
I think you can use a before_validation callback here to set the discount_amount field.
before_validation :calculate_discount_amount
def discount_amount_with_tax
#discount_amount_with_tax ||= discount_amount * (1 + tax.rate / 100.0)
end
def discount_amount_with_tax=(amt)
#discount_amount_with_tax = amt
end
def calculate_discount_amount
self.discount_amount = discount_amount_with_tax / (1 + tax.rate / 100.0)
end
Just a reminder that you need to use 100.0 instead of 100 so that you'll be dividing by float and not by integer.

Creating a Bayesian Average for each Product

I would like to use the following formula to calculate a Bayesian score for each of my products, based on their scores (0-100) and number of votes:
( (avg_num_votes * avg_rating) + (this_num_votes * this_rating) ) /
(avg_num_votes + this_num_votes)
Each product has_many reviews, and each review has a 'score' which is 0-100.
When pulling the list of products from the database what would be the best approach to calculating the Bayesian score to be listed in a table in the view?
EDIT: I am looking for something along the lines of:
Product.each do |product|
#product_bayesian = ((Review.count * Review.average(:score)) + (product.reviews.count + product.reviews.average(:score))/(Review.count+product.reviews.count)
end
but, done in the most efficient way possible, possibly through a join in the controller?
Here is my solution:
def overall
#products = Product.all
#overall_count = Review.count
#overall_average = Review.average(:score)
#unranked = {}
#products.each do |product|
#review_score_raw = product.reviews.average(:score)
#review_score = #review_score_raw.to_int if #review_score_raw
#review_count = product.reviews.count
if product.reviews.count == 0
#bayesian = 0
else
#bayesian = (((#overall_count * #overall_average) + (#review_count * #review_score_raw))/(#overall_count + #review_count))
end
#unranked[product] = #bayesian
end
#ranked = #unranked.sort_by {|key, value| value}.reverse.each_with_index.to_a
end

Resources