首页 > 解决方案 > 如何从 ActiveRelation 更新选定 ActiveRecords 上的 `attr_accessor`

问题描述

介绍

假设我有一个attr_accessor名为Book 的模型声明:purchased

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 的整个关系

问题

如何以最少的代码量并尽可能高效地完成此任务?

假设我们有 200 000 本书,而用户只购买了 50 本书,我们暂时避免使用分页解决方案。我知道在显示它们时我将调用:purchased200 000 次,但我只想更新unarchived_books(具体为 50 条记录)的一个子集,而不是遍历所有书籍并检查它们是否在purchased_ids.

我想到的最好的解决方案是

  1. 获取所有未归档的图书
  2. 获取一个子集并使用循环一个一个地更新它们(因为整体上.each没有什么比:update_attr_accessorActiveRecord::Relation
  3. 将这些关系合并在一起,用更新的关系覆盖第一个关系。

但是我的解决方案的第 3 个问题存在一个问题 - 该:merge方法实际上忽略了attr_accessors,所以我不知道从那里去哪里。

您对如何改进我的解决方案有任何提示吗?

标签: ruby-on-railsrails-activerecordattr-accessor

解决方案


保证一个一个地加载和更新所有记录会用大量的数据耗尽内存。最快的方法是使用update_allwhich 更新单个查询中的所有记录而不实例化它们:

def mark_purchased(purchased_ids)
  purchased_books = Book.unarchived.where(id: purchased_ids)
  purchased_books.update_all(purchased: true) 
  purchased_books
end

如果您真的必须一一更新它们,例如触发回调,请确保您使用批处理

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 查询都已发送到数据库,但这会慢一个数量级。


推荐阅读