假设我有一个attr_accessor
名为:purchased
的Book的模型声明。
class Book < ActiveRecord::Base
# contains fields like :id, :title, :description, :archived_at
...
attr_accessor :purchased
def purchased
@purchased || false
end
scope :unarchived, -> { where(archived_at: nil) }
...
end
我有一个任意方法
def mark_purchased(purchased_ids)
# 1. Returns ActiveRecord::Relation
unarchived_books = Book.unarchived
# 2. Mark only purchased books
purchased_books = unarchived_books.where(id: purchased_ids)
purchased_books.update_attr_accessor(purchased: true)
# 3. Return unarchived books with update :purchased attr_accessor
unarchived_books
end
此方法采用ActiveRecord::Relation
并在此关系的子集上更新attr_accessor
,然后应使用更新的attr_accessor返回整个关系
如何用最少的代码并尽可能高效地完成此任务?
让我们假设我们有20万本书,而用户仅购买了50本书,现在就让我们避免分页解决方案。我知道我在显示:purchased
时会打电话200,000次,但我只想更新unarchived_books
的一部分(具体是50条记录),而不是循环浏览所有书籍并检查如果它们在purchased_ids
中。
我想到的最好的解决方案是
.each
循环一次更新它们(因为在整个:update_attr_accessor
上没有像ActiveRecord::Relation
的东西但是我的解决方案的第3个问题是-:merge
方法实际上忽略了attr_accessors
,所以我不知道从那里去哪里。
您对改善我的解决方案有什么建议吗?
答案 0 :(得分:1)
保证一个接一个地加载和更新所有记录会耗尽内存,并带来不小的数据量。最快的方法是使用update_all
,它可以在单个查询中更新所有记录而无需实例化它们:
def mark_purchased(purchased_ids)
purchased_books = Book.unarchived.where(id: purchased_ids)
purchased_books.update_all(purchased: true)
purchased_books
end
例如,如果您确实需要逐个更新它们以触发回调,请确保使用batches:
def mark_purchased(purchased_ids)
purchased_books = Book.unarchived.where(id: purchased_ids)
purchased_books.find_each do |book|
book.update(purchased: true)
end
purchased_books
end
尽管每个UPDATE查询都已发送到数据库,但这会慢一个数量级。
答案 1 :(得分:0)
我可以知道为什么您需要attr_accessor以及为什么要一次更新值吗?
从数据库中获取未归档的数据时,您可以将值设置为true,作为使用原始sql查询购买的自定义列。
Book.unarchived.where(id: purchased_ids).select("books.*, true as purchased")