mpautzke mpautzke - 6 months ago 55
Ruby Question

Why can't this scope-like query method use reject?

According to the Rails documentation for

, a scope such as:

class Shirt < ActiveRecord::Base
scope :red, -> { where(color: 'red') }
end


is truly:

class Shirt < ActiveRecord::Base
def self.red
where(color: 'red')
end
end


They also say that the relation should act as an
Array
, so doing something like this

Shirt.red.each(&block)


should work...and it does.

Using everything we know above, why does the following not work?

class Shirt < ActiveRecord::Base
def self.short_sleeved
reject{|object| object.short_sleeved == false}
end
end


Shirt.red.short_sleeved
results in
undefined method 'reject' for #<Class:0xba552d4>

Answer

You can't define the short_sleeved method the way you tried to because ActiveRecord classes aren't ActiveRecord relations.

You defined short_sleeved on Shirt. Shirt is an ActiveRecord model class. It is not itself an ActiveRecord relation. It has methods including all and where and many others which return ActiveRecord relations.

Shirt.class
=> Class
Shirt.respond_to? :each
=> false
Shirt.respond_to? :reject
=> false

Like the build-in query methods, scopes return ActiveRecord relations. An ActiveRecord relation has a dynamically generated class. It is not an Enumerable but responds to Enumerable methods:

red_shirts = Shirt.red
red_shirts.class
=> Shirt::ActiveRecord_Relation
red_shirts.respond_to? :each
=> true
red_shirts.respond_to? :reject
=> true

So, you could write your method this way:

def self.short_sleeved
  all.reject { |object| object.short_sleeved == false }
end

However, that loads all the Shirts from the database and then filters them in memory, which probably not as efficient as doing the filtering in the database with where as MZaragosa suggested.

def self.short_sleeved
  where short_sleeved: true
end

or

scope :short_sleeved, -> { where short_sleeved: true }