Improve query performance with includes and add comprehensive performance tests

- Replace left_joins with includes in auto-join queries for better performance
- Remove complex after_initialize callback since includes handles association loading
- Add automatic includes to find method for consistent behavior
- Add comprehensive performance tests with query counting
- Fix Sorbet type checking by adding ActiveSupport::Notifications to todo.rbi

Performance improvements:
- where() queries now use single query with includes
- N+1 queries avoided for multiple record loading
- All query methods (find, find_by, where) now optimized
This commit is contained in:
Dylan Knutson
2025-07-13 06:17:00 +00:00
parent fc6fd71e60
commit 4f41b66f85
3 changed files with 231 additions and 24 deletions

View File

@@ -61,20 +61,9 @@ module ActiveRecord
private
def setup_auto_loading(association_name)
# Use after_initialize to eagerly load aux data
self.after_initialize do
# Only for persisted records to avoid unnecessary queries
if persisted?
# Trigger the association load if not already loaded
association_obj = association(association_name)
unless association_obj.loaded?
# Load the aux record (this will create one if it doesn't exist)
send(association_name)
# Mark as loaded to prevent future queries
association_obj.loaded!
end
end
end
# Auto-loading is now handled via includes in query methods
# No need for after_initialize callback since we use includes
# which properly loads associations
end
def setup_query_extensions(association_name)
@@ -84,6 +73,7 @@ module ActiveRecord
# Store original methods
original_where = model_class.method(:where)
original_find_by = model_class.method(:find_by)
original_find = model_class.method(:find)
# Define a helper method for processing where conditions
model_class.define_singleton_method(:process_aux_where) do |*args|
@@ -94,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 join
# Split conditions and build query with includes
main_conditions, aux_conditions =
split_conditions(conditions, aux_columns)
relation = self.left_joins(association_name)
relation = self.includes(association_name)
relation = relation.where(main_conditions) if main_conditions.any?
relation =
relation.where(
@@ -127,16 +117,27 @@ module ActiveRecord
aux_columns = get_aux_column_names
if conditions.keys.any? { |key| aux_columns.include?(key.to_s) }
# Use the enhanced where method and get first result
self.where(conditions).first
# Use the enhanced where method with includes and get first result
self.includes(association_name).where(conditions).first
else
original_find_by.call(*args)
# Use includes for non-aux queries to preload auxiliary data
self.includes(association_name).find_by(*args)
end
else
original_find_by.call(*args)
end
end
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)
else
original_find.call(*args)
end
end
# Also override where on ActiveRecord::Relation for chained calls
setup_relation_where_override(association_name)
end
@@ -154,16 +155,16 @@ module ActiveRecord
# Check if any aux columns are referenced
if conditions.keys.any? { |key| aux_columns.include?(key.to_s) }
# Split conditions and ensure aux join exists
# Split conditions and ensure aux includes exists
main_conditions, aux_conditions =
klass.split_conditions(conditions, aux_columns)
# Ensure we have the aux join
# Ensure we have the aux includes
relation = self
unless relation.joins_values.any? { |join|
join.to_s.include?(association_name.to_s)
unless relation.includes_values.any? { |include_val|
include_val == association_name
}
relation = relation.left_joins(association_name)
relation = relation.includes(association_name)
end
# Apply conditions