鉴于此模型:
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
答案 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>
前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。