I'm trying to understand the Active Record find_each method to retrieve records in batches.
If a Post has_many comments (thousands of comments) and I want to render each comment, I can do:
@post.comments.each { |comment| ... }
But is it faster if I do :
@post.comments.find_each { |comment| ... } ?
Is the second query valid?
The way I'm seeing this is that by doing @post.comments before the find_each is already getting all the comments for each post so ending the query with find_each is probably redundant ?!
This is the full block of code in a rails app views folder:
json.comments @post.comments.find_each do |comment|
json.rating comment.rating
json.content comment.content
end
Thank you
CodePudding user response:
@post.comments.find_each
is correct. When you call @post.comments
the query that it creates does not execute right away. That's why you can chain other query methods like where and order etc. to this association. (sql log truncated for readability)
>> comments = Post.first.comments; nil;
Post Load (0.8ms)
=> nil
# NOTE: ^ no comments are loaded yet, until we need to output the result
>> comments
Comment Load (2.0ms)
=> [#<Comment:0x00007f2ef1733328 id: 1, post_id: 1>, ...] # <= array of 2000 comments
When each
method is chained, all of the comments are loaded into memory which is an issue when you have thousands of records.
>> Post.first.comments.each{|c| print "." }
Post Load (0.6ms)
# NOTE: executes one query for comments
Comment Load (1.4ms)
.................................................. # <= 2000 dots
=> [#<Comment:0x00007f2ef1560be0 id: 1, post_id: 1>, ...]
When find_each
method is chained, comments are retrieved in batches of 1000 by default:
>> Post.first.comments.find_each{|c| print "." }
Post Load (0.6ms)
# NOTE: executes query to get 1000 comments at a time
Comment Load (0.8ms)
.................................................. # <= 1000 dots
Comment Load (0.8ms)
.................................................. # <= 1000 dots
Comment Load (0.4ms)
# no more comments
=> nil
You can also specify the :batch_size
:
>> Post.first.comments.find_each(batch_size: 100){|c| print "." }
Post Load (0.7ms)
# NOTE: get comments in batches of 100
Comment Load (0.6ms)
.................................................. # <= 100 dots
Comment Load (0.5ms)
.................................................. # <= 100 dots
# 21 queries in total
=> nil
https://api.rubyonrails.org/classes/ActiveRecord/Batches.html#method-i-find_each