Rails 4:如何在调用Model.all之前阻止加载所有数据库记录

时间:2018-01-03 22:46:51

标签: ruby-on-rails performance

我遇到了一个性能问题,在过滤器之前调用了Processing.all,而过滤器又在Processing模型上运行了第二个查询。 Processing有大量的记录,并将它们全部加载到内存中,只是对它们运行第二个查询导致RAM的峰值,我想修复。

控制器中的行是:

@filter = ProcessingFilter.new(base_collection: Processing.all, options: params[:filter])

如您所见,Processing.all作为base_collection参数传递到此处。

ProcessingFilter然后运行:

class ProcessingFilter
  def initialize(base_collection:, options: {})
    @base_collection = base_collection
    @options = options
  end

  def collection
    @collection ||= begin
      scope = @base_collection
      if condition1
        scope = scope.joins(:another_entry).where(another_entry: {customer_id: customer_id})
      end
      if condition2
        scope = scope.where('created_at >= ?', created_at_start)
      end
      if condition3
        scope = scope.where('created_at <= ?', created_at_end)
      end
      if condition4
        scope = scope.where(number: processing_number)
      end
      scope
    end
  end
end

此过滤器将创建单个ActiveRecord查询的各种if条件链接在一起,这很好。

问题是我无法取消这个过滤器,因为它设置了一些在别处使用的实例变量。我试图想出一种聪明的方法,让第一次没有Processing.all查询运行,而是让它将其他选项链接在一起,尽管它是在一个单独的类中。这可能吗?

提前致谢!

3 个答案:

答案 0 :(得分:2)

免责声明:这本身并不是答案,但因为它太长而需要输入代码:

Processing.all尚未将记录加载到内存中,因为记录“延迟加载”,因为它只返回ActiveRecord_Relation个对象。只有在Arrayeachfirstlastmap上使用[]方法后,它才会启动实际上将数据库中的记录提取到内存中。

演示:

processings = Processing.all

puts processings.class
# => Processing::ActiveRecord_Relation

puts processings.first.class
#   Processing Load (2.9ms)  SELECT  "processings".* FROM "processings"  ORDER BY "processings"."id" ASC LIMIT 1
# => Processing

既然我们知道.all并不急于将记录立即加载到内存中,那么我们需要找出当您调用@filter = ProcessingFilter.new(base_collection: Processing.all, options: params[:filter])时代码仍然会立即将记录加载到内存中的原因

仅限于您​​显示的代码,我在ProcessingFilter类中看不到会触发将记录加载到内存中的任何内容(没有调用Array方法将它们加载到内存中);他们都只是ActiveRecord_Relation个对象。因此,我目前的猜测是在两个过滤器之间的某个位置,您正在调用Array方法:

@filter = ProcessingFilter.new(base_collection: Processing.all, options: params[:filter])

# An Array method is called:
@filter.collection.first

如果您在rails console中执行此操作,则需要附加; nil以防止“处理”每行调用的值,因为它会立即加载记录:

@filter = ProcessingFilter.new(base_collection: Processing.all, options: params[:filter]); nil

@filter.collection.first; nil

答案 1 :(得分:1)

如果您不使用过滤 default_scopes (或者想要忽略默认过滤),Processing.unscoped就可以了。

顺便提一下,您目前使用的是哪个版本的导轨?

有关已弃用的.scoped方法的其他链接: With Rails 4, Model.scoped is deprecated but Model.all can't replace it

答案 2 :(得分:0)

我会考虑将每个条件提取到自己的范围方法中,然后弃用这个ProcessingFilter类。它看起来像装饰设计不好。

您可以使用base_collection值来确定被调用的模型,然后将对ProcessingFilter的调用代理到初始化中的相应范围。应该省去一些麻烦,只需调用base_collection范围。我怀疑你因为这种用法而得到重复的查询调用。