如果已加载关联,则避免范围命中数据库

时间:2012-12-21 14:33:30

标签: ruby-on-rails performance activerecord code-reuse named-scope

我有2个这样的模型:

class Country < ActiveRecord::Base
   has_many :cities
end

class City < ActiveRecord::Base
   belongs_to :country
   scope :big, where("population > 1000000")
end

然后,在代码中我加载一个带有城市的国家,如下:

country = Country.include(:cities).find(id)

但是当我执行时:

country.cities.big

使用此查询命中数据库:

SELECT * FROM cities where country_id = 1 AND population > 1000000

哪种方法很好,但是由于所有已经加载的城市都包含在内,因此没有必要。 如果关联已经加载,有没有办法告诉范围没有命中db?

我可以使用关联扩展来实现,但不适用于常规范围。在扩展上我做了类似的事情:

has_many :cities do
   def big
      if loaded?
        detect {|city| city.population > 1000000}
      else
        where("population > 1000000")
      end
   end
end

但这会在2个地方重复范围,我想在城市模型上重复使用范围。

1 个答案:

答案 0 :(得分:1)

范围逻辑使用了与Arel一起使用的方法,并且ruby Enumerables不知道如何使用它们。您可以将逻辑重构为可以转换为使用Arel或Enumerable方法的抽象,但这并不总是可行的:

def self.build_scope(abstracted)
  where(abstracted.map(&:to_s).join(' '))
end

def self.build_enum(abstracted)
  select{|city| city.send(abstracted[0]).send(*abstracted[1..2]) }
end

def self.abstract_big
  [:population, ">", 10000]
end

scope :big_scope, build_scope(abstract_big)

def self.big_enum
  build_enum abstract_big      
end

然后你可以这样做:

country.cities.big_enum

更好的想法是只根据你想要的范围急切加载(如果你事先知道的话):

country = Country.include(:cities).merge(City.big).find(id)