Andy Harvey
Andy Harvey

Reputation: 12663

How do you perform calculations on multiple Rails models, and where should the logic go?

I have an Invoice model in a Rails app. The Invoice has_many Products and Services (two separate models). The Invoice show view displays these associated records.

Both Products and Services have a similar structure, i.e., Item, Price, Quantity. I can calculate line totals as price * quantity and I can calculate the total price of Products as all.collect { |product| product.price * product.quantity }, same for Services.

What if I now want to calculate the total cost of Products + Services on this Invoice?

How can I perform calculations across multiple models? And where should the calculation logic go?

I realize one solution would be to combine Products and Services into a single model, but this is not an option here.

I'm lost as to where to start with this, and would really appreciate some pointers, ideas or links to useful documentation.

Upvotes: 3

Views: 1099

Answers (3)

corroded
corroded

Reputation: 21584

Just to check:

  • Invoice has_many products and services
  • Products has a total_cost method(or total_price)
  • Services also has a total_cost method

and you're asking where to put the logic for total price of products and services of an invoice. Correct?

In that case, you are correct to put it in Invoice:

def total_cost_of_products
  products.collect { |product| product.price * product.quantity }
end

def total_cost_of_services
  services.collect { |services| service.price * service.quantity }
end

def total_price_of_products_and_services
  total_cost_of_products + total_cost_of_services
end

Of course you can DRY this up more and you probably want some unit tests on your invoice model. See lucapette's answer for a good way to refactor and DRY it up

Upvotes: 2

lucapette
lucapette

Reputation: 20724

In short: Use modules. I'm thinking something like:

module TotalCalculator
  def sum
     map { |e| e.price * e.quantity }
  end
end

And then you can:

class Product
  include TotalCalculator
end

Of course, it's just a trivial example and the naming is terrible. By the way, you can get the picture. I would put all the logic in a dir like app/logic or in the lib directory. Sometimes I call this technique module as business logic pattern but I don't know if it's a proper name.

Upvotes: 2

Nick
Nick

Reputation: 2478

Can you not just make a virtual attribute on your invoice model total_cost and then put your code in there?

def total_cost 
  cost = #do your calculation here....
  return cost  
end

Upvotes: 0

Related Questions