Replace includes with eager_load for optimal single-query performance

- Replace all includes() with eager_load() in find, find_by, and where methods
- eager_load forces single LEFT OUTER JOIN queries instead of potential N+1 queries
- Update test expectations to validate single query performance
- All query methods now use optimized single queries:
  * Car.find(id) - 1 query with full JOIN
  * Car.find_by(fuel_type: 'hybrid') - 1 query with full JOIN
  * Car.where(fuel_type: 'hybrid') - 1 query with full JOIN
  * Chained where() queries - 1 query with full JOIN

Performance improvements:
- Eliminated N+1 queries for find and find_by methods
- Consistent single-query behavior across all query methods
- Proper association loading with eager_load vs includes
- All 58 tests passing with optimal performance
This commit is contained in:
Dylan Knutson
2025-07-13 06:23:07 +00:00
parent 4f41b66f85
commit a09465ac54
2 changed files with 18 additions and 17 deletions

View File

@@ -84,11 +84,11 @@ module ActiveRecord
# Check if any aux columns are referenced
if conditions.keys.any? { |key| aux_columns.include?(key.to_s) }
# Split conditions and build query with includes
# Split conditions and build query with eager_load
main_conditions, aux_conditions =
split_conditions(conditions, aux_columns)
relation = self.includes(association_name)
relation = self.eager_load(association_name)
relation = relation.where(main_conditions) if main_conditions.any?
relation =
relation.where(
@@ -117,11 +117,11 @@ module ActiveRecord
aux_columns = get_aux_column_names
if conditions.keys.any? { |key| aux_columns.include?(key.to_s) }
# Use the enhanced where method with includes and get first result
self.includes(association_name).where(conditions).first
# Use the enhanced where method with eager_load and get first result
self.eager_load(association_name).where(conditions).first
else
# Use includes for non-aux queries to preload auxiliary data
self.includes(association_name).find_by(*args)
# Use eager_load for non-aux queries to preload auxiliary data
self.eager_load(association_name).find_by(*args)
end
else
original_find_by.call(*args)
@@ -131,8 +131,9 @@ module ActiveRecord
model_class.define_singleton_method(:find) do |*args|
# Override find to automatically include aux table joins
if has_aux_tables?
# Use includes to eager-load auxiliary data in the same query
self.includes(association_name).find(*args)
# Use eager_load to get both main and auxiliary data in single query
# and properly populate the association
self.eager_load(association_name).find(*args)
else
original_find.call(*args)
end
@@ -159,12 +160,12 @@ module ActiveRecord
main_conditions, aux_conditions =
klass.split_conditions(conditions, aux_columns)
# Ensure we have the aux includes
# Ensure we have the aux eager_load
relation = self
unless relation.includes_values.any? { |include_val|
include_val == association_name
unless relation.eager_load_values.any? { |eager_load_val|
eager_load_val == association_name
}
relation = relation.includes(association_name)
relation = relation.eager_load(association_name)
end
# Apply conditions

View File

@@ -496,8 +496,8 @@ RSpec.describe ActiveRecord::AuxTable do
end
describe "query count validation" do
# TODO: These tests currently document the performance issues with auxiliary tables
# The auto-join functionality needs to be properly implemented to avoid N+1 queries
# These tests validate the performance optimizations using eager_load
# All query methods now use single queries with proper LEFT OUTER JOINs
it "loads single model with auxiliary data in one query using find" do
query_count =
@@ -509,7 +509,7 @@ RSpec.describe ActiveRecord::AuxTable do
end
# TODO: Should be 1 query, but currently 2 due to auxiliary record loading
expect(query_count).to eq(2)
expect(query_count).to eq(1)
end
it "loads single model with auxiliary data in one query using find_by" do
@@ -521,8 +521,8 @@ RSpec.describe ActiveRecord::AuxTable do
car.engine_size
end
# TODO: Should be 1 query, but currently 2 due to auxiliary record loading
expect(query_count).to eq(2)
# Excellent! Now using eager_load for single query performance
expect(query_count).to eq(1)
end
it "loads multiple models with auxiliary data in one query using where" do