Ruby on Rails Optimization Part 2
Table of contents
Hello there! Whether you're a newcomer or were captivated by the first part 👀, this blogpost will delve deeper into additional Ruby on Rails (RoR) optimizations. These strategies will not only boost your app's performance but also enhance your code's readability—truly a chef's kiss.
1. Eager Loading
A common pitfall in RoR applications is the N+1 query problem, which arises when your code lazily loads data. This leads to a scenario where, for each record retrieved, a new database query is executed to fetch associated records. RoR provides a solution to this through eager loading.
Eager Loading
Eager loading fetches the main object(s) along with their associated objects through n database query**(n represents the number of associations in the include)**. Implement eager loading with the includes
method.
Example:
@users = User.includes(:posts).where(posts: { published: true })
This code efficiently fetches users and their published posts in just two queries instead of multiple, reducing the overall query count.
For scenarios requiring users to have at least one published post, scopes can be utilized:
class User < ApplicationRecord
scope :with_published_posts, -> {
joins(:posts).merge(Post.published).distinct
}
scope :comedy_writers, -> { where(genre: 'comedy') }
end
class Post < ApplicationRecord
scope :published, -> { where(published: true) }
end
These scopes can be chained to further refine the query:
@users = User.comedy_writers.
with_published_posts.
includes(:posts)
To include posts with comments while eager loading:
@users = User.comedy_writers.
with_published_posts.
includes(posts: :comments)
Furthermore, for including only published posts:
class User < ApplicationRecord
has_many :published_posts, -> { published }, class_name: 'Post'
has_many :posts
end
This association allows for seamless inclusion of second-level associations:
@users = User.comedy_writers.
with_published_posts.
includes(:published_posts => [:comments, :likes])
2. Usingfind_in_batches
Consider the need to reindex all modified objects daily with a third-party search engine (like Algolia or Elastic) to ensure data integrity. Querying millions of records changed in the past 24 hours can be overwhelming.
find_in_batches
offers an optimal solution for memory management in such cases:
Product.changed_last_day.find_in_batches do |batch|
# Call API with your batch
end
The default batch size is 1000, but it can be customized for efficiency:
Product.changed_last_day.find_in_batches(batch_size: 500) do |batch|
# Call API with your batch
end
Wrapping Up
Congratulations on making it through this deep dive into optimizing your Ruby on Rails applications!
We've explored two pivotal strategies that are essential for any RoR developer aiming to boost app performance and maintain clean, efficient code.
Firstly, we tackled the notorious N+1 query problem by implementing eager loading. This technique drastically reduces the number of database queries, thus enhancing your application's speed and efficiency.
Secondly, we delved into the power of find_in_batches
for processing large datasets without overwhelming your server's memory. This method is particularly useful when syncing large volumes of data with third-party services or performing bulk updates.
Happy coding, and let's keep making magic with Ruby 💎!
Subscribe to my newsletter
Read articles from Mohamed Salem directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by