I have a model which contains items for sale. The model is mirroring an API, so I can't change it's structure.
In this model I have the fields price and sold_quantity.
It doesn't seem right to iterate through the query with a loop.
current_user.items.each do |item|
total += item.price * item.sold_quantity
end
Is there a way to get the same total using only ActiveRecord? Like .sum(:price) but multiplying by sold_quantity?
You probably want this:
current_user.items
.select('items.*, items.price * items.sold AS total')
.all
Or, if you want the totals and nothing else:
current_user.items.pluck('items.price * items.sold')
You can do this:
current_user.items.sum { |item| item.price * item.sold_quantity }
I hope this help you.
For more information sum in AR
One way you could approach this would be to move this logic into the Item class:
class Item < ActiveRecord::Base
...
def total
self.price * self.sold_quantity
end
def self.sold_value
all.map{|a| a.total}.sum
end
end
Here each instance of item is calculating it's own total, and a class method allows you get the sold value on any collection of items.
#user.items.sold_value
Related
I have a Campaign model that has_many items.
Each item has :quantity and :price
I want to calculate the total price for the Campaign.
I tried with this but won't work.
def campaign_subtotal(campaign)
return campaign.items.sum(item.price * item.quantity)
end
How is this approached?
Thanks a lot
The naive implementation is:
class Campaign < ApplicationRecord
has_many :items
def subtotal
items.sum { |item| item.price * item.quantity }
end
end
You were somewhat close but you need to pass a block to calculate the sum per item. It also makes more sense to make this an instance method rather then a helper.
However if you want to use the subtotal in database queries or you want to display a bunch of campaigns efficiently you would want to calculate it in SQL with a subquery, window query or lateral join. You should also consider recording the price per line (the subtotal) instead of recalculating it all the time.
I have a model Invoices
Table Invoices:
ID, PRODUCT, UNITS, PRICE
In my view, I want to show total for some columns.
For instance to show total quantity I can set in my controller:
#invoices = Invoice.group(:PRODUCT).select(ID, PRODUCT, UNITS, SUM(UNITS) AS TOTALUNITS, PRICE").order('PRODUCT ASC')
#units_total = #invoices.map(&:UNITS).sum
and in my view
<%= #units_total %>
and it returns the total in column UNITS. This works fine.
If I define in my model:
def total_amount
(self.PRICE * self.TOTALUNITS)
end
and then on the same way I want to show the total of total_amount, I tried in my controller:
#amount_total = #invoices.map(&:total_amount).sum
it doesn't work, as I assume that if I'm not using a column name the syntax must be different.
What should I enter then?
UPDATE
The problem comes form the fact that I'm using in model (self.PRICE * self.TOTALUNITS). I didn't include it when I posted the question as I thought it didn't matter. But if I replace(self.PRICE * self.TOTALUNITS) with (self.PRICE * self.UNITS) there's no error but values are obviously wrong.
What you have taken is correct, I think some of the invoices doesnot have the value, check using try,
def total_amount
price = self.try(:PRICE) || 0
units = self.try(:UNITS) || 0
price * units
end
make 0 if the corresponding field value is absent, so that you will not get an error.
You can check if this is working or not
#amount_total = #invoices.map{ |invoice| invoice.PRICE.to_f * invoice.UNITS.to_i }.sum
I don't know what error you are getting but it might be due to null values in the column.
Hope that helps!
I am trying to write a method that will find each price and add it to the current price.
Quote.rb:
class Quote < ActiveRecord::Base
has_many :items
def calc_price
sum = 0
items.each do |item|
item.price
end
sum = (item1 + item2 etc)
end
end
items.inject(0){|sum, item| sum += item.price}
Here's a nice feature about the more current Rubies:
values = [1,2,3,4,5]
values.inject(:+) # => 15
Now, that said, you're working with a database, so have it sum the records. From the documentation:
Calculates the sum of values on a given column. The value is returned with the same data type of the column, 0 if there’s no row. See calculate for examples with options.
Person.sum('age') # => 4562
In this case, you can offload the calculation to the database.
def total_price
items.sum('price')
end
Reference: http://api.rubyonrails.org/classes/ActiveRecord/Calculations.html#method-i-sum
You need to sum the price within the .each loop
class Quote < ActiveRecord::Base
has_many :items
def calc_price
sum = 0
items.each do |item|
sum += item.price
end
end
end
You can further compact that loop to a single line using
items.each {|item| sum += item.price}
How about
items.map(&:price).inject(0, :+)
The map part maps the items array to an array of prices and the inject part starts with 0 and adds each element of the price array.
Mapping StackOverflow question
inject docs
inject examples
So I have a Vendor model, and a Sale model. An entry is made in my Sale model whenever an order is placed via a vendor.
On my vendor model, I have 3 cache columns. sales_today, sales_this_week, and sales_lifetime.
For the first two, I calculated it something like this:
def update_sales_today
today = Date.today.beginning_of_day
sales_today = Sale.where("created_at >= ?", today).find_all_by_vendor_id(self.id)
self.sales_today = 0
sales_today.each do |s|
self.sales_today = self.sales_today + s.amount
end
self.save
end
So that resets that value everytime it is accessed and re-calculates it based on the most current records.
The weekly one is similar but I use a range of dates instead of today.
But...I am not quite sure how to do Lifetime data.
I don't want to clear out my value and have to sum all the Sale.amount for all the sales records for my vendor, every single time I update this record. That's why I am even implementing a cache in the first place.
What's the best way to approach this, from a performance perspective?
I might use ActiveRecord's sum method in this case (docs). All in one:
today = Date.today
vendor_sales = Sale.where(:vendor_id => self.id)
self.sales_today = vendor_sales.
where("created_at >= ?", today.beginning_of_day).
sum("amount")
self.sales_this_week = vendor_sales.
where("created_at >= ?", today.beginning_of_week).
sum("amount")
self.sales_lifetime = vendor_sales.sum("amount")
This would mean you wouldn't have to load lots of sales objects in memory to add the amounts.
You can use callbacks on the create and destroy events for your Sales model:
class SalesController < ApplicationController
after_save :increment_vendor_lifetime_sales
before_destroy :decrement_vendor_lifetime_sales
def increment_vendor_lifetime_sales
vendor.update_attribute :sales_lifetime, vendor.sales_lifetime + amount
end
def decrement_vendor_lifetime_sales
vendor.update_attribute :sales_lifetime, vendor.sales_lifetime - amount
end
end
Picture a normal invoice: On it, you have several items. Each item has a quantity and a price per unit, among other things (unit and description).
The total amount for each item is calculated like this: quantity * price per unit.
This is done for each item. Then, the overall invoice's net amount is the sum of all the totals. Add VAT, and you have the invoice's gross amount.
This is what I am trying to do with my Rails app. An invoice has many items and accepts nested attributes for them. Generally, this all works fine.
Following the logic, all I need to enter in manually is the price per unit and the quantity for each item as well as the invoice's VAT. The totals and the resulting net and gross amount should be calculated automatically. I want to achieve this using the before_save filter.
Here is my invoice model:
before_save :calculate_net_amount, :calculate_gross_amount
def calculate_net_amount
self.items do |item|
self.net_amount += item.total
end
end
def calculate_gross_amount
self.gross_amount = self.net_amount * (1 + self.vat_rate)
end
This is the item model:
before_save :calculate_total
def calculate_total
self.total = self.quantity * self.price_per_unit
end
And here is my spec that is failing:
it "calculates the net amount from all item totals" do
invoice = FactoryGirl.build(:invoice)
item = invoice.items.build(quantity: 2, unit: "Unit", description: "Desc", price_per_unit: 2)
invoice.save
invoice.net_amount.should == 4
end
It uses this invoice factory:
FactoryGirl.define do
factory :invoice do
association :client
currency "EUR"
vat_rate 0.19
net_amount 1
payment_on "2013-01-01"
status "paid"
end
end
The test basically does the following: An invoice with 2 things that both cost USD 2 should have a net amount of USD 4. Instead, the test returns 1, which seems to come from the factory and apparently isn't overwritten. If I remove it from the fixture, it says that it cannot calculate the gross amount anymore, since it can't use * on any nil object.
I am assuming I am doing something wrong with the filters and the order in which they are called - the total amounts are calculated correctly, so it has to be something about the calculate_net_amount method that's going wrong and as a result it can't calculate the gross amount anymore.
Can you see what I am doing wrong?
self.items do |item|
should be
self.items.each do |item|
Since items is an accessor, which is a method, it can technically take a block, but that block isn't getting called, so no summing is happening. It's an easy typo to make.
As an aside, it's better is to sum using inject:
self.net_amount = self.items.inject(0){|sum, item| sum + item.total}