Neil Neil - 1 month ago 11
Ruby Question

Rails: Iterate over collection within a Model's Class Method

I have a collection of

Foo
objects. I want to create a class method that iterates over that collection and returns
true
if ALL objects in that collection have a certain value for an attribute. Otherwise the class method returns
false
.

I attempted the following but it does not work:

class Foo < ActiveRecord::Base

def self.all_have_number_value_999?
each do |foo_object|
return false if foo_object.number_val != 999
end
return true #all objects have #number_val == 999
end

end


Now assume I got a collection of
Foo
objects somehow. I will make up how I got the collection of Foo objects, but the importance is that
number_value_999?
can iterate over that collection. In other words: some unknown scope generated the collection of
Foo
objects before hand. I just want to iterate over that collection within a
Foo
class method.

Foo.where(id: [123, 456]).all_have_number_value_999?


It is complaining in the class method because it doesn't know how to iterate over the collection. So that is my question: How do I iterate over a collection within a model's class method?

Answer

Reverse your logic and let the database do the work. If all of them have a number_val of 999 then none of them have a number_val that is not 999 and this is easily expressed:

def self.all_have_number_value_999
  !where.not(:number_val => 999).exists?
end

That will be a single fairly efficient database query.

Since this is a class method and class methods are mixed into relations (scopes are pretty much specific types of class methods after all), you can call this method on a relation and things like:

Foo.where(id: [123, 456]).all_have_number_value_999?

should work as would Foo.all_have_number_value_999?.


If you want to perform some arbitrary logic while iterating over the collection outside the database, then you could throw an all call to resolve the query before iterating:

def self.all_have_number_value_999?
  all.each do |foo_object|
    ...
  end
end