如何组合ActiveRecord关系?合并不起作用



最初,当我试图根据项目的确切内容(:name_id)和:name_id的频率,为在给定日期集之间具有Order开始的所有Items构建直方图时,我使用了以下代码:

dates = ["May 27, 2016", "May 30, 2016"]
items = Item.joins(:order).where("orders.start >= ?", dates.first).where("orders.start <= ?", dates.last)
histogram = {}
items.pluck(:name_id).uniq.each do |name_id|
histogram[name_id] = items.where(name_id:name_id).count
end

这个代码运行良好。

然而,现在,我正试图构建一个更具扩展性的直方图。我仍然想捕捉一段时间内:name_id的频率,但现在我想通过Order的开始和结束来绑定该时间。但是,我在组合查询后面的ActiveRecord关系时遇到了问题。具体来说,如果我的查询如下:

items_a = Item.joins(:order).where("orders.start >= ?", dates.first).where("orders.start <= ?", dates.last)
items_b = Item.joins(:order).where("orders.end >= ?", dates.first).where("orders.end <= ?", dates.last)      

如何连接这两个查询,以便下面作用于查询对象的代码仍然有效

items.pluck(:name_id).each do |name_id|
histogram[name_id] = items.where(name_id:name_id).count
end

我尝试过的:

+,但这当然不起作用,因为它将结果变成了一个数组,而像pluck这样的方法不起作用:

(items_a + items_b).pluck(:name_id)
=> error

merge,这似乎是所有SO的答案所说的。。。但它对我不起作用,因为正如文档所说,merge计算出了交集,所以我的结果是这样的:

items_a.count
=> 100
items_b.count
=> 30
items_a.merge(items_b)
=> 15

仅供参考,目前,我已经用下面的猴子修补了这个,但它不是很理想。谢谢你的帮助!

name_ids = (items_a.pluck(:name_id) + items_b.pluck(:name_id)).uniq
name_ids.each do |name_id|
# from each query object, return the ids of the item objects that meet the name_id criterion
item_object_ids = items_a.where(name_id:name_id).pluck(:id) + items_b.where(name_id:name_id).pluck(:id) + items_c.where(name_id:name_id).pluck(:id)
# then check the item objects for duplicates and then count up. btw I realize that with the uniq here I'm SOMEWHAT doing an intersection of the objects, but it's nowhere near as severe... the above example where merge yielded a count of 15 is not that off from the truth, when the count should be maybe -5 from the addition of the 2 queries
histogram[name_id] = item_object_ids.uniq.count
end

您可以将两个查询合并为一个:

items = Item.joins(:order).where(
"(orders.start >= ? AND orders.start <= ?) OR (orders.end >= ? AND orders.end <= ?)", 
dates.first, dates.last, dates.first, dates.last

)

这可能更可读:

items = Item.joins(:order).where(
"(orders.start >= :first AND orders.start <= :last) OR (orders.end >= :first AND orders.end <= :last)", 
{ first: dates.first, last: dates.last }
)

Rails5将支持or方法,这可能会让它变得更好:

items_a = Item.joins(:order).where(
"orders.start >= :first AND orders.start <= :last", 
{ first: dates.first, last: dates.last }
).or(
"orders.end >= :first AND orders.end <= :last", 
{ first: dates.first, last: dates.last }
)

或者在这种情况下可能不会更好

也许这会更干净一点:

date_range = "May 27, 2016".to_date.."May 30, 2016".to_date
items = Item.joins(:order).where('orders.start' => date_range).or('orders.end' => date_range)

最新更新