ActiveRecord最好的部分之一是可链式查询接口:
Post.includes(:comments)
.where(published: true)
.where(author: Current.user)
.order(:name)
为了利用这一优势并使你的代码具有灵活性,在查询数据时总是尝试返回可链式对象。
使用方法
随着你的应用程序的增长,提取复杂的查询是很常见的:
class SpecialOffer
def self.find_eligible_products(store, shopper)
return [] if store.restricted?
store.products
.where('price >= ?', 100)
.select{ |p| shopper.can_order?(p) }
end
end
@products = SpecialOffer.find_eligible_products(store, shopper)
#=> [ #<Product:0x00007fb1719b7ec0>, #<Product:0x00007fb174744de8>, ... ]
虽然这段代码可能有效,但如果你需要以某种方式对@products ,会发生什么?或者添加额外的逻辑?或者懒得加载一些关联?
在这种情况下,我们的SpecialOffer 方法的返回类型是数组。我们将不得不改用Ruby数组方法,如sort 和select ,如果我们需要更多的数据,也许会意外地引入一个N+1的错误。
让我们重构这段代码,使其返回可连锁的对象:
class SpecialOffer
def self.find_eligible_products(store, shopper)
return Product.none if store.restricted?
product_ids = store.products
.where('price >= ?', 100)
.select{ |p| shopper.can_order?(p) }
.map(&:id)
Product.where(id: product_ids)
end
end
@products = SpecialOffer.find_eligible_products(store, shopper)
#=> Product::ActiveRecord_Relation
首先,我们利用none 查询方法:这将返回一个空的(但仍然是可连锁的!)结果。你可以在这个空关系上调用ActiveRecord方法,如order,includes, 或where ,它将简单地返回无结果。
第二,我们不直接返回复杂的产品查询结果,而是收集正确的产品,然后只为这些id,返回 "新鲜 "的结果。虽然这确实会产生额外的数据库查询,但我们也可以根据需要处理结果。
如果我们想对结果进行排序或加载一个关联,我们可以在数据库中进行,而不必担心任何作为计算的一部分而运行的现有条件:
@products = SpecialOffer.find_eligible_products(store, shopper)
.includes(:variants)
.order(:price)
@products = SpecialOffer.find_eligible_products(store, shopper)
.joins(:sales)
.where("sales.count > 15")
.order(:sku)
我发现这种模式对于提取复杂的查询非常有帮助,同时还能保持灵活性,将数据按摩成正确的形状。