Rails ActiveRecord :: Relation保持命中数据库

时间:2018-03-19 20:28:14

标签: ruby-on-rails ruby

我有一个简单的控制器方法:

products = Product.where(name: name, color: color, size: size, available: true)

返回Product :: ActiveRecord_Relation对象。我想拿第一个物体然后拉出一个例如products.first.product_code

但我和其他一些尝试重新查询数据库的方法。我试过了:

products[0].product_code
products.take.product_code

所有这些都重新查询数据库,两次击中数据库。一次为了哪里和一个采取领域。有没有一个简单的解决方案不会打击数据库?

将ActiveRecord转换为数组(products.to_a.[0].product_code)的工作原理是什么,但这似乎效率低下。

以下是显示两个单独匹配的服务器日志:

enter image description here

这是我的控制器方法供参考:

  def update_selection
    size = params[:size]
    color = params[:color]
    name = params[:product_name]
    products = Product.where(name: name, color: color, size: size, available: true)
    product_code = products.empty? ? 'sold out' : products.first.product_code
    respond_to do |format|
      format.json { render json: { count: products.length, code: product_code}}
    end
  end

2 个答案:

答案 0 :(得分:4)

我最好的猜测是,在分配给products时,您实际上并没有访问数据库。 ActiveRecord关系是惰性的,这意味着在需要该查询的结果之前,它们不会查询数据库。

根据您的问题加以说明 - 这一行:

products = Product.where(name: name, color: color, size: size, available: true)

不需要实际从数据库中提取任何数据,因此不执行查询。

另一方面,

products.first.product_code将导致查询触发。

编辑:

我认为问题来自于使用.exists?(这是总是触发查询的ActiveRecord方法)。如果您要检查查询是否返回了任何结果,请尝试使用.present?.any?代替.exists?

编辑2:

好的,谢谢您发布代码。你在这里有几个选择。

  1. 使用.to_a
  2. products = Product.where(name: name, color: color, size: size, available: true).to_a

    这会将所有products加载到内存中,如果您预计会有少量产品,这是有意义的。这只会触发一个查询。

    1. 您可以将此行更改为仅触发一个查询:
    2. product_code = products.first.try(:product_code) || 'sold out'

      这样可以提高内存效率(因为您最多只能将product加载到内存中),但使用了两个查询(products.count is the other one)。

答案 1 :(得分:1)

您可能会发现这是有效的,因为它获得了基于SQL的计数,然后在需要获得单个产品时重新运行产品查询。

你真的想要ID最低的那个“第一”吗?如果是,请使用first,否则请使用take

def update_selection
  size          = params[:size]
  color         = params[:color]
  name          = params[:product_name]
  products      = Product.where(name: name, color: color, size: size, available: true)
  product_count = products.count
  product_code  = product_count.zero? ? 'sold out' : products.take.product_code
  respond_to do |format|
    format.json { render json: { count: product_count, code: product_code}}
  end
end