I do (in my view):
# myUser is a User in ActiveRecord with :has_many :posts
myUser.posts.each do |post|
end
If the user had ten posts, would this be doing a database call ten times? Should these loops be like (less pretty)?:
myPosts = myUser.posts
myPosts.each do |post|
end
Here is a paste bin of a ruby file that I did to test. EDIT Modified the paste bin.
This reminds me of code in Java
for (int i = 0; i < someExpensiveFunction(); i++)
that should be (unless the array is modified)
for (int i = 0, len=someExpensiveFunction(); i < len; i++)
Am I missing something? I see a bunch of rails examples where people loop through some has_many
field of an object. This is important to me as I'm trying to optimize my application's performance.
If the user had 10 posts, this would technically be doing a database call 10 times?
Nope.
myUser = User.find 123
myUser.posts.each do |post|
puts post.title
end
This code here will run 2 queries. The first will find a user by its id, returning a single row. The second will run a query asking for all posts which have a user id that matches myUser
. Then the each
will use the result of that to iterate through.
If you watch the log in development mode it tells you the queries it's running, and you should see a single query returning all those posts.
Rails uses an association proxy object to hold your query, execute the query when you need records, and then caches the result. It's a very helpful bit of code and, for the most part, it handles things like this for you.
This is a feature of Rails, not ruby.
At the ruby level, each
simply acts on collections.
def get_letters
puts 'executing "query"'
sleep(3)
["a","b","c"]
end
get_letters.each do |item|
puts item
end
This should print out.
executing "query"
a
b
c
The get_letters
method executes, it returns an array, an that array already full of data is what we call the each
method on, so we can process each item. Fetching the collection and iterating through it are 2 completely separate steps.
From your pastebin:
# will this run forever?
myArr = ["a","b","c"]
myArr.each do |item|
myArr.push("x")
end
Yeah it will, But not because the array is being "fetched" over an over.
This equivalent to javascript like this, which spells it out better.
myArr = ["a","b","c"];
for (var i = 0; i < myArr.length; i++) {
myArr.push('x');
}
The original array, on each iteration of the loop checks to see if it's done yet. But because the array gets longer by one every single time we progress by one item, i < myArr.length
will always be true
because they both increase by one on each iteration. The same thing is going on in ruby with your each
loop, for the same reason.
It runs forever, not because you keep running some code to regenerate the array. It runs forever because you are artificially mutating the resulting array.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With