3

I am trying to rewrite these three functions in a single one:

def self.net_amount_by_year(year)
  year(year).map(&:net_amount).sum
end

def self.taxable_amount_by_year(year)
  year(year).map(&:taxable_amount).sum
end

def self.gross_amount_by_year(year)
  year(year).map(&:gross_amount).sum
end

Can anybody help?

This is what I've got so far:

def self.amount_by_year(type_of_amount, year)
  year(year).map(&type_of_amount.to_sym).sum
end

The &type_of_amount bit doesn't work of course. And I wonder how to do that.

Thanks for any help.

P.S.: By the way, I don't even know what the & is for. Can anybody explain?

Tintin81
  • 9,821
  • 20
  • 85
  • 178

2 Answers2

2

This should work:

def self.amount_by_year(type_of_amount, year)
  year(year).map{|y| y.send(type_of_amount)}.sum
end

In fact, you should just be able to do this:

def self.amount_by_year(type_of_amount, year)
  year(year).sum{|y| y.send(type_of_amount)}
end

References:
Ruby send method
Rails sum method

Carlos Drew
  • 1,633
  • 9
  • 17
  • 2
    By the way, the & in map is shorthand for referencing an attribute or method on the objects (in lieu of using a block, as I did in my answer above). http://stackoverflow.com/questions/1217088/what-does-ampersand-colon-pretzel-colon-mean-in-ruby – Carlos Drew Sep 10 '13 at 17:27
  • 2
    Depending upon usage, for safety you might add a check to make sure `type_of_amount` matches one of your 3 methods. You wouldn't want someone to call `amount_by_year(:delete, 2013)` – Kyle Heironimus Sep 10 '13 at 17:44
  • 1
    Kyle is absolutely correct that you should whitelist your passed attribute/method in some way. Although my answer satisfies your question, I would be careful in designing an application in this fashion. – Carlos Drew Sep 10 '13 at 17:46
2

Your code should work as is if you give it a symbol (to_sym is redundant).

def self.amount_by_year(type_of_amount, year)
  year(year).map(&type_of_amount).sum
end

type_of_amount to be passed should be either :net_amount, :taxable_amount, or :gross_amount.

If you want to compact the arguments, you can even do:

def self.amount_by_year(type, year)
  year(year).map(&:"#{type}_amount").sum
end

and pass to type either :net, :taxable, or :gross.

In fact, you can do:

def self.amount_by_year(type, year)
  year(year).sum(&:"#{type}_amount")
end
sawa
  • 165,429
  • 45
  • 277
  • 381
  • This is also very good. Thank you! I still wonder what is best here, `.map(..).sum` or using a block as Carlos suggested. – Tintin81 Sep 10 '13 at 17:44
  • 1
    You can combine them to make it even shorter. See my addition. – sawa Sep 10 '13 at 17:46