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 I am learning rails and have hit a bit of a wall.

I have created methods previousley that work fine on a single object. However in my latest project I am trying to calculate the total cost of a group of phonecalls and I am having difficulty creating a method to calculate this. I have a phonecall model and phonecall has a cost attribute.

In my controller I am finding the @phonecalls And in my view I want to say @phoncalls.total_cost

However I keep getting an undefined method error.

In my phonecall model i have the following method for totl_cost. def total_cost totalcost=0 each do |phonecall| totalcost += phonecall.cost end totalcost end

I have tried varios types of blocks, various connertaions of this method and self.each.... but I dont seem to be getting anywhere.

I am not sure if this is because of the fact that my syntax is wrong when dealing with a collection of objects as I am in this case.

I can iterate through those objects in the view ie for each phonecall in @phonecalls etc and I am getting the correct output so I am happy that the controller code is working fine.

Any help would be muchly appreciated as I am struggling to find help in my books and online. Thank you

So, this is less about Rails, and more just about standard OO programming.

Think about what where that total_cost method is defined. It's defined as an instance method, which means that you would need to call it for an individual @@phonecall@, just like the other instance methods like @@phonecall.cost@. So then take a look at the sort of object that @@phonecalls@ is, do this in your controller or view: @logger.debug "------> #{@phonecalls.class}"@ and then check your logfile for the output. Can you see why the @total_cost@ method is not being found for the @@phonecalls@ object?

The way that what you want to do is more commonly done is to define a class method in @Phonecall@ that does something like this:

def self.total_cost
  sum(:cost)
end

For more information on the "sum method see here":http://api.rubyonrails.org/classes/ActiveRecord/Calculations/ClassMethods.html#M002134

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

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

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

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 Richard,

You might try:

  @phonecalls.sum(&:cost)

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.

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])

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
12 Posts
Login to add your message