Introduction: The Drive for Efficiency

In the realm of software engineering, efficiency extends beyond speed. It’s about optimizing resources and enhancing user experiences. In Ruby, known for its elegance, the choice of methods significantly affects performance. This article explores various Ruby and Rails methods, comparing their memory usage and runtime efficiency.

exists? vs present? vs any?

Using present? or any? on an ActiveRecord relation loads the records into memory to check for matches, which can be inefficient with large datasets.

User.where(email: 'example@gmail.com').present?
User.where(email: 'example@gmail.com').any?

The Advantage of exists?

The exists? method is designed to check for the presence of a record without loading the actual data, making it much faster.

User.where(email: 'example@gmail.com').exists?

update_all vs update

The Drawback of update

Using update on individual records triggers multiple database queries.

users.each { |user| user.update(active: true) }

The Efficiency of update_all

The update_all method updates all matching records in a single query, significantly improving performance. It’s important to use where judiciously to ensure the column is indexed.

User.where(active: false).update_all(active: true)

includes vs joins vs preload

Eager Loading Associations

The N+1 Query Problem: Using joins can cause the N+1 query problem, where each associated record is queried separately.

User.joins(:posts) # Inefficient
User.joins(:posts).each { |user| user.posts.each { |post| puts post.title } }

The Efficiency of includes

The includes method performs eager loading, fetching all associated records with minimal queries.

User.includes(:posts).each { |user| user.posts.each { |post| puts post.title } }

count vs size vs length

Counting Records

The Inefficiency of length

Using length loads all records into memory, which can be inefficient.

User.all.length # Inefficient

The Efficiency of count

The count method executes a SELECT COUNT(*) query, which is more efficient for large datasets.

User.count # Always makes one query
User.all.count { |u| u.active? } # Efficient use case

The Versatility of size

The size method adapts to the situation, determining whether to use an existing dataset or make a count query.

  • Use length if the dataset is already loaded.
  • Use count if nothing is loaded.
  • Use size for automatic adaptation.

find vs find_each

Using find to handle thousands of records is inefficient as it loads all records before processing.

User.find(1..10000).each do |user|
  # some processing
end

The Efficiency of find_each

find_each processes records in batches, reducing memory usage.

User.find_each(batch_size: 1000) do |user|
  # some processing
end

select vs pluck

Using select

This method selects specific columns and loads them as ActiveRecord objects.

User.select(:id, :name, :email).each { |user| ... }

Using pluck

This method retrieves specified columns directly from the database, returning an array of values.

User.pluck(:id, :name, :email)

where vs find_by

Using where

where is suitable for retrieving multiple records.

User.where(name: 'John').first

Using find_by

find_by is optimized to return the first matching record, halting the query as soon as a match is found.

User.find_by(name: 'John')

delete_all vs destroy_all

delete_all

delete_all performs fast deletions directly in the database without loading records into Rails models.

User.where(active: false).delete_all

destroy_all

destroy_all deletes records while running callbacks, ensuring data integrity at the cost of performance.

User.where(active: false).destroy_all

I hope you find this article helpful.

Thôi Lo Code Đi Kẻo Sếp nạt!