等价于foo_ids的find_each?

时间:2015-02-05 18:04:00

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

鉴于此模型:

class User < ActiveRecord::Base
  has_many :things
end

然后我们可以这样做::

@user = User.find(123)
@user.things.find_each{ |t| print t.name }
@user.thing_ids.each{ |id| print id }

有大量的@user.things,我想只批量迭代它们的ID,就像使用find_each一样。有没有方便的方法呢?

目标是:

  • 不会立即将整个thing_ids数组加载到内存中
  • 仍然只加载thing_ids的数组,而不为每个ID实例化Thing

5 个答案:

答案 0 :(得分:4)

Rails 5引入了in_batches方法,该方法产生关系并在内部使用pluck(primary_key)。我们可以使用关系的where_values_hash方法来检索已经拔出的ID:

@user.things.in_batches { |batch_rel| p batch_rel.where_values_hash['id'] }

请注意,in_batches的{​​{1}}和order限制类似于limit

这种方法有点hacky,因为它取决于find_each的内部实现,如果in_batches将来停止采集id,将会失败。一个非hacky方法是in_batches,但这会运行相同的pluck查询两次。

答案 1 :(得分:0)

你可以尝试类似下面的内容,每个切片一次需要4个元素,你可以循环4个

@user.thing_ids.each_slice(4) do |batch|
  batch.each do |id|
   puts id
   end
end

答案 2 :(得分:0)

更新最终编辑:

我在审核了您的更新问题之后更新了我的答案(不确定为什么在我使用源代码备份我的答案以证明它之后你会拒绝投票...但我不抱怨...)

这是我的解决方案,经过测试和运作,如果您满意,可以接受这个答案。

下面,我扩展了ActiveRecord :: Relation,覆盖了find_in_batches方法以接受一个附加选项:relation。当设置为true时,它将返回与块的activerecord关系,因此您可以使用所需的方法&#39; pluck&#39;只获取目标查询的ID。

#put this file in your lib directory:
#active_record_extension.rb
module ARAExtension
  extend ActiveSupport::Concern

  def find_in_batches(options = {})
    options.assert_valid_keys(:start, :batch_size, :relation)

    relation = self
    start = options[:start]
    batch_size = options[:batch_size] || 1000

    unless block_given?
      return to_enum(:find_in_batches, options) do
        total = start ? where(table[primary_key].gteq(start)).size : size
        (total - 1).div(batch_size) + 1
      end
    end

    if logger && (arel.orders.present? || arel.taken.present?)
      logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
    end

    relation = relation.reorder(batch_order).limit(batch_size)
    records = start ? relation.where(table[primary_key].gteq(start)) : relation

    records = records.to_a unless options[:relation]

    while records.any?
      records_size = records.size
      primary_key_offset = records.last.id
      raise "Primary key not included in the custom select clause" unless primary_key_offset

      yield records

      break if records_size < batch_size

      records = relation.where(table[primary_key].gt(primary_key_offset))
      records = records.to_a unless options[:relation]
    end
  end

end

ActiveRecord::Relation.send(:include, ARAExtension)

这是初始化程序

#put this file in config/initializers directory:
#extensions.rb
require "active_record_extension"

最初,此方法强制将关系转换为activrecord对象数组并将其返回给您。现在,我可选地允许您在转换到数组之前返回查询。以下是如何使用它的示例:

@user.things.find_in_batches(:batch_size=>10, :relation=>true).each do |batch_query|
  # do any kind of further querying/filtering/mapping that you want

  # show that this is actually an activerecord relation, not an array of AR objects
  puts batch_query.to_sql
  # add more conditions to this query, this is just an example
  batch_query = batch_query.where(:color=>"blue")
  # pluck just the ids
  puts batch_query.pluck(:id)
end

最终,如果您不喜欢SO帖子上给出的任何答案,您可以自行推出解决方案。当答案偏离主题或以任何方式没有帮助时,只考虑低估。我们都在努力提供帮助。低估一个有源代码来证明它的答案只能阻止其他人试图帮助你。

以前的编辑

回应你的评论(因为我的评论不合适):<​​/ p>

  1. 主叫 thing_ids 内部使用 pluck
  2. 采用内部使用 select_all
  3. ...实例化一个activerecord Result
  4. 前2日编辑:

    pluck中的这行代码返回一个activerecord Result

     ....
     result = klass.connection.select_all(relation.arel, nil, bound_attributes)
     ...
    

    我刚刚为您介绍了源代码。使用select_all可以节省一些内存,但最后,即使使用pluck方法,仍会创建并映射一个activerecord Result

答案 3 :(得分:0)

不幸的是,这不是一个允许你这样做的单行或帮助,所以相反:

limit = 1000
offset = 0
loop do
  batch = @user.things.limit(limit).offset(offset).pluck(:id)
  batch.each { |id| puts id }
  break if batch.count < limit
  offset += limit
end

答案 4 :(得分:-1)

我会用这样的东西:

User.things.find_each(batch_size: 1000).map(&:id)

这将为您提供一系列ID。