Skip to content

Commit c7ca2a9

Browse files
authored
Merge pull request #63 from clio/run_preload_query_base_on_dive_limit
Update has_many_aggregate preload function to slice query into multiple ones base on dive limit
2 parents 3ee33e1 + 11f1443 commit c7ca2a9

File tree

4 files changed

+93
-9
lines changed

4 files changed

+93
-9
lines changed

lib/jit_preloader.rb

+10
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ def self.globally_enabled=(value)
2626
@enabled = value
2727
end
2828

29+
def self.max_ids_per_query=(max_ids)
30+
if max_ids && max_ids >= 1
31+
@max_ids_per_query = max_ids
32+
end
33+
end
34+
35+
def self.max_ids_per_query
36+
@max_ids_per_query
37+
end
38+
2939
def self.globally_enabled?
3040
if @enabled && @enabled.respond_to?(:call)
3141
@enabled.call

lib/jit_preloader/active_record/base.rb

+18-9
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def self.prepended(base)
9090
class << base
9191
delegate :jit_preload, to: :all
9292

93-
def has_many_aggregate(assoc, name, aggregate, field, table_alias_name: nil, default: 0)
93+
def has_many_aggregate(assoc, name, aggregate, field, table_alias_name: nil, default: 0, max_ids_per_query: nil)
9494
method_name = "#{assoc}_#{name}"
9595

9696
define_method(method_name) do |conditions={}|
@@ -101,6 +101,13 @@ def has_many_aggregate(assoc, name, aggregate, field, table_alias_name: nil, def
101101
if jit_preloader
102102
reflection = association(assoc).reflection
103103
primary_ids = jit_preloader.records.collect{|r| r[reflection.active_record_primary_key] }
104+
max_ids_per_query = max_ids_per_query || JitPreloader.max_ids_per_query
105+
if max_ids_per_query
106+
slices = primary_ids.each_slice(max_ids_per_query)
107+
else
108+
slices = [primary_ids]
109+
end
110+
104111
klass = reflection.klass
105112

106113
aggregate_association = reflection
@@ -115,27 +122,29 @@ def has_many_aggregate(assoc, name, aggregate, field, table_alias_name: nil, def
115122
table_reference = table_alias_name
116123
table_reference ||= association_scope.references_values.first || aggregate_association.table_name
117124

118-
conditions[table_reference] = { aggregate_association.foreign_key => primary_ids }
119-
120125
# If the association is a STI child model, specify its type in the condition so that it
121126
# doesn't include results from other child models
122127
parent_is_base_class = aggregate_association.klass.superclass.abstract_class? || aggregate_association.klass.superclass == ActiveRecord::Base
123128
has_type_column = aggregate_association.klass.column_names.include?(aggregate_association.klass.inheritance_column)
124129
is_child_sti_model = !parent_is_base_class && has_type_column
125130
if is_child_sti_model
126-
conditions[table_reference].merge!({ aggregate_association.klass.inheritance_column => aggregate_association.klass.sti_name })
131+
conditions[table_reference] = { aggregate_association.klass.inheritance_column => aggregate_association.klass.sti_name }
127132
end
128133

129134
if reflection.type.present?
130135
conditions[reflection.type] = self.class.name
131136
end
132137
group_by = "#{table_reference}.#{aggregate_association.foreign_key}"
133138

134-
preloaded_data = Hash[association_scope
135-
.where(conditions)
136-
.group(group_by)
137-
.send(aggregate, field)
138-
]
139+
preloaded_data = {}
140+
slices.each do |slice|
141+
data = Hash[association_scope
142+
.where(conditions.deep_merge(table_reference => { aggregate_association.foreign_key => slice }))
143+
.group(group_by)
144+
.send(aggregate, field)
145+
]
146+
preloaded_data.merge!(data)
147+
end
139148

140149
jit_preloader.records.each do |record|
141150
record.jit_preload_aggregates ||= {}

spec/lib/jit_preloader/preloader_spec.rb

+64
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require "spec_helper"
2+
require "db-query-matchers"
23

34
RSpec.describe JitPreloader::Preloader do
45
let!(:contact1) do
@@ -479,6 +480,69 @@
479480
end
480481
end
481482
end
483+
484+
context "with dive limit set" do
485+
let!(:contact_book_1) { ContactBook.create(name: "The Yellow Pages") }
486+
let!(:contact_book_2) { ContactBook.create(name: "The Yellow Pages") }
487+
let!(:contact_book_3) { ContactBook.create(name: "The Yellow Pages") }
488+
let!(:company1) { Company.create(name: "Company1", contact_book: contact_book_1) }
489+
let!(:company2) { Company.create(name: "Company2", contact_book: contact_book_1) }
490+
let!(:company3) { Company.create(name: "Company2", contact_book: contact_book_2) }
491+
let!(:company4) { Company.create(name: "Company4", contact_book: contact_book_3) }
492+
let!(:company5) { Company.create(name: "Company5", contact_book: contact_book_3) }
493+
494+
context "from the global value" do
495+
before do
496+
JitPreloader.max_ids_per_query = 2
497+
end
498+
499+
after do
500+
JitPreloader.max_ids_per_query = nil
501+
end
502+
503+
it "can handle queries" do
504+
contact_books = ContactBook.jit_preload.to_a
505+
506+
expect(contact_books.first.companies_count).to eq 2
507+
expect(contact_books.second.companies_count).to eq 1
508+
expect(contact_books.last.companies_count).to eq 2
509+
end
510+
511+
it "makes the right number of queries based on dive limit" do
512+
contact_books = ContactBook.jit_preload.to_a
513+
expect do
514+
contact_books.first.companies_count
515+
end.to make_database_queries(count: 2)
516+
517+
expect do
518+
contact_books.second.companies_count
519+
contact_books.last.companies_count
520+
end.to_not make_database_queries
521+
end
522+
end
523+
524+
context "from aggregate argument" do
525+
it "can handle queries" do
526+
contact_books = ContactBook.jit_preload.to_a
527+
528+
expect(contact_books.first.companies_count_with_max_ids_set).to eq 2
529+
expect(contact_books.second.companies_count_with_max_ids_set).to eq 1
530+
expect(contact_books.last.companies_count_with_max_ids_set).to eq 2
531+
end
532+
533+
it "makes the right number of queries based on dive limit" do
534+
contact_books = ContactBook.jit_preload.to_a
535+
expect do
536+
contact_books.first.companies_count_with_max_ids_set
537+
end.to make_database_queries(count: 2)
538+
539+
expect do
540+
contact_books.second.companies_count_with_max_ids_set
541+
contact_books.last.companies_count_with_max_ids_set
542+
end.to_not make_database_queries
543+
end
544+
end
545+
end
482546
end
483547

484548
end

spec/support/models.rb

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class ContactBook < ActiveRecord::Base
1010
has_many :children, through: :parents
1111

1212
has_many_aggregate :companies, :count, :count, "*"
13+
has_many_aggregate :companies, :count_with_max_ids_set, :count, "*", max_ids_per_query: 2
1314
has_many_aggregate :employees, :count, :count, "*"
1415
has_many_aggregate :company_employees, :count, :count, "*"
1516
has_many_aggregate :children, :count, :count, "*"

0 commit comments

Comments
 (0)