invoke a class method on an array of objects?

Hey all,

The Subject line may be a bad question, whether it's possible to invoke a class method on an array of objects. But look at the code below, and unless I am misunderstanding, that's what appears to be going on, and yes the code does work:

class Dog   def total_caught     Cat.counters(:dog => self)[:caught]   end

class Cat def self.counters(constraints = {})   source = constraints[:dog] ? constraints[:dog].cats : self   CatState.keys.map.each_with_object({}) { |k, h| h[k] = source.count_of(k) end

  def self.count_of(state, options = {})     since = options.delete(:since)     options[:conditions] = ["#{state}_on > ?", since] if since

    Cat.count("#{state}_on", options)   end

Basically, what's going on here is when the total_caught method is invoked, it calls the Cat class method of Cat, passing into the argument list a hash key/value pair. We assign that hash to local variable constraints, and then check if the key dog exists, if so we get all associated cats to the dog. Hence, it is possible for the source local variable to hold an array of cat objects, which is the purpose of has_many and belongs_to. But then notice we call count_of which is a class method, so if source holds an array of objects, how is it possible to call the count_of class method of class Cat on it?

thanks for response

Hey all,

The Subject line may be a bad question, whether it's possible to invoke a class method on an array of objects. But look at the code below, and unless I am misunderstanding, that's what appears to be going on, and yes the code does work:

class Dog def total_caught    Cat.counters(:dog => self)[:caught] end

class Cat def self.counters(constraints = {}) source = constraints[:dog] ? constraints[:dog].cats : self CatState.keys.map.each_with_object({}) { |k, h| h[k] = source.count_of(k) end

def self.count_of(state, options = {})    since = options.delete(:since)    options[:conditions] = ["#{state}_on > ?", since] if since

   Cat.count("#{state}_on", options) end

Basically, what's going on here is when the total_caught method is invoked, it calls the Cat class method of Cat, passing into the argument list a hash key/value pair. We assign that hash to local variable constraints, and then check if the key dog exists, if so we get all associated cats to the dog. Hence, it is possible for the source local variable to hold an array of cat objects, which is the purpose of has_many and belongs_to. But then notice we call count_of which is a class method, so if source holds an array of objects, how is it possible to call the count_of class method of class Cat on it?

thanks for response

If you have an array of objects, you can use map to pass a block to each member of that array, and the result will be an array holding the output of that block.

['foo','bar','baz'].map{|word| word.upcase } #-> ['FOO','BAR','BAZ']

So anything you want to put inside that block will work. If you need multiple lines, just use do and end instead of the curly braces. If you want to modify your objects in place, then I think you can use map! instead, and that will return the original array with each of its elements modified by the block.

Walter

Hey all,

The Subject line may be a bad question, whether it's possible to invoke a class method on an array of objects. But look at the code below, and unless I am misunderstanding, that's what appears to be going on, and yes the code does work:

class Dog def total_caught Cat.counters(:dog => self)[:caught] end

class Cat def self.counters(constraints = {}) source = constraints[:dog] ? constraints[:dog].cats : self CatState.keys.map.each_with_object({}) { |k, h| h[k] = source.count_of(k) end

def self.count_of(state, options = {}) since = options.delete(:since) options[:conditions] = ["#{state}_on > ?", since] if since

Cat\.count\("\#\{state\}\_on", options\)

end

Basically, what's going on here is when the total_caught method is invoked, it calls the Cat class method of Cat, passing into the argument list a hash key/value pair. We assign that hash to local variable constraints, and then check if the key dog exists, if so we get all associated cats to the dog. Hence, it is possible for the source local variable to hold an array of cat objects, which is the purpose of has_many and belongs_to. But then notice we call count_of which is a class method, so if source holds an array of objects, how is it possible to call the count_of class method of class Cat on it?

Assuming these are active record objects and that dog has_many cats, then dog.cats isn't actually an array, it's an association proxy which will forwards attempts to call class methods of Cat onto Cat, setting the scope so that only the relevant cats will be found. If it was an actual array you wouldn't be able to do this.

Fred

Hi.

Try to do something like this:

CatState.keys.map.each_with_object({}) { |k, h| h[k] = source.to_a.inject(0){|sum, a| sum += count_of(a)}

[1,2,3].inject(0) {|sum, a| sum += a }

as result => 6

Random note, you can use & and a symbol to send a message to each element in an array. So for the above example, it would be more concise to write

['foo','bar','baz'].map(&:upcase) #-> ['FOO','BAR','BAZ']

There are obviously still many times to use the block form, but I find a lot can be shortened to this and it makes the code more readable.

\Peter

If you have an array of objects, you can use map to pass a block to each member of that array, and the result will be an array holding the output of that block.

['foo','bar','baz'].map{|word| word.upcase } #-> ['FOO','BAR','BAZ']

Random note, you can use & and a symbol to send a message to each element in an array. So for the above example, it would be more concise to write

['foo','bar','baz'].map(&:upcase) #-> ['FOO','BAR','BAZ']

There are obviously still many times to use the block form, but I find a lot can be shortened to this and it makes the code more readable.

I agree. I was showing the block case in order to allow for more elaborate multi-step conversion.

Walter