Ruby .each 效率

5

我认为(依照我的观点):

# myUser is a User in ActiveRecord with :has_many :posts
myUser.posts.each do |post|
end

如果用户有十篇文章,这样做会进行十次数据库调用吗?这些循环应该像这样(不够好看):
myPosts = myUser.posts
myPosts.each do |post|
end

这里 是一个Ruby文件的粘贴版,我用它进行了测试。编辑 修改了粘贴版。

这让我想起Java中的代码。

for (int i = 0; i < someExpensiveFunction(); i++)

这应该是正确的(除非修改了数组)

for (int i = 0, len=someExpensiveFunction(); i < len; i++)

我有点不理解,我看到很多Rails的例子中人们会遍历某个对象的has_many字段。这对我来说很重要,因为我正在尝试优化我的应用程序性能。

1个回答

10

如果用户有10篇文章,那么这技术上会进行10次数据库调用吗?

不会。

myUser = User.find 123
myUser.posts.each do |post|
  puts post.title
end

这段代码将运行2个查询。第一个查询将通过ID查找用户,并返回单行结果。第二个查询将查询所有帖子,其中用户ID匹配myUser。然后,each将使用该结果进行迭代。


如果您在开发模式下观察日志,它会告诉您正在运行的查询,并且您应该看到一个返回所有这些帖子的单个查询。

Rails使用关联代理对象来保存您的查询,在需要记录时执行查询,然后缓存结果。这是一个非常有用的代码片段,并且在大多数情况下,它为您处理此类事情。

这是Rails的一个功能,而不是Ruby。


在Ruby层面上,each只是对集合进行操作。

def get_letters
  puts 'executing "query"'
  sleep(3)
  ["a","b","c"]
end

get_letters.each do |item|
  puts item
end

这应该会打印出来。

executing "query"
a
b
c
get_letters 方法被调用后,它返回一个数组。我们使用 each 方法来处理这个已经包含数据的数组中的每一项,从而完成逐个处理的操作。获取集合和迭代集合是两个完全不同的步骤。
# will this run forever?
myArr = ["a","b","c"]
myArr.each do |item|
  myArr.push("x")
end

是的,会有影响,但不是因为数组一遍又一遍地被“获取”。

这相当于 JavaScript 中的以下代码,更能清楚说明问题。

myArr = ["a","b","c"];
for (var i = 0; i < myArr.length; i++) {
  myArr.push('x');
}
原始数组在每次循环迭代时检查是否已完成。但是,由于数组每次前进一个项目都会变长一倍,所以i < myArr.length将始终为true,因为它们每次迭代都会增加一个。由于同样的原因,使用each循环在ruby中也会发生这种情况。
它永远运行下去,不是因为您在运行某些代码来重新生成数组,而是因为您人为地改变了结果数组。

谢谢,但是我有问题。在我的 Paste Bin 上作为最后一个示例,循环会一直运行... 我不太明白为什么会这样,如果结果被“缓存”,Ruby 是否会修改缓存?如果我在帖子的 .each 循环中添加一个帖子,会发生什么? - K2xL
@K2xL 请看我的编辑,更详细地解释了基本的 Ruby(非 Rails)层面的事情。但是你发布的循环肯定不会“永远运行”。3秒后,它就完成了。 - Alex Wayne
@K2xL 你正在修改你正在迭代的数组。这通常是一个非常糟糕的想法。请看我的最后更新... - Alex Wayne

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接