2

let's say I have a model like this:

class Surface < ActiveRecord::Base
  attr_accessible :width, :length

  def area
     self.width * self.length
  end
end

Where @surface.area returns the area of a surface. Now, lets say I have many surfaces called @surfaces and I want to sum all areas. How do I do this?

My favorite solution would look like this:

@surfaces.sum(:area)

But it does not work. So how can I call .sum() or .group() or any similar functions on a virtual attributes like this in rails? I would like to avoid .each do |surface| calls.

Thanks in advance.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
kernification
  • 511
  • 1
  • 5
  • 15

2 Answers2

3

sum will quite happily accept an SQL expression instead of just a column name so all you need to do is translate your sum method into SQL:

@surfaces.sum('width * height')
@surfaces.sum('surface.width * surface.height') # If you think other tables might be involved.

Also, class methods on your models are mixed into relations (since scopes and class methods are really the same thing) so you could add a class method to go with your instance method:

class Surface < ActiveRecord::Base
  def self.area
    sum('surface.width * surface.height')
  end
  def area
     self.width * self.length
  end
end

and say:

@surfaces.area

You're usually better off letting the database do the heavy lifting with your data, that's what they're for and what they're good at.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • Thanks for your reply! Your solution is great but I'm still corious if there is any way to make the virtual `:area` attribute as useable as the real `:width` or `:length` attributes. So I can do `.sum(:area)` or `.group(:area)` just as I would do it with `:width`. `width * height` was just a simple examlpe it should represent any calculation or even more complex function for general purpose. – kernification Nov 04 '16 at 10:20
  • Sorry but ActiveRecord isn't smart enough to translate arbitrary Ruby code to SQL. – mu is too short Nov 06 '16 at 07:08
2

This can be achieved by Enumerable#reduce method:

@surfaces.reduce(0){ |sum, surface| sum + surface.area }
maicher
  • 2,625
  • 2
  • 16
  • 27