jit_preloader 0.2.5 → 1.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: '09ecd094d5ac62f0599c9ed43773b8fc8188af6f'
4
- data.tar.gz: ab9723b5d2a0aa9c73f3ac6108fe573e24c67ec9
2
+ SHA256:
3
+ metadata.gz: e5cc23052a16564dfac2f68f6668197ceda450b77fa83f87b0d33dc11138a863
4
+ data.tar.gz: 35f074caa0985696872a9bdb56f48f966897715b6ad2db5545b5c3d08d01117b
5
5
  SHA512:
6
- metadata.gz: d966d6459872e1c2d97a6c511823f781e6c3949e45e60cba2e1dd46a16a10d2932a7d9cadd8173771eef1b808355923bc2bcfa515bc2be821ddf909b1d52e5af
7
- data.tar.gz: d680dbaa731dc7f0cf5621d0bf39bbcb45feb09419b6c43c90d6c0ac5fa3cb6ffc4c3e9be372df545c2b897a66cf134ab3e0c47fc40c8bc1c754398120caaf63
6
+ metadata.gz: b5df602d53a93602c2733ace0a78ff16e0c1e2ea8958aa2585e56a7c119637f6d6e033761a710e66fb0c69e6c03701a264faf2e605c4eaf88333ccb2c860f9d7
7
+ data.tar.gz: 3b64bdcde548bb182088294b14d1e239e825b92dc3b43772839e2967e6da64900360c84dea1d0578251262511333a55a42c57d948d101af032235a417e9c3e3a
data/.gitignore CHANGED
@@ -43,7 +43,7 @@ build-iPhoneSimulator/
43
43
 
44
44
  # for a library or gem, you might want to ignore these files since the code is
45
45
  # intended to run in multiple environments; otherwise, check them in:
46
- # Gemfile.lock
46
+ Gemfile.lock
47
47
  # .ruby-version
48
48
  # .ruby-gemset
49
49
 
@@ -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", "> 4.2", "< 6"
21
+ spec.add_dependency "activerecord", "> 6.0", "< 6.1"
22
22
  spec.add_dependency "activesupport"
23
23
 
24
24
  spec.add_development_dependency "bundler"
data/lib/jit_preloader.rb CHANGED
@@ -8,16 +8,17 @@ 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("5.2.2")
11
+ if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("6.0.0")
12
+ 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
+ else
12
16
  require 'jit_preloader/active_record/associations/preloader/collection_association'
13
17
  require 'jit_preloader/active_record/associations/preloader/singular_association'
14
- else
15
- require 'jit_preloader/active_record/associations/preloader/association'
16
18
  end
17
19
  require 'jit_preloader/preloader'
18
20
 
19
21
  module JitPreloader
20
-
21
22
  def self.globally_enabled=(value)
22
23
  @enabled = value
23
24
  end
@@ -4,7 +4,7 @@ module JitPreloader
4
4
  def load_target
5
5
  was_loaded = loaded?
6
6
 
7
- if !loaded? && owner.persisted? && owner.jit_preloader
7
+ if !loaded? && owner.persisted? && owner.jit_preloader && (reflection.scope.nil? || reflection.scope.arity == 0)
8
8
  owner.jit_preloader.jit_preload(reflection.name)
9
9
  end
10
10
 
@@ -18,20 +18,13 @@ module JitPreloader
18
18
  # end
19
19
 
20
20
  def run(preloader)
21
- all_records = []
22
- records = load_records do |record|
23
- owner = owners_by_key[convert_key(record[association_key_name])]
24
- association = owner.association(reflection.name)
25
- association.set_inverse_instance(record)
26
- end
21
+ return unless (reflection.scope.nil? || reflection.scope.arity == 0) && klass.ancestors.include?(ActiveRecord::Base)
27
22
 
28
- owners.each do |owner|
29
- owned_records = records[convert_key(owner[owner_key_name])] || []
30
- all_records.concat(Array(owned_records)) if owner.jit_preloader || JitPreloader.globally_enabled?
31
- associate_records_to_owner(owner, owned_records)
23
+ super.tap do
24
+ if preloaded_records.any? && preloaded_records.none?(&:jit_preloader)
25
+ JitPreloader::Preloader.attach(preloaded_records) if owners.any?(&:jit_preloader) || JitPreloader.globally_enabled?
26
+ end
32
27
  end
33
-
34
- JitPreloader::Preloader.attach(all_records) if all_records.any?
35
28
  end
36
29
 
37
30
  # Original method:
@@ -54,6 +47,7 @@ module JitPreloader
54
47
  # the original copy so that we don't blow away in-memory changes.
55
48
  new_records = association.target.any? ? records - association.target : records
56
49
  association.target.concat(new_records)
50
+ association.loaded!
57
51
  else
58
52
  association.target ||= records.first unless records.empty?
59
53
  end
@@ -69,3 +63,4 @@ module JitPreloader
69
63
  end
70
64
 
71
65
  ActiveRecord::Associations::Preloader::Association.prepend(JitPreloader::PreloaderAssociation)
66
+ ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(JitPreloader::PreloaderAssociation)
@@ -0,0 +1,57 @@
1
+ module JitPreloader
2
+ module PreloaderAssociation
3
+
4
+ # A monkey patch to ActiveRecord. The old method looked like the snippet
5
+ # below. Our changes here are that we remove records that are already
6
+ # part of the target, then attach all of the records to a new jit preloader.
7
+ #
8
+ # def run
9
+ # records = records_by_owner
10
+
11
+ # owners.each do |owner|
12
+ # associate_records_to_owner(owner, records[owner] || [])
13
+ # end if @associate
14
+
15
+ # self
16
+ # end
17
+
18
+ def run
19
+ return unless (reflection.scope.nil? || reflection.scope.arity == 0) && klass.ancestors.include?(ActiveRecord::Base)
20
+
21
+ super.tap do
22
+ if preloaded_records.any? && preloaded_records.none?(&:jit_preloader)
23
+ JitPreloader::Preloader.attach(preloaded_records) if owners.any?(&:jit_preloader) || JitPreloader.globally_enabled?
24
+ end
25
+ end
26
+ end
27
+
28
+ # Original method:
29
+ # def associate_records_to_owner(owner, records)
30
+ # association = owner.association(reflection.name)
31
+ # if reflection.collection?
32
+ # association.target = records
33
+ # else
34
+ # association.target = records.first
35
+ # end
36
+ # end
37
+ def associate_records_to_owner(owner, records)
38
+ association = owner.association(reflection.name)
39
+ if reflection.collection?
40
+ new_records = association.target.any? ? records - association.target : records
41
+ association.target.concat(new_records)
42
+ association.loaded!
43
+ else
44
+ association.target = records.first
45
+ end
46
+ end
47
+
48
+ def build_scope
49
+ super.tap do |scope|
50
+ scope.jit_preload! if owners.any?(&:jit_preloader) || JitPreloader.globally_enabled?
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ ActiveRecord::Associations::Preloader::Association.prepend(JitPreloader::PreloaderAssociation)
57
+ ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(JitPreloader::PreloaderAssociation)
@@ -4,7 +4,7 @@ module JitPreloader
4
4
  def load_target
5
5
  was_loaded = loaded?
6
6
 
7
- if !loaded? && owner.persisted? && owner.jit_preloader
7
+ if !loaded? && owner.persisted? && owner.jit_preloader && (reflection.scope.nil? || reflection.scope.arity == 0)
8
8
  owner.jit_preloader.jit_preload(reflection.name)
9
9
  end
10
10
 
@@ -18,37 +18,59 @@ module JitPreloadExtension
18
18
  end
19
19
  end
20
20
 
21
- def preload_scoped_relation(name:, base_association:, preload_scope: nil)
22
- return jit_preload_scoped_relations[name] if jit_preload_scoped_relations&.key?(name)
21
+ if Gem::Version.new(ActiveRecord::VERSION::STRING) >= Gem::Version.new("6.0.0")
22
+ def preload_scoped_relation(name:, base_association:, preload_scope: nil)
23
+ return jit_preload_scoped_relations[name] if jit_preload_scoped_relations&.key?(name)
24
+
25
+ records = jit_preloader&.records || [self]
26
+
27
+ preloader_association = ActiveRecord::Associations::Preloader.new.preload(
28
+ records,
29
+ base_association,
30
+ preload_scope
31
+ ).first
32
+
33
+ records.each do |record|
34
+ record.jit_preload_scoped_relations ||= {}
35
+ association = record.association(base_association)
36
+ record.jit_preload_scoped_relations[name] = preloader_association.records_by_owner[record] || []
37
+ end
38
+
39
+ jit_preload_scoped_relations[name]
40
+ end
41
+ else
42
+ def preload_scoped_relation(name:, base_association:, preload_scope: nil)
43
+ return jit_preload_scoped_relations[name] if jit_preload_scoped_relations&.key?(name)
44
+
45
+ records = jit_preloader&.records || [self]
46
+ previous_association_values = {}
47
+
48
+ records.each do |record|
49
+ association = record.association(base_association)
50
+ if association.loaded?
51
+ previous_association_values[record] = association.target
52
+ association.reset
53
+ end
54
+ end
23
55
 
24
- records = jit_preloader&.records || [self]
25
- previous_association_values = {}
56
+ ActiveRecord::Associations::Preloader.new.preload(
57
+ records,
58
+ base_association,
59
+ preload_scope
60
+ )
26
61
 
27
- records.each do |record|
28
- association = record.association(base_association)
29
- if association.loaded?
30
- previous_association_values[record] = association.target
62
+ records.each do |record|
63
+ record.jit_preload_scoped_relations ||= {}
64
+ association = record.association(base_association)
65
+ record.jit_preload_scoped_relations[name] = association.target
31
66
  association.reset
67
+ if previous_association_values.key?(record)
68
+ association.target = previous_association_values[record]
69
+ end
32
70
  end
33
- end
34
71
 
35
- ActiveRecord::Associations::Preloader.new.preload(
36
- records,
37
- base_association,
38
- preload_scope
39
- )
40
-
41
- records.each do |record|
42
- record.jit_preload_scoped_relations ||= {}
43
- association = record.association(base_association)
44
- record.jit_preload_scoped_relations[name] = association.target
45
- association.reset
46
- if previous_association_values.key?(record)
47
- association.target = previous_association_values[record]
48
- end
72
+ jit_preload_scoped_relations[name]
49
73
  end
50
-
51
- jit_preload_scoped_relations[name]
52
74
  end
53
75
 
54
76
  def self.prepended(base)
@@ -1,3 +1,3 @@
1
1
  module JitPreloader
2
- VERSION = "0.2.5"
2
+ VERSION = "1.0.3"
3
3
  end
@@ -90,5 +90,18 @@ RSpec.describe "ActiveRecord::Base Extensions" do
90
90
  expect(contacts.last.association(:addresses)).to be_loaded
91
91
  end
92
92
  end
93
+
94
+ context "when no records exist for the association" do
95
+ let!(:record) { Parent.create }
96
+
97
+ it "returns an empty array" do
98
+ value = record.preload_scoped_relation(
99
+ name: "Parent Children",
100
+ base_association: :parents_child
101
+ )
102
+
103
+ expect(value).to eq([])
104
+ end
105
+ end
93
106
  end
94
107
  end
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ require "spec_helper"
2
2
 
3
3
  RSpec.describe JitPreloader::Preloader do
4
4
  let!(:contact1) do
@@ -154,6 +154,16 @@ RSpec.describe JitPreloader::Preloader do
154
154
  expect(c.contacts_count).to eql contact_owner_counts[i]
155
155
  end
156
156
  end
157
+
158
+ context "when a record has a polymorphic association type that's not an ActiveRecord" do
159
+ before do
160
+ contact1.update!(contact_owner_type: "NilClass", contact_owner_id: nil)
161
+ end
162
+
163
+ it "doesn't die while trying to load the association" do
164
+ expect(Contact.jit_preload.map(&:contact_owner)).to eq [nil, ContactOwner.first, Address.first]
165
+ end
166
+ end
157
167
  end
158
168
  end
159
169
 
@@ -192,6 +202,22 @@ RSpec.describe JitPreloader::Preloader do
192
202
  end
193
203
  end
194
204
 
205
+ context "when accessing an association with a scope that has a parameter" do
206
+ let!(:contact_book) { ContactBook.create(name: "The Yellow Pages") }
207
+ let!(:contact) { Contact.create(name: "Contact", contact_book: contact_book) }
208
+ let!(:company1) { Company.create(name: "Company1", contact_book: contact_book) }
209
+
210
+ it "is unable to be preloaded" do
211
+ ActiveSupport::Notifications.subscribed(callback, "n_plus_one_query") do
212
+ ContactBook.all.jit_preload.each do |contact_book|
213
+ expect(contact_book.contacts_with_scope.to_a).to eql [company1, contact]
214
+ end
215
+ end
216
+
217
+ expect(source_map).to eql(Hash[contact_book, [:contacts_with_scope]])
218
+ end
219
+ end
220
+
195
221
  context "when preloading an aggregate on a polymorphic has_many through relationship" do
196
222
  let(:contact_owner_addresses_counts) { [3] }
197
223
 
@@ -1,5 +1,6 @@
1
1
  class ContactBook < ActiveRecord::Base
2
2
  has_many :contacts
3
+ has_many :contacts_with_scope, ->(_contact_book) { desc }, class_name: "Contact", foreign_key: :contact_book_id
3
4
  has_many :employees, through: :contacts
4
5
 
5
6
  has_many :companies
@@ -29,6 +30,8 @@ class Contact < ActiveRecord::Base
29
30
  has_many_aggregate :addresses, :max_street_length, :maximum, "LENGTH(street)"
30
31
  has_many_aggregate :phone_numbers, :count, :count, "id"
31
32
  has_many_aggregate :addresses, :count, :count, "*"
33
+
34
+ scope :desc, ->{ order(id: :desc) }
32
35
  end
33
36
 
34
37
  class Company < Contact
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jit_preloader
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyle d'Oliveira
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-07 00:00:00.000000000 Z
11
+ date: 2021-04-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">"
18
18
  - !ruby/object:Gem::Version
19
- version: '4.2'
19
+ version: '6.0'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '6'
22
+ version: '6.1'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">"
28
28
  - !ruby/object:Gem::Version
29
- version: '4.2'
29
+ version: '6.0'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '6'
32
+ version: '6.1'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: activesupport
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -154,14 +154,14 @@ files:
154
154
  - ".gitignore"
155
155
  - ".rspec"
156
156
  - Gemfile
157
- - Gemfile.lock
158
157
  - LICENSE
159
158
  - README.md
160
159
  - Rakefile
161
160
  - jit_preloader.gemspec
162
161
  - lib/jit_preloader.rb
163
162
  - lib/jit_preloader/active_record/associations/collection_association.rb
164
- - lib/jit_preloader/active_record/associations/preloader/association.rb
163
+ - lib/jit_preloader/active_record/associations/preloader/ar5_association.rb
164
+ - lib/jit_preloader/active_record/associations/preloader/ar6_association.rb
165
165
  - lib/jit_preloader/active_record/associations/preloader/collection_association.rb
166
166
  - lib/jit_preloader/active_record/associations/preloader/singular_association.rb
167
167
  - lib/jit_preloader/active_record/associations/singular_association.rb
@@ -178,7 +178,7 @@ homepage: ''
178
178
  licenses:
179
179
  - MIT
180
180
  metadata: {}
181
- post_install_message:
181
+ post_install_message:
182
182
  rdoc_options: []
183
183
  require_paths:
184
184
  - lib
@@ -193,9 +193,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
193
  - !ruby/object:Gem::Version
194
194
  version: '0'
195
195
  requirements: []
196
- rubyforge_project:
197
- rubygems_version: 2.6.14
198
- signing_key:
196
+ rubygems_version: 3.2.3
197
+ signing_key:
199
198
  specification_version: 4
200
199
  summary: Tool to understand N+1 queries and to remove them
201
200
  test_files:
data/Gemfile.lock DELETED
@@ -1,66 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- jit_preloader (0.2.5)
5
- activerecord (> 4.2, < 6)
6
- activesupport
7
-
8
- GEM
9
- remote: https://rubygems.org/
10
- specs:
11
- activemodel (5.2.4.2)
12
- activesupport (= 5.2.4.2)
13
- activerecord (5.2.4.2)
14
- activemodel (= 5.2.4.2)
15
- activesupport (= 5.2.4.2)
16
- arel (>= 9.0)
17
- activesupport (5.2.4.2)
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 (9.0.6)
24
- concurrent-ruby (1.1.6)
25
- database_cleaner (1.5.3)
26
- db-query-matchers (0.10.0)
27
- activesupport (>= 4.0, < 7)
28
- rspec (~> 3.0)
29
- diff-lcs (1.2.5)
30
- i18n (1.8.2)
31
- concurrent-ruby (~> 1.0)
32
- minitest (5.14.0)
33
- rake (13.0.1)
34
- rspec (3.5.0)
35
- rspec-core (~> 3.5.0)
36
- rspec-expectations (~> 3.5.0)
37
- rspec-mocks (~> 3.5.0)
38
- rspec-core (3.5.4)
39
- rspec-support (~> 3.5.0)
40
- rspec-expectations (3.5.0)
41
- diff-lcs (>= 1.2.0, < 2.0)
42
- rspec-support (~> 3.5.0)
43
- rspec-mocks (3.5.0)
44
- diff-lcs (>= 1.2.0, < 2.0)
45
- rspec-support (~> 3.5.0)
46
- rspec-support (3.5.0)
47
- sqlite3 (1.3.12)
48
- thread_safe (0.3.6)
49
- tzinfo (1.2.6)
50
- thread_safe (~> 0.1)
51
-
52
- PLATFORMS
53
- ruby
54
-
55
- DEPENDENCIES
56
- bundler
57
- byebug
58
- database_cleaner
59
- db-query-matchers
60
- jit_preloader!
61
- rake (~> 13.0)
62
- rspec
63
- sqlite3
64
-
65
- BUNDLED WITH
66
- 2.1.4