find_each 相当于 foo_ids?
Equivalent of find_each for foo_ids?
鉴于此模型:
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
你可以试试下面的方法,每个切片一次取 4 个元素,你可以循环 4 个元素
@user.thing_ids.each_slice(4) do |batch|
batch.each do |id|
puts id
end
end
更新最终编辑:
在查看了您更新的问题后,我更新了我的答案(不确定为什么在我用源代码备份我的答案以证明这一点后您会投反对票...但我不怀恨在心:)
这是我的解决方案,经过测试和工作,如果您满意,可以接受它作为答案。
下面,我扩展了 ActiveRecord::Relation,覆盖了 find_in_batches 方法以接受一个额外的选项,:relation。当设置为 true 时,它将 return 与您的块的 activerecord 关系,因此您可以使用所需的方法 'pluck' 仅获取目标查询的 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 对象数组,并 returned 给您。现在,我可以选择允许您在转换为数组之前 return 查询。这是一个如何使用它的例子:
@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 post 上给出的任何答案,您可以推出自己的解决方案。只有当答案偏离主题或没有任何帮助时,才考虑投反对票。我们都只是想提供帮助。否决具有源代码的答案只会阻止其他人尝试帮助您。
上次编辑
回应您的评论(因为我的评论不合适):
- 打电话
thing_ids
内部使用
pluck
- pluck 内部使用
select_all
- ...实例化一个 activerecord Result
之前的第二次编辑:
这行代码在 pluck returns 一个 activerecord Result:
....
result = klass.connection.select_all(relation.arel, nil, bound_attributes)
...
我刚刚为您逐步浏览了源代码。使用 select_all 会节省一些内存,但最终,即使您使用 pluck 方法,仍会创建并映射一个 activerecord Result。
我会用这样的东西:
User.things.find_each(batch_size: 1000).map(&:id)
这将为您提供一组 ID。
不幸的是,它不是一个单一的班轮或帮手,可以让你做到这一点,所以改为:
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
Rails 5 引入了关系的in_batches
method, which yields a relation and uses pluck(primary_key)
internally. And we can make use of the where_values_hash
方法以检索已经采摘的ids:
@user.things.in_batches { |batch_rel| p batch_rel.where_values_hash['id'] }
请注意,in_batches
具有类似于 find_each
的 order
和 limit
限制。
这种方法有点老套,因为它依赖于 in_batches
的内部实现,如果 in_batches
将来停止提取 ID,将会失败。一个非 hacky 方法是 batch_rel.pluck(:id)
,但这会运行相同的 pluck 查询两次。
鉴于此模型:
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
你可以试试下面的方法,每个切片一次取 4 个元素,你可以循环 4 个元素
@user.thing_ids.each_slice(4) do |batch|
batch.each do |id|
puts id
end
end
更新最终编辑:
在查看了您更新的问题后,我更新了我的答案(不确定为什么在我用源代码备份我的答案以证明这一点后您会投反对票...但我不怀恨在心:)
这是我的解决方案,经过测试和工作,如果您满意,可以接受它作为答案。
下面,我扩展了 ActiveRecord::Relation,覆盖了 find_in_batches 方法以接受一个额外的选项,:relation。当设置为 true 时,它将 return 与您的块的 activerecord 关系,因此您可以使用所需的方法 'pluck' 仅获取目标查询的 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 对象数组,并 returned 给您。现在,我可以选择允许您在转换为数组之前 return 查询。这是一个如何使用它的例子:
@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 post 上给出的任何答案,您可以推出自己的解决方案。只有当答案偏离主题或没有任何帮助时,才考虑投反对票。我们都只是想提供帮助。否决具有源代码的答案只会阻止其他人尝试帮助您。
上次编辑
回应您的评论(因为我的评论不合适):
- 打电话 thing_ids 内部使用 pluck
- pluck 内部使用 select_all
- ...实例化一个 activerecord Result
之前的第二次编辑:
这行代码在 pluck returns 一个 activerecord Result:
....
result = klass.connection.select_all(relation.arel, nil, bound_attributes)
...
我刚刚为您逐步浏览了源代码。使用 select_all 会节省一些内存,但最终,即使您使用 pluck 方法,仍会创建并映射一个 activerecord Result。
我会用这样的东西:
User.things.find_each(batch_size: 1000).map(&:id)
这将为您提供一组 ID。
不幸的是,它不是一个单一的班轮或帮手,可以让你做到这一点,所以改为:
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
Rails 5 引入了关系的in_batches
method, which yields a relation and uses pluck(primary_key)
internally. And we can make use of the where_values_hash
方法以检索已经采摘的ids:
@user.things.in_batches { |batch_rel| p batch_rel.where_values_hash['id'] }
请注意,in_batches
具有类似于 find_each
的 order
和 limit
限制。
这种方法有点老套,因为它依赖于 in_batches
的内部实现,如果 in_batches
将来停止提取 ID,将会失败。一个非 hacky 方法是 batch_rel.pluck(:id)
,但这会运行相同的 pluck 查询两次。