marcamillion
marcamillion

Reputation: 33765

How do I sum the product of two values, across multiple objects in Rails?

Imagine I have a portfolio p that has 2 stocks port_stocks. What I want to do is run a calculation on each port_stock, and then sum up all the results.

[60] pry(main)> p.port_stocks
=> [#<PortStock:0x00007fd520e064e0
  id: 17,
  portfolio_id: 1,
  stock_id: 385,
  volume: 2000,
  purchase_price: 5.9,
  total_spend: 11800.0>,
 #<PortStock:0x00007fd52045be68
  id: 18,
  portfolio_id: 1,
  stock_id: 348,
  volume: 1000,
  purchase_price: 9.0,
  total_spend: 9000.0>]
[61] pry(main)> 

So, in essence, using the code above I would like to do this:

ps = p.port_stocks.first #(`id=17`)
first = ps.volume * ps.purchase_price # 2000 * 5.9 = 11,800

ps = p.port_stocks.second #(`id=18`)
second = ps.volume * ps.purchase_price # 1000 * 9.0 = 9,000

first + second = 19,800

I want to simply get 19,800. Ideally I would like to do this in a very Ruby way.

If I were simply summing up all the values in 1 total_spend, I know I could simply do: p.port_stocks.map(&:total_spend).sum and that would be that.

But not sure how to do something similar when I am first doing a math operation on each object, then adding up all the products from all the objects. This should obviously work for 2 objects or 500.

Upvotes: 0

Views: 736

Answers (2)

SRack
SRack

Reputation: 12203

The best way of doing this using Rails is to pass a block to sum, such as the following:

p.port_stocks.sum do |port_stock|
  port_stock.volume * port_stock.purchase_price
end

That uses the method dedicated to totalling figures, and tends to be very fast and efficient - particularly when compared to manipulating the data ahead of calling a straight sum without a block.

A quick benchmark here typically shows it performing ~20% faster than the obvious alternatives.

I've not been able to test, but give that a try and it should resolve this for you.

Let me know how you get on!


Just a quick update as you also mention the best Ruby way, sum was introduced in 2.4, though on older versions of Ruby you can use reduce (also aliased to inject):

p.port_stocks.reduce(0) do |sum, port_stock|
  sum + (port_stock.volume * port_stock.purchase_price)
end

This isn't as efficient as sum, but thought I'd give you the options :)

Upvotes: 6

guitarman
guitarman

Reputation: 3310

You are right to use Array#map to iterate through all stocks, but instead to sum all total_spend values, you could calculate it for each stock. After, you sum all results and your done:

p.port_stocks.map{|ps| ps.volume * ps.purchase_price}.sum

Or you could use Enumerable#reduce like SRack did. This would return the result with one step/iteration.

Upvotes: 2

Related Questions