路人皆知 Rails 可用includes
用来避免N+1
问题,但我最近就因 includes 献了一回丑。
class TransactionHistory < ActiveRecord::Base
has_many :transaction_items
scope :with_item_named_after, ->(item_name) { joins(:transaction_items).merge TransactionItem.named_after(item_name) }
end
class TransactionItem < ActiveRecord::Base
belongs_to :transaction_history
scope :named_after, ->(name) { where(name: name) }
end
现假设我们需要根据 item_name 来过滤 transaction_hitory,只要 transaction_hitory 中有 transaction_item 满足条件就行,但是为了信息完整也要显示匹配 transaction_hitory 中那些不匹配的 transaction_item。
第一次不假思索地写出了下面的代码:
# Controller
@transaction_histories = TransactionHistory.with_item_named_after(params[:query][:item_name]).includes(:transaction_items)
# View
<%- @transaction_histories.each do |transaction_history| %>
<!-- ... -->
<%= transaction_history.transaction_number %>
<!-- ... -->
<%- transaction_history.transaction_items.each do |transaction_item| %>
<%= transaction_item.name %>
<%- end %>
<%- end %>
不幸的是,这并不好使。用 item_name 去过滤,transaction_history 列表倒是对了,但每条 transaction_history 只显示了那些匹配的 transaction_item。这非我所愿。
transaction_history = create :transaction_history
item_1 = transaction_history.transaction_items.create name: 'item_1'
item_2 = transaction_history.transaction_items.create name: 'item_2'
# 下面这行代码返回的transaction_history里面就只包含了item_1,而没包含item_2
TransactionHistory.with_item_named_after('item_1').includes(:transaction_items)
# 去掉解决 N+1问题的 includes(:items) 就对了,但我们的确需要解决 N+1。
TransactionHistory.with_item_named_after('item_1')
TransactionHistory.with_item_named_after('item_1').includes(:transaction_items)
生成的 SQL 类似这样:
而TransactionHistory.with_item_named_after('item_1')
生成的 SQL 却类似这样:
现在能想到的办法如下,不够优雅,且分成了两次查询,Rails 如此优雅的东西,肯定有更优雅的解决方案。若有人有漂亮的解决方案,请一定告诉我,不甚感激。
class TransactionHistory < ActiveRecord::Base
has_many :transaction_items
scope :with_item_named_after, ->(item_name) do
matched_transaction_item_ids = TransactionItem.named_after(item_name).map(&:id).uniq
where(id: matched_transaction_item_ids)
end
end
听我啰嗦了这么久,最后推荐一个N+1
嗅探 gem 包表示感谢: bullet
。
另外,可能你也没从这学到啥东西,推荐几篇文章,表示我的歉意: