Rails: working with calculated values - ruby-on-rails

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

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.

Rails nested forms: assign index as value to DB

I'm building a booking system for hotels and created a price model, which is accepted by the room model as nested attributes. So far so good, everything works perfectly fine. However, since I want to give the opportunity to be able to maintain price increments, I need the responding days in my price model. So basically, price: 50, 80, 120 along with days: 1, 2, 3 ...ok, so now my question: how can I achieve that the days are saved automatically in the price table, so that the user only needs to enter the price for a specific day.
I've tried to get it working with a hidden field that submits to the days column, but I couldn't fix it. As I have 20 increments generated by the controller, I've tried it with the index generated by the fields_for helper.
here's the relevant part of my controller:
def new
#room = current_user.rooms.build
20.times do
increment = #room.increments.build
end
end
And here's my view:
<%= form_for #room do |f| %>
#...
<%= f.fields_for :increments do |ff| %>
<%= ff.text_field :price, placeholder: 'price' %>
<%= ff.hidden_field :days, value: 'index' %>
Do you have any ideas how I could achieve to get the value of my hidden_field to equal 1...20 with respect of my controller that generates 20 price fields? Do I need to write a form helper and if yes, how could I get it to add the number of days? Am I totally wrong with my approach and there is a more obvious way to achieve the same functionality? Any help highly appreciated!
I'm not entirely sure this is what you're asking, but could you use something like:
def new
#room = current_user.rooms.build
(1..20).each do |i|
#center.increments.build(days: i)
end
end
Or the building in a single line:
(1..20).each { |i| #center.increments.build(days: i) }
(Assigning increment in the loop isn't used anywhere, and will update to the last new increment for each element of the loop; therefore I've removed it.)
Edit: what #oscar said...
Do you always want to generate 20 times increments ?
well this line 20.times do is a loop. What would I do will be something like:
20.times do |t|
increment = #center.increments.build(days: t+1) # t+1 since t starts in 0
end
In this way you set up the days at the building of the instances

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

How do I add an attribute to an instance variable in rails 4?

Using Rails 4, in a controller I would like to add an attribute to an instance variable.
Sorry for the poor example, I'm trying to keep it simple.
E.g. In a controller, I create a new instance variable by looking up some users named John. Now, in my controller, I would like to sum up all the ages for all Users named John, put that summed age back in to the instance variable so it is available to the view.
The User model has attributes 'id', 'name' and 'age'.
#foo_users = Users.where(name: 'John')
#foo_users.each do |foo|
#foo_users.age_sum = Users.where(name: 'John').sum(:age) <-- this does not work
end
I have no need to save that summed age back to a database, since I will only use it in one view. I would like to be able to display all the users:
<% #foo_users.each do |user| %>
User name: <%= user.name =>
Sum of ages: <%= user.age_sum %>
<% end %>
Update: I might have over simplified my example. Here is a closer to reality example.
A company owns hotels. Hotels have Rooms. Management software delivers to the company daily Hotel_Statistics via an API. For lack of a better word, these Hotel_Statistics contain the hotel_id, daily check-ins, daily check-outs. In the company's back-office Rails app that I am working on, on the page displayed there is a table of hotels with their given most recent statistics. One line would look like:
Hotel Id: 123
Daily check-ins: 50
Daily check-outs: 48
Hotel Id: 124
Daily check-ins: 35
Daily check-outs: 37
The company wants to also display the running sum of the last 30 days of check-ins (outs, net check-ins).
To accomplish this, in my controller, I find the Hotel_Statics for the most recent date (normally yesterday).
latest_stat = HotelStatistic.order('date DESC, hotel_id DESC').first
#latest_date = latest_stat.date
#recent_stats = HotelStatistic.where(date: #latest_date).order('hotel.id ASC').all
I display the details of #recent_stats in my view.
Now, I would like to display in my view the sum of the last 30 days of #recent_stats.check_ins for each Hotel. My idea was to sum up the the last 30 days of check_ins statistics for a given Hotel like:
#recent_stats.each do |stat|
#last_30_days_check_ins = HotelStatistic.where(hotel_id: stat.hotel_id).where("date >= ?", Date.today - 30).sum(:check_ins)
end
The math works, but I need a way to access the 30 day sum variable for each hotel. I was a hoping to make this easy in the view by adding the hotel 30 day sum to the #recent_stats instance variable so in my view I could do:
<% #recent_stats.each do |statistic| %>
Hotel Id: <%= statistic.hotel_id %>
Daily check-ins: <%= statistic.check_ins %>
Last 30 days check-ins: <%= statistic.last_30_days_check_ins %>
<% end %>
Does this more realistic example change anything in your suggested answers? Thanks
The type of #foo_users is ActiveRecord::Relation. Trying to add age_sum as a new attribute to an ActiveRecord::Relation object doesn't make sense because semantically age_sum is not an attribute of ActiveRecord::Relation objects. It's better to store the sum of ages in a new instance variable, for example #user_age_sum.
UPDATE
Try the following
class HotelStatistic < ActiveRecord::Base
belongs_to :hotel
end
class Hotel < ActiveRecord::Base
has_many :hotel_statistics
def last_30_days_check_ins
self.hotel_statistics.where("date >= ?", 30.days.ago).sum(:check_ins)
end
end
Keep the existing code for building #recent_stats in the controller
In the view
<% #recent_stats.each do |statistic| %>
Hotel Id: <%= statistic.hotel_id %>
Daily check-ins: <%= statistic.check_ins %>
Last 30 days check-ins: <%= statistic.hotel.last_30_days_check_ins %>
<% end %>
Using select should solve your problem:
#users = User.select("*, SUM(age) as age_sum").where(name: 'John')
Now each User in the #users array will have a age_sum property. This is not 100% ideal as the value of the property will be the same on each instance, but it will work with how you've setup your view.
Edit
It's possible to dynamically define a method on an instance manually:
#foo_users.each do |foo|
def foo.age_sum; Users.where(name: 'John').sum(:age); end;
end
However while this is possible, it would have to be a very good use case to justify the negative impact this may have (such as on how readable, efficient and maintainable the code is). There are probably much better OO ways to solve the same problem

Can View call some Model methods in Rails?

First let me explain an example:
In Model:
class Product < ActiveRecord::Base
has_many :line_items
def income
self.line_items.sum(:price)
end
def cost
self.line_items.sum(:cost)
end
def profit
self.income - self.cost
end
end
Then in Controller:
def show
#products = Product.all
end
And in View:
<% #products.each do |product| %>
Product Name: <%= product.name %>
Product Income: <%= product.income %>
Product Cost: <%= product.cost %>
Product Profit: <%= product.profit %>
<% end %>
Is it a good practice to call model methods from view?
When I searched for that, I found many people saying it is NOT a good practice to ever call model methods or access DB from views.
And on the other hand, some others said that don't call class methods or any method updates the DB from view but you can access any method that only retrieve data.
Then, is this code a good practice?
Its perfectly fine to call the object-methods/attributes from the view, as long as the call would not change the data. I mean, call readers/getters. A Bad practice would be to call/invoke methods that update/delete the data. Don't call setters.
Also, if there is any complex computation involved, resort to helpers.
Since your methods need to access line_items association, to avoid N+1 problem and calling DB queries from view, I'd advice fetching your line_items in show action, with includes:
def show
#products = Product.includes(:line_items)
end
With this adjustment, I think it's ok to call these methods in view.

Resources