如何从ActiveRelation

时间:2018-08-14 13:50:14

标签: ruby-on-rails rails-activerecord attr-accessor

简介

假设我有一个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中。

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

  1. 获取所有未存档的书
  2. 获取一个子集并使用.each循环一次更新它们(因为在整个:update_attr_accessor上没有像ActiveRecord::Relation的东西
  3. 将这些关系合并在一起,用更新的关系覆盖第一个关系。

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

您对改善我的解决方案有什么建议吗?

2 个答案:

答案 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")