Welcome to Working With Rails

 

Discussion Forums

Discuss all things Ruby on Rails with perhaps the web's most vibrant group of Ruby on Rails enthusiasts.
Help with Methods for Multiple objects
12 Posts
Help with Methods for Multiple objects

Hi Jason Thanks very much for that. Its good to grasp that the context of the use of sum is what defines it. The activesupport methods will com in very handy I think.

Hi Adam,

Thanks for your long and detailed explanation. I am learning so much from my simple question. It just gives me such respect for you guys that have such a detailed understanding of this. If you told me before I started trying to learn this that their was a sum method and a sum method and a sum method and they were all different I would have probably decided to leave it alone! But now I am hooked in and its too late!! Thankyou.

Am I right in thinking it is slightly inefficient to find the objects in the controller first so that I can iterate through them in the view to display one attribute and then effectively find them again with the activesuport sum method to find the sum when all the information that I need is already continued in that array of phonecalls from the original find. So using the total method with the array passed to it should be more efficient with regard to database calls.

I need to work through what I have learned a few times on Monday to make sure I have really understood what I have learned.

My find with conditions returns an array of phonecalls that do not have cost centers assigned to them for a certain period. The cli model is the dialled out line number and so each line number that has not been associated to a cost center has all its phonecalls returned. I would like to display each of the unique cli and the total of its phonecalls cost. So in my array of phonecalls many of them are for the same cli. Could you point me in the direction of how to iterate through the array of calls and identify the unique cli attribute and then the total sum.

That probably makes no sense!

Anyway I figure I am going to need to create a class method with an array passed any tips much appreciated.

Thanks again everybody for your help, the Rails community seems to be one of its greatest attributes.

If anybody is happy to pass their email or skype name to allow me to tap them up occasionally when I get stauck then I would really appreciate it.

Richard

Richard,

You're right that the example I gave you for passing an array of PhoneCall objects to a class methods to determine the sum is incorrect. I left off one bit. If you take a look at the #sum method for Enumerable (defined in ActiveSupport), you'll see it can take a block. So, my initial suggestion was something like this:

def self.total_cost(phone_calls)
  phone_calls.to_a.sum
end

However, calling #sum on an Array without a block will simply try to add up the elements of the array. The PhoneCall class likely doesn't (and shouldn't) respond to the + operator, so this won't work. Passing a block to #sum allows you to tell it what values you want the sum of. First, it will execute the block on each element of the array, then it will compute the sum of the results. So, this will work:

def self.total_cost(phone_calls)
  phone_calls.to_a.sum { |phone_call| phone_call.cost }
end

Kevin suggested you try using a shorthand syntax that Rails provides by defining the #to_proc method on symbols:

def self.total_cost(phone_calls)
  phone_calls.to_a.sum(&:cost)
end

The last two examples do exactly the same thing, functionally. Whenver you see the block operator (&) applied to a symbol in Rails, it will expand to a block that takes one parameter and returns the result of sending the symbol to that parameter. It does this by calling #to_proc on the symbol, which is standard Ruby behavior for the & operator.

This is all further obfuscated by the fact that I've so far given examples only for the #sum method defined on the Enumerable class by ActiveSupport. Jason has been giving examples for the #sum class method defined for ActiveRecord::Base. The latter is what you want to use for generating the sum for a given colum in the database, using the standard finder options to limit the rows included in the sum. The Enumerable#sum method will allow you to add up most anything else.

Take a look at "the docs for the sum method":http://api.rubyonrails.org/classes/ActiveRecord/Calculations/ClassMethods.html#M002134 See how it accepts two parameters, the column name and an options hash? That options hash is the same options hash that you can pass to the other finders.

So basically your method would become something like:

def self.period_cost( period_id, cli_id )
  sum(:cost, :conditions => ['period_id=? AND cli_id=?', period_id, cli_id])
end

bq. I am trying to find the total cost of particular phonecall that I can find with a condition. bq.

I mean partiular phonecalls. Sorry thought I would make that clear.

This is the find I am doing in the controller. I am trying to then present the sum of the :cost attribute of these objects.

@phonecalls_for_this_period_without_cost_centres = Phonecall.find(:all, :conditions => ["period_id=? and cli_id=?", @period, @clis_without_cost_centres])

Hi Adam,

Thanks as well for your input.

Your response helps me better understand the class method and I see that Jasons method returns for me the total cost of all phonecalls and I now undertsnd how it is doing that.

I am still a little confused however about passing the array into the method. I presume that you mean an array of Phonecall objects?

I am trying to find the total cost of particular phonecall that I can find with a condition.

What confuses me about your method is that if I pass an array of phonecalls to the total cost method how does it know that I want the sum of the cost attribute?

I can"t do phonecalls.cost because there is no cost method. I hope that makes sense.

Hi Kevin, Thanks also for your input.

I presume that you mean I can use this in the view on the array instance of my phonecalls array. Would you be so kind as to explain (&:cost) syntax. Is it an option on the sum method that asks to sum only the cost part of the hash? or is (&:attribute) a more general syntax.

Thanks sorry to respond with more questions its just I would like to really undertsand what I am doing so that I can use these things later rather than copy and paste parrot fashion without really understanding.

Hi Richard,

You might try:

  @phonecalls.sum(&:cost)

Richard,

Jason's example of a class method is taking advantage of the fact that your Phonecall class is an ActiveRecord. ActiveRecord provides the #sum class method, which your Phonecall class has access to as a subclass of ActiveRecord::Base. This method returns the sum of all rows in a particular column in the database, in this example the :cost column. The result comes from a SQL statement that ActiveRecord creates and runs against your DB for you.

If you want to calculate your total cost without using ActiveRecord methods or the database, you could pass an array to the class method like this:

def self.total_cost(phone_calls)
  phone_calls.to_a.sum
end

Now, this is using the #sum method defined on the Array class (the call to #to_a is necessary if you pass an association proxy to the method, since the ActiveRecord#sum class method overrides the Enumerable#sum method; a subtle point you can probably ignore for now).

You also may want to consider why you're collecting this data in a class method. Perhaps it would make more sense to think about where a #total_cost method should live, such as on a PhoneBill object. A PhoneBill could have many PhoneCalls, and therefore it would make sense for a PhoneBill to have a #total_cost method on the instance.

Hi Jason,

Thanks again for your input.

I am still confused however.

On this basis it is still only possible to use a method against a single object. If cost is an attribute of the sigle object then when I want the total cost of a collection of those objects then the sum needs to be of an array of those objects. If I try and keep the code out of the view and into the model, how do I create a method that totals the sums of a collection of the objects.

Your example of a class method still only operates on a single object. nd so the sum of the cost will only be the cost.

I hope that makes sense. I am trying to solve this particular problem and also learn how to do similar things when I want to work with the information of a collection of objects.

Thanks again.

Richard

Heh, sorry. So, a class method is called on the class, ie.: @Phonecall.total_cost@

Hi Jason Thanks for your time it is much appreciatd.

I understand now that @phonecalls is of the class Array. @phonecall is a singl;e object of the class Phonecall.

I have tried your class method and this still returns a no method error. I think I understand the difference between a class method and an instance method and I am unsure how this would allow the total_cost method to act on an array? Should I create a class method that accepts an array and pass @phonecalls to it? total_cost(@phonecalls)?

Sorry for my ignorance but appreciate your enligtenment.

Thanks Richard

12 Posts
Login to add your message