jit_preloader 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +30 -0
- data/.github/workflows/gem-push.yml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +2 -0
- data/README.md +27 -0
- data/jit_preloader.gemspec +1 -1
- data/lib/jit_preloader/active_record/associations/preloader/{ar5_association.rb → ar7_association.rb} +15 -18
- data/lib/jit_preloader/active_record/associations/preloader/ar7_branch.rb +22 -0
- data/lib/jit_preloader/active_record/associations/singular_association.rb +5 -1
- data/lib/jit_preloader/active_record/base.rb +19 -10
- data/lib/jit_preloader/preloader.rb +36 -10
- data/lib/jit_preloader/version.rb +1 -1
- data/lib/jit_preloader.rb +14 -3
- data/spec/lib/jit_preloader/preloader_spec.rb +88 -0
- data/spec/support/models.rb +1 -0
- metadata +6 -12
- data/Gemfile.5.2 +0 -6
- data/Gemfile.5.2.lock +0 -72
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 11ad98c8ab59226bb807d9686a0440140b7ca802d2faf33a0c26c428f2371e7a
|
4
|
+
data.tar.gz: 0e4832fbea9fc6fafce7445390ef34eed82c3984df471486a7e871db4c6b2717
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8932fc1d40b83fd0678ceed57856ab13021e047e9c4ea82b6b42434b98b760f9d51d5b77b232db249573bce512e930e7374b006e09977d9105086a24ff79b1b
|
7
|
+
data.tar.gz: 9595ae9cddddde356275480f54efe19c3792e93b100abad3ddced491644c06d8dfc197af4651a7be7af9aa1fe4d319fcf8f383e6493f2e7efec245094926e56e
|
@@ -0,0 +1,30 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [master]
|
6
|
+
pull_request:
|
7
|
+
branches: [master]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
strategy:
|
13
|
+
fail-fast: false
|
14
|
+
matrix:
|
15
|
+
gemfile:
|
16
|
+
- Gemfile
|
17
|
+
- Gemfile.6.1
|
18
|
+
env:
|
19
|
+
BUNDLE_GEMFILE: ${{ matrix.gemfile }}
|
20
|
+
steps:
|
21
|
+
- uses: actions/checkout@v4
|
22
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
23
|
+
uses: ruby/setup-ruby@v1
|
24
|
+
with:
|
25
|
+
ruby-version: 3.1
|
26
|
+
- name: Install dependencies
|
27
|
+
run: bundle install
|
28
|
+
- name: Run tests
|
29
|
+
run:
|
30
|
+
bundle exec rspec
|
@@ -12,11 +12,11 @@ jobs:
|
|
12
12
|
contents: read
|
13
13
|
|
14
14
|
steps:
|
15
|
-
- uses: actions/checkout@
|
16
|
-
- name: Set up Ruby
|
17
|
-
uses:
|
15
|
+
- uses: actions/checkout@v4
|
16
|
+
- name: Set up Ruby 3.1
|
17
|
+
uses: ruby/setup-ruby@v1
|
18
18
|
with:
|
19
|
-
ruby-version:
|
19
|
+
ruby-version: 3.1
|
20
20
|
|
21
21
|
- name: Publish to RubyGems
|
22
22
|
env:
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -173,6 +173,20 @@ end
|
|
173
173
|
|
174
174
|
```
|
175
175
|
|
176
|
+
Furthermore, there is an argument `max_ids_per_query` setting max ids per query. This helps prevent running a single query with too large list of ids which may be less efficient than splitting into multiple queries.
|
177
|
+
```ruby
|
178
|
+
class Contact < ActiveRecord::Base
|
179
|
+
has_many :addresses
|
180
|
+
has_many_aggregate :addresses, :count_all, :count, "*", max_ids_per_query: 10
|
181
|
+
end
|
182
|
+
|
183
|
+
Contact.jit_preload.each do |contact|
|
184
|
+
contact.addresses_count_all
|
185
|
+
end
|
186
|
+
# SELECT contact_id, COUNT(*) FROM addresses WHERE contact_id IN (1, 2, 3, ... ,10) GROUP BY contact_id
|
187
|
+
# SELECT contact_id, COUNT(*) FROM addresses WHERE contact_id IN (11, 12, 13) GROUP BY contact_id
|
188
|
+
```
|
189
|
+
|
176
190
|
### Preloading a subset of an association
|
177
191
|
|
178
192
|
There are often times when you want to preload a subset of an association, or change how the SQL statement is generated. For example, if a `Contact` model has
|
@@ -213,6 +227,7 @@ end
|
|
213
227
|
### Jit preloading globally across your application
|
214
228
|
|
215
229
|
The JitPreloader can be globally enabled, in which case most N+1 queries in your app should just disappear. It is off by default.
|
230
|
+
The `max_ids_per_query` argument on loading aggregate methods can also apply on a global level.
|
216
231
|
|
217
232
|
```ruby
|
218
233
|
# Can be true or false
|
@@ -223,12 +238,24 @@ JitPreloader.globally_enabled = true
|
|
223
238
|
# so that you can turn it on or off dynamically.
|
224
239
|
JitPreloader.globally_enabled = ->{ $redis.get('always_jit_preload') == 'on' }
|
225
240
|
|
241
|
+
# Setting global max ids constraint on all aggregation methods.
|
242
|
+
JitPreloader.max_ids_per_query = 10
|
243
|
+
|
244
|
+
class Contact < ActiveRecord::Base
|
245
|
+
has_many :emails
|
246
|
+
has_many_aggregate :emails, :count_all, :count, "*"
|
247
|
+
end
|
248
|
+
|
226
249
|
# When enabled globally, this would not generate an N+1 query.
|
227
250
|
Contact.all.each do |contact|
|
228
251
|
contact.emails.each do |email|
|
229
252
|
# do something
|
230
253
|
end
|
254
|
+
# When max_ides_per_query is set globally, the aggregate method will split query base on the limit.
|
255
|
+
contact.emails_count_all
|
231
256
|
end
|
257
|
+
|
258
|
+
|
232
259
|
```
|
233
260
|
|
234
261
|
## What it doesn't solve
|
data/jit_preloader.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_dependency "activerecord", "
|
21
|
+
spec.add_dependency "activerecord", "< 8"
|
22
22
|
spec.add_dependency "activesupport"
|
23
23
|
|
24
24
|
spec.add_development_dependency "bundler"
|
@@ -5,19 +5,17 @@ module JitPreloader
|
|
5
5
|
# below. Our changes here are that we remove records that are already
|
6
6
|
# part of the target, then attach all of the records to a new jit preloader.
|
7
7
|
#
|
8
|
-
# def run
|
9
|
-
# records =
|
10
|
-
# owner = owners_by_key[convert_key(record[association_key_name])]
|
11
|
-
# association = owner.association(reflection.name)
|
12
|
-
# association.set_inverse_instance(record)
|
13
|
-
# end
|
8
|
+
# def run
|
9
|
+
# records = records_by_owner
|
14
10
|
|
15
11
|
# owners.each do |owner|
|
16
|
-
# associate_records_to_owner(owner, records[
|
17
|
-
# end
|
12
|
+
# associate_records_to_owner(owner, records[owner] || [])
|
13
|
+
# end if @associate
|
14
|
+
|
15
|
+
# self
|
18
16
|
# end
|
19
17
|
|
20
|
-
def run
|
18
|
+
def run
|
21
19
|
return unless (reflection.scope.nil? || reflection.scope.arity == 0) && klass.ancestors.include?(ActiveRecord::Base)
|
22
20
|
|
23
21
|
super.tap do
|
@@ -29,31 +27,30 @@ module JitPreloader
|
|
29
27
|
|
30
28
|
# Original method:
|
31
29
|
# def associate_records_to_owner(owner, records)
|
30
|
+
# return if loaded?(owner)
|
31
|
+
#
|
32
32
|
# association = owner.association(reflection.name)
|
33
|
-
#
|
33
|
+
#
|
34
34
|
# if reflection.collection?
|
35
|
-
# association.target
|
35
|
+
# association.target = records
|
36
36
|
# else
|
37
|
-
# association.target = records.first
|
37
|
+
# association.target = records.first
|
38
38
|
# end
|
39
39
|
# end
|
40
40
|
def associate_records_to_owner(owner, records)
|
41
|
+
return if loaded?(owner)
|
42
|
+
|
41
43
|
association = owner.association(reflection.name)
|
42
|
-
association.loaded!
|
43
44
|
|
44
45
|
if reflection.collection?
|
45
|
-
# It is possible that some of the records are loaded already.
|
46
|
-
# We don't want to duplicate them, but we also want to preserve
|
47
|
-
# the original copy so that we don't blow away in-memory changes.
|
48
46
|
new_records = association.target.any? ? records - association.target : records
|
49
47
|
association.target.concat(new_records)
|
50
48
|
association.loaded!
|
51
49
|
else
|
52
|
-
association.target = records.first
|
50
|
+
association.target = records.first
|
53
51
|
end
|
54
52
|
end
|
55
53
|
|
56
|
-
|
57
54
|
def build_scope
|
58
55
|
super.tap do |scope|
|
59
56
|
scope.jit_preload! if owners.any?(&:jit_preloader) || JitPreloader.globally_enabled?
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module JitPreloader
|
2
|
+
module PreloaderBranch
|
3
|
+
"""
|
4
|
+
ActiveRecord version >= 7.x.x introduced an improvement for preloading associations in batches:
|
5
|
+
https://github.com/rails/rails/blob/main/activerecord/lib/active_record/associations/preloader.rb#L121
|
6
|
+
|
7
|
+
Our existing monkey-patches will ignore associations whose classes are not descendants of
|
8
|
+
ActiveRecord::Base (example: https://github.com/clio/jit_preloader/blob/master/lib/jit_preloader/active_record/associations/preloader/ar6_association.rb#L19).
|
9
|
+
But this change breaks that behaviour because now Batch is calling `klass.base_class` (a method defined by ActiveRecord::Base)
|
10
|
+
before we have a chance to filter out the non-AR classes.
|
11
|
+
This patch for AR 7.x makes the Branch class ignore any association loaders that aren't for ActiveRecord::Base subclasses.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def loaders
|
15
|
+
@loaders = super.find_all do |loader|
|
16
|
+
loader.klass < ::ActiveRecord::Base
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
ActiveRecord::Associations::Preloader::Branch.prepend(JitPreloader::PreloaderBranch)
|
@@ -18,13 +18,17 @@ module JitPreloader
|
|
18
18
|
# always an N+1 query.
|
19
19
|
record.jit_n_plus_one_tracking ||= owner.jit_n_plus_one_tracking if record
|
20
20
|
|
21
|
-
if !jit_loaded && owner.jit_n_plus_one_tracking
|
21
|
+
if !jit_loaded && owner.jit_n_plus_one_tracking && !is_polymorphic_association_without_type
|
22
22
|
ActiveSupport::Notifications.publish("n_plus_one_query",
|
23
23
|
source: owner, association: reflection.name)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
28
|
+
|
29
|
+
private def is_polymorphic_association_without_type
|
30
|
+
self.is_a?(ActiveRecord::Associations::BelongsToPolymorphicAssociation) && self.klass.nil?
|
31
|
+
end
|
28
32
|
end
|
29
33
|
end
|
30
34
|
|
@@ -18,7 +18,7 @@ module JitPreloadExtension
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("
|
21
|
+
if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("7.0.0")
|
22
22
|
def preload_scoped_relation(name:, base_association:, preload_scope: nil)
|
23
23
|
return jit_preload_scoped_relations[name] if jit_preload_scoped_relations&.key?(name)
|
24
24
|
|
@@ -90,7 +90,7 @@ module JitPreloadExtension
|
|
90
90
|
class << base
|
91
91
|
delegate :jit_preload, to: :all
|
92
92
|
|
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)
|
94
94
|
method_name = "#{assoc}_#{name}"
|
95
95
|
|
96
96
|
define_method(method_name) do |conditions={}|
|
@@ -101,6 +101,13 @@ module JitPreloadExtension
|
|
101
101
|
if jit_preloader
|
102
102
|
reflection = association(assoc).reflection
|
103
103
|
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
|
+
|
104
111
|
klass = reflection.klass
|
105
112
|
|
106
113
|
aggregate_association = reflection
|
@@ -115,15 +122,13 @@ module JitPreloadExtension
|
|
115
122
|
table_reference = table_alias_name
|
116
123
|
table_reference ||= association_scope.references_values.first || aggregate_association.table_name
|
117
124
|
|
118
|
-
conditions[table_reference] = { aggregate_association.foreign_key => primary_ids }
|
119
|
-
|
120
125
|
# If the association is a STI child model, specify its type in the condition so that it
|
121
126
|
# doesn't include results from other child models
|
122
127
|
parent_is_base_class = aggregate_association.klass.superclass.abstract_class? || aggregate_association.klass.superclass == ActiveRecord::Base
|
123
128
|
has_type_column = aggregate_association.klass.column_names.include?(aggregate_association.klass.inheritance_column)
|
124
129
|
is_child_sti_model = !parent_is_base_class && has_type_column
|
125
130
|
if is_child_sti_model
|
126
|
-
conditions[table_reference]
|
131
|
+
conditions[table_reference] = { aggregate_association.klass.inheritance_column => aggregate_association.klass.sti_name }
|
127
132
|
end
|
128
133
|
|
129
134
|
if reflection.type.present?
|
@@ -131,11 +136,15 @@ module JitPreloadExtension
|
|
131
136
|
end
|
132
137
|
group_by = "#{table_reference}.#{aggregate_association.foreign_key}"
|
133
138
|
|
134
|
-
preloaded_data =
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
139
148
|
|
140
149
|
jit_preloader.records.each do |record|
|
141
150
|
record.jit_preload_aggregates ||= {}
|
@@ -3,21 +3,47 @@ module JitPreloader
|
|
3
3
|
|
4
4
|
attr_accessor :records
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
records.
|
9
|
-
|
6
|
+
if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("7.0.0")
|
7
|
+
def self.attach(records)
|
8
|
+
new(records: records.dup, associations: nil).tap do |loader|
|
9
|
+
records.each do |record|
|
10
|
+
record.jit_preloader = loader
|
11
|
+
end
|
10
12
|
end
|
11
13
|
end
|
12
|
-
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def jit_preload(associations)
|
16
|
+
# It is possible that the records array has multiple different classes (think single table inheritance).
|
17
|
+
# Thus, it is possible that some of the records don't have an association.
|
18
|
+
records_with_association = records.reject{|r| r.class.reflect_on_association(associations).nil? }
|
19
|
+
|
20
|
+
# Some of the records may already have the association loaded and we should not load them again
|
21
|
+
records_requiring_loading = records_with_association.select{|r| !r.association(associations).loaded? }
|
22
|
+
|
23
|
+
self.class.new(records: records_requiring_loading, associations: associations).call
|
24
|
+
end
|
25
|
+
else
|
26
|
+
def self.attach(records)
|
27
|
+
new.tap do |loader|
|
28
|
+
loader.records = records.dup
|
29
|
+
records.each do |record|
|
30
|
+
record.jit_preloader = loader
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def jit_preload(associations)
|
36
|
+
# It is possible that the records array has multiple different classes (think single table inheritance).
|
37
|
+
# Thus, it is possible that some of the records don't have an association.
|
38
|
+
records_with_association = records.reject{ |record| record.class.reflect_on_association(associations).nil? }
|
39
|
+
|
40
|
+
# Some of the records may already have the association loaded and we should not load them again
|
41
|
+
records_requiring_loading = records_with_association.select{ |record| !record.association(associations).loaded? }
|
42
|
+
preload records_with_association, associations
|
43
|
+
end
|
19
44
|
end
|
20
45
|
|
46
|
+
|
21
47
|
# We do not want the jit_preloader to be dumpable
|
22
48
|
# If you dump a ActiveRecord::Base object that has a jit_preloader instance variable
|
23
49
|
# you will also end up dumping all of the records the preloader has reference to.
|
data/lib/jit_preloader.rb
CHANGED
@@ -8,10 +8,11 @@ require 'jit_preloader/active_record/base'
|
|
8
8
|
require 'jit_preloader/active_record/relation'
|
9
9
|
require 'jit_preloader/active_record/associations/collection_association'
|
10
10
|
require 'jit_preloader/active_record/associations/singular_association'
|
11
|
-
if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("
|
11
|
+
if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("7.0.0")
|
12
|
+
require 'jit_preloader/active_record/associations/preloader/ar7_association'
|
13
|
+
require 'jit_preloader/active_record/associations/preloader/ar7_branch'
|
14
|
+
elsif Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("6.1.0")
|
12
15
|
require 'jit_preloader/active_record/associations/preloader/ar6_association'
|
13
|
-
elsif Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("5.2.2")
|
14
|
-
require 'jit_preloader/active_record/associations/preloader/ar5_association'
|
15
16
|
else
|
16
17
|
require 'jit_preloader/active_record/associations/preloader/collection_association'
|
17
18
|
require 'jit_preloader/active_record/associations/preloader/singular_association'
|
@@ -23,6 +24,16 @@ module JitPreloader
|
|
23
24
|
@enabled = value
|
24
25
|
end
|
25
26
|
|
27
|
+
def self.max_ids_per_query=(max_ids)
|
28
|
+
if max_ids && max_ids >= 1
|
29
|
+
@max_ids_per_query = max_ids
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.max_ids_per_query
|
34
|
+
@max_ids_per_query
|
35
|
+
end
|
36
|
+
|
26
37
|
def self.globally_enabled?
|
27
38
|
if @enabled && @enabled.respond_to?(:call)
|
28
39
|
@enabled.call
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "spec_helper"
|
2
|
+
require "db-query-matchers"
|
2
3
|
|
3
4
|
RSpec.describe JitPreloader::Preloader do
|
4
5
|
let!(:contact1) do
|
@@ -164,6 +165,30 @@ RSpec.describe JitPreloader::Preloader do
|
|
164
165
|
expect(Contact.jit_preload.map(&:contact_owner)).to eq [nil, ContactOwner.first, Address.first]
|
165
166
|
end
|
166
167
|
end
|
168
|
+
|
169
|
+
context "when a record has a polymorphic association type is nil" do
|
170
|
+
before do
|
171
|
+
contact1.update!(contact_owner_type: nil, contact_owner_id: nil)
|
172
|
+
end
|
173
|
+
|
174
|
+
it "successfully load the rest of association values and does not publish a n+1 notification" do
|
175
|
+
contacts = Contact.jit_preload.to_a
|
176
|
+
ActiveSupport::Notifications.subscribed(callback, "n_plus_one_query") do
|
177
|
+
expect(contacts.first.contact_owner).to eq(nil)
|
178
|
+
end
|
179
|
+
|
180
|
+
expect(source_map).to eql({})
|
181
|
+
|
182
|
+
expect do
|
183
|
+
contacts.first.contact_owner
|
184
|
+
contacts.second.contact_owner
|
185
|
+
contacts.third.contact_owner
|
186
|
+
end.not_to make_database_queries
|
187
|
+
|
188
|
+
expect(contacts.second.contact_owner).to eq(ContactOwner.first)
|
189
|
+
expect(contacts.third.contact_owner).to eq(Address.first)
|
190
|
+
end
|
191
|
+
end
|
167
192
|
end
|
168
193
|
end
|
169
194
|
|
@@ -479,6 +504,69 @@ RSpec.describe JitPreloader::Preloader do
|
|
479
504
|
end
|
480
505
|
end
|
481
506
|
end
|
507
|
+
|
508
|
+
context "with dive limit set" do
|
509
|
+
let!(:contact_book_1) { ContactBook.create(name: "The Yellow Pages") }
|
510
|
+
let!(:contact_book_2) { ContactBook.create(name: "The Yellow Pages") }
|
511
|
+
let!(:contact_book_3) { ContactBook.create(name: "The Yellow Pages") }
|
512
|
+
let!(:company1) { Company.create(name: "Company1", contact_book: contact_book_1) }
|
513
|
+
let!(:company2) { Company.create(name: "Company2", contact_book: contact_book_1) }
|
514
|
+
let!(:company3) { Company.create(name: "Company2", contact_book: contact_book_2) }
|
515
|
+
let!(:company4) { Company.create(name: "Company4", contact_book: contact_book_3) }
|
516
|
+
let!(:company5) { Company.create(name: "Company5", contact_book: contact_book_3) }
|
517
|
+
|
518
|
+
context "from the global value" do
|
519
|
+
before do
|
520
|
+
JitPreloader.max_ids_per_query = 2
|
521
|
+
end
|
522
|
+
|
523
|
+
after do
|
524
|
+
JitPreloader.max_ids_per_query = nil
|
525
|
+
end
|
526
|
+
|
527
|
+
it "can handle queries" do
|
528
|
+
contact_books = ContactBook.jit_preload.to_a
|
529
|
+
|
530
|
+
expect(contact_books.first.companies_count).to eq 2
|
531
|
+
expect(contact_books.second.companies_count).to eq 1
|
532
|
+
expect(contact_books.last.companies_count).to eq 2
|
533
|
+
end
|
534
|
+
|
535
|
+
it "makes the right number of queries based on dive limit" do
|
536
|
+
contact_books = ContactBook.jit_preload.to_a
|
537
|
+
expect do
|
538
|
+
contact_books.first.companies_count
|
539
|
+
end.to make_database_queries(count: 2)
|
540
|
+
|
541
|
+
expect do
|
542
|
+
contact_books.second.companies_count
|
543
|
+
contact_books.last.companies_count
|
544
|
+
end.to_not make_database_queries
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
context "from aggregate argument" do
|
549
|
+
it "can handle queries" do
|
550
|
+
contact_books = ContactBook.jit_preload.to_a
|
551
|
+
|
552
|
+
expect(contact_books.first.companies_count_with_max_ids_set).to eq 2
|
553
|
+
expect(contact_books.second.companies_count_with_max_ids_set).to eq 1
|
554
|
+
expect(contact_books.last.companies_count_with_max_ids_set).to eq 2
|
555
|
+
end
|
556
|
+
|
557
|
+
it "makes the right number of queries based on dive limit" do
|
558
|
+
contact_books = ContactBook.jit_preload.to_a
|
559
|
+
expect do
|
560
|
+
contact_books.first.companies_count_with_max_ids_set
|
561
|
+
end.to make_database_queries(count: 2)
|
562
|
+
|
563
|
+
expect do
|
564
|
+
contact_books.second.companies_count_with_max_ids_set
|
565
|
+
contact_books.last.companies_count_with_max_ids_set
|
566
|
+
end.to_not make_database_queries
|
567
|
+
end
|
568
|
+
end
|
569
|
+
end
|
482
570
|
end
|
483
571
|
|
484
572
|
end
|
data/spec/support/models.rb
CHANGED
@@ -10,6 +10,7 @@ class ContactBook < ActiveRecord::Base
|
|
10
10
|
has_many :children, through: :parents
|
11
11
|
|
12
12
|
has_many_aggregate :companies, :count, :count, "*"
|
13
|
+
has_many_aggregate :companies, :count_with_max_ids_set, :count, "*", max_ids_per_query: 2
|
13
14
|
has_many_aggregate :employees, :count, :count, "*"
|
14
15
|
has_many_aggregate :company_employees, :count, :count, "*"
|
15
16
|
has_many_aggregate :children, :count, :count, "*"
|
metadata
CHANGED
@@ -1,22 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jit_preloader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kyle d'Oliveira
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '7'
|
20
17
|
- - "<"
|
21
18
|
- !ruby/object:Gem::Version
|
22
19
|
version: '8'
|
@@ -24,9 +21,6 @@ dependencies:
|
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
|
-
- - ">="
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: '7'
|
30
24
|
- - "<"
|
31
25
|
- !ruby/object:Gem::Version
|
32
26
|
version: '8'
|
@@ -152,12 +146,11 @@ extensions: []
|
|
152
146
|
extra_rdoc_files: []
|
153
147
|
files:
|
154
148
|
- ".github/CODEOWNERS"
|
149
|
+
- ".github/workflows/ci.yml"
|
155
150
|
- ".github/workflows/gem-push.yml"
|
156
151
|
- ".gitignore"
|
157
152
|
- ".rspec"
|
158
153
|
- Gemfile
|
159
|
-
- Gemfile.5.2
|
160
|
-
- Gemfile.5.2.lock
|
161
154
|
- Gemfile.6.0
|
162
155
|
- Gemfile.6.0.lock
|
163
156
|
- Gemfile.6.1
|
@@ -168,8 +161,9 @@ files:
|
|
168
161
|
- jit_preloader.gemspec
|
169
162
|
- lib/jit_preloader.rb
|
170
163
|
- lib/jit_preloader/active_record/associations/collection_association.rb
|
171
|
-
- lib/jit_preloader/active_record/associations/preloader/ar5_association.rb
|
172
164
|
- lib/jit_preloader/active_record/associations/preloader/ar6_association.rb
|
165
|
+
- lib/jit_preloader/active_record/associations/preloader/ar7_association.rb
|
166
|
+
- lib/jit_preloader/active_record/associations/preloader/ar7_branch.rb
|
173
167
|
- lib/jit_preloader/active_record/associations/preloader/collection_association.rb
|
174
168
|
- lib/jit_preloader/active_record/associations/preloader/singular_association.rb
|
175
169
|
- lib/jit_preloader/active_record/associations/singular_association.rb
|
@@ -201,7 +195,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
201
195
|
- !ruby/object:Gem::Version
|
202
196
|
version: '0'
|
203
197
|
requirements: []
|
204
|
-
rubygems_version: 3.
|
198
|
+
rubygems_version: 3.3.27
|
205
199
|
signing_key:
|
206
200
|
specification_version: 4
|
207
201
|
summary: Tool to understand N+1 queries and to remove them
|
data/Gemfile.5.2
DELETED
data/Gemfile.5.2.lock
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
jit_preloader (1.0.3)
|
5
|
-
activerecord (>= 5.2, < 7)
|
6
|
-
activesupport
|
7
|
-
|
8
|
-
GEM
|
9
|
-
remote: https://rubygems.org/
|
10
|
-
specs:
|
11
|
-
activemodel (5.2.6)
|
12
|
-
activesupport (= 5.2.6)
|
13
|
-
activerecord (5.2.6)
|
14
|
-
activemodel (= 5.2.6)
|
15
|
-
activesupport (= 5.2.6)
|
16
|
-
arel (>= 9.0)
|
17
|
-
activesupport (5.2.6)
|
18
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
19
|
-
i18n (>= 0.7, < 2)
|
20
|
-
minitest (~> 5.1)
|
21
|
-
tzinfo (~> 1.1)
|
22
|
-
arel (9.0.0)
|
23
|
-
byebug (11.1.3)
|
24
|
-
concurrent-ruby (1.1.9)
|
25
|
-
database_cleaner (2.0.1)
|
26
|
-
database_cleaner-active_record (~> 2.0.0)
|
27
|
-
database_cleaner-active_record (2.0.1)
|
28
|
-
activerecord (>= 5.a)
|
29
|
-
database_cleaner-core (~> 2.0.0)
|
30
|
-
database_cleaner-core (2.0.1)
|
31
|
-
db-query-matchers (0.10.0)
|
32
|
-
activesupport (>= 4.0, < 7)
|
33
|
-
rspec (~> 3.0)
|
34
|
-
diff-lcs (1.4.4)
|
35
|
-
i18n (1.8.10)
|
36
|
-
concurrent-ruby (~> 1.0)
|
37
|
-
minitest (5.14.4)
|
38
|
-
rake (13.0.6)
|
39
|
-
rspec (3.10.0)
|
40
|
-
rspec-core (~> 3.10.0)
|
41
|
-
rspec-expectations (~> 3.10.0)
|
42
|
-
rspec-mocks (~> 3.10.0)
|
43
|
-
rspec-core (3.10.1)
|
44
|
-
rspec-support (~> 3.10.0)
|
45
|
-
rspec-expectations (3.10.1)
|
46
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
47
|
-
rspec-support (~> 3.10.0)
|
48
|
-
rspec-mocks (3.10.2)
|
49
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
50
|
-
rspec-support (~> 3.10.0)
|
51
|
-
rspec-support (3.10.2)
|
52
|
-
sqlite3 (1.4.2)
|
53
|
-
thread_safe (0.3.6)
|
54
|
-
tzinfo (1.2.9)
|
55
|
-
thread_safe (~> 0.1)
|
56
|
-
|
57
|
-
PLATFORMS
|
58
|
-
x86_64-darwin-19
|
59
|
-
|
60
|
-
DEPENDENCIES
|
61
|
-
activerecord (~> 5.2)
|
62
|
-
bundler
|
63
|
-
byebug
|
64
|
-
database_cleaner
|
65
|
-
db-query-matchers
|
66
|
-
jit_preloader!
|
67
|
-
rake (~> 13.0)
|
68
|
-
rspec
|
69
|
-
sqlite3
|
70
|
-
|
71
|
-
BUNDLED WITH
|
72
|
-
2.2.12
|