Skip to content

Commit 25757d7

Browse files
authored
Merge pull request #64 from clio/refactor_version_compatibility_with_ci
Refactor gem to be compatible with multiple ActiveRecord versions; Adding ci github action
2 parents c7ca2a9 + 24ead71 commit 25757d7

File tree

8 files changed

+98
-15
lines changed

8 files changed

+98
-15
lines changed

.github/workflows/ci.yml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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.5.2
18+
- Gemfile.6.0
19+
- Gemfile.6.1
20+
env:
21+
BUNDLE_GEMFILE: ${{ matrix.gemfile }}
22+
steps:
23+
- uses: actions/checkout@v4
24+
- name: Set up Ruby ${{ matrix.ruby-version }}
25+
uses: ruby/setup-ruby@v1
26+
with:
27+
ruby-version: 2.7
28+
- name: Install dependencies
29+
run: bundle install
30+
- name: Run tests
31+
run:
32+
bundle exec rspec

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
/test/tmp/
1111
/test/version_tmp/
1212
/tmp/
13+
.idea/*
1314

1415
# Used by dotenv library to load environment variables.
1516
# .env

Gemfile

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
source 'https://rubygems.org'
22

3+
gem "activerecord", ">=7"
34
# Specify your gem's dependencies in jit_preloader.gemspec
45
gemspec

README.md

+27
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,20 @@ end
173173

174174
```
175175

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+
176190
### Preloading a subset of an association
177191

178192
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
213227
### Jit preloading globally across your application
214228

215229
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.
216231

217232
```ruby
218233
# Can be true or false
@@ -223,12 +238,24 @@ JitPreloader.globally_enabled = true
223238
# so that you can turn it on or off dynamically.
224239
JitPreloader.globally_enabled = ->{ $redis.get('always_jit_preload') == 'on' }
225240

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+
226249
# When enabled globally, this would not generate an N+1 query.
227250
Contact.all.each do |contact|
228251
contact.emails.each do |email|
229252
# do something
230253
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
231256
end
257+
258+
232259
```
233260

234261
## What it doesn't solve

jit_preloader.gemspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
1818
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
1919
spec.require_paths = ["lib"]
2020

21-
spec.add_dependency "activerecord", ">= 7", "< 8"
21+
spec.add_dependency "activerecord", "< 8"
2222
spec.add_dependency "activesupport"
2323

2424
spec.add_development_dependency "bundler"

lib/jit_preloader/active_record/base.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def clear_jit_preloader!
1818
end
1919
end
2020

21-
if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("6.0.0")
21+
if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("7.0.0")
2222
def preload_scoped_relation(name:, base_association:, preload_scope: nil)
2323
return jit_preload_scoped_relations[name] if jit_preload_scoped_relations&.key?(name)
2424

lib/jit_preloader/preloader.rb

+34-12
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,47 @@ class Preloader < ActiveRecord::Associations::Preloader
33

44
attr_accessor :records
55

6-
def self.attach(records)
7-
new(records: records.dup, associations: nil).tap do |loader|
8-
records.each do |record|
9-
record.jit_preloader = loader
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
1012
end
1113
end
12-
end
1314

14-
def jit_preload(associations)
15-
# It is possible that the records array has multiple different classes (think single table inheritance).
16-
# Thus, it is possible that some of the records don't have an association.
17-
records_with_association = records.reject{|r| r.class.reflect_on_association(associations).nil? }
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
1834

19-
# Some of the records may already have the association loaded and we should not load them again
20-
records_requiring_loading = records_with_association.select{|r| !r.association(associations).loaded? }
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? }
2139

22-
self.class.new(records: records_requiring_loading, associations: associations).call
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
2344
end
2445

46+
2547
# We do not want the jit_preloader to be dumpable
2648
# If you dump a ActiveRecord::Base object that has a jit_preloader instance variable
2749
# you will also end up dumping all of the records the preloader has reference to.

lib/jit_preloader/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module JitPreloader
2-
VERSION = "2.0.2"
2+
VERSION = "2.1.0"
33
end

0 commit comments

Comments
 (0)