bullet 6.1.3 → 7.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +82 -0
  3. data/CHANGELOG.md +22 -0
  4. data/Gemfile.rails-7.0 +10 -0
  5. data/README.md +12 -10
  6. data/lib/bullet/active_record41.rb +1 -0
  7. data/lib/bullet/active_record42.rb +1 -0
  8. data/lib/bullet/active_record52.rb +11 -17
  9. data/lib/bullet/active_record60.rb +11 -17
  10. data/lib/bullet/active_record61.rb +11 -17
  11. data/lib/bullet/active_record70.rb +275 -0
  12. data/lib/bullet/bullet_xhr.js +1 -0
  13. data/lib/bullet/dependency.rb +10 -0
  14. data/lib/bullet/detector/base.rb +2 -1
  15. data/lib/bullet/detector/counter_cache.rb +1 -1
  16. data/lib/bullet/detector/n_plus_one_query.rb +3 -3
  17. data/lib/bullet/detector/unused_eager_loading.rb +1 -1
  18. data/lib/bullet/mongoid7x.rb +26 -9
  19. data/lib/bullet/notification.rb +2 -1
  20. data/lib/bullet/rack.rb +6 -3
  21. data/lib/bullet/stack_trace_filter.rb +7 -8
  22. data/lib/bullet/version.rb +1 -1
  23. data/lib/bullet.rb +23 -24
  24. data/perf/benchmark.rb +4 -1
  25. data/spec/bullet/detector/unused_eager_loading_spec.rb +6 -2
  26. data/spec/bullet/ext/object_spec.rb +1 -1
  27. data/spec/bullet/rack_spec.rb +90 -13
  28. data/spec/bullet_spec.rb +15 -15
  29. data/spec/integration/active_record/association_spec.rb +27 -8
  30. data/spec/integration/counter_cache_spec.rb +4 -4
  31. data/spec/integration/mongoid/association_spec.rb +1 -1
  32. data/spec/models/deal.rb +5 -0
  33. data/spec/models/folder.rb +2 -1
  34. data/spec/models/group.rb +2 -1
  35. data/spec/models/page.rb +2 -1
  36. data/spec/models/post.rb +2 -0
  37. data/spec/models/role.rb +7 -0
  38. data/spec/models/user.rb +1 -0
  39. data/spec/models/writer.rb +2 -1
  40. data/spec/spec_helper.rb +0 -2
  41. data/spec/support/mongo_seed.rb +1 -0
  42. data/spec/support/sqlite_seed.rb +30 -0
  43. data/test.sh +2 -0
  44. metadata +10 -4
  45. data/.travis.yml +0 -33
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b574ae38dafed75dc9aded2d6c97ab057593fcda5a49ec4875bc327f99625efd
4
- data.tar.gz: df882c895e98b91a26371b670ffd439a2a39e80df57ce1b61a87de6e89c10c1a
3
+ metadata.gz: 823bcc1af2be934ec4f10df71ea2951223834d3146e9c323afada99ba4ae4254
4
+ data.tar.gz: 4cda048b45bd219d62bee22e0041027561c943d283ee262ea71686a15e6e4345
5
5
  SHA512:
6
- metadata.gz: 475691616eca228e0938effbc8a9a8930d8ab33f673d4cb2a75ff02b2933ba01ee2a570fff1d9d669700fb05cf09dff9149555f1f2b35c4a31c8904620db70e3
7
- data.tar.gz: 86f37317790084aa7ca3860c1757a41354678a1a0bfbf56fe6f21ebe6793eef5ff257fe75bbad77b999484a9e1abb527d9e9ac877dda04cf9f1236e110e6d561
6
+ metadata.gz: 9a2398eb23da7201bb8ab3fb1dbea3d48089d6c51d4a0e473d25009841aab41c68a5b0c27fb6ac6b77084e2465a836a5b848ffda63a2ea3b457a9571b04d428f
7
+ data.tar.gz: ab3debd03dc9cafbbd8ef9237f747c67d14bf3d8bf116b7dbed4e145176cf3f10045862b32641036e9bb00caca6eee841c75f9b3defad83ca681d0d70a5c6f0d
@@ -0,0 +1,82 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: CI
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ jobs:
17
+ test_rails_4:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ matrix:
21
+ gemfile: ['Gemfile.rails-4.0', 'Gemfile.rails-4.1', 'Gemfile.rails-4.2']
22
+ env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
23
+ BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
24
+ steps:
25
+ - uses: actions/checkout@v2
26
+ - name: Set up Ruby
27
+ uses: ruby/setup-ruby@v1
28
+ with:
29
+ ruby-version: 2.3
30
+ bundler: 1
31
+ bundler-cache: true
32
+ - name: Run tests
33
+ run: bundle exec rake
34
+ test_rails_5:
35
+ runs-on: ubuntu-latest
36
+ strategy:
37
+ matrix:
38
+ gemfile: ['Gemfile.rails-5.0', 'Gemfile.rails-5.1', 'Gemfile.rails-5.2']
39
+ env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
40
+ BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
41
+ steps:
42
+ - uses: actions/checkout@v2
43
+ - name: Set up Ruby
44
+ uses: ruby/setup-ruby@v1
45
+ with:
46
+ ruby-version: 2.5
47
+ bundler: 1
48
+ bundler-cache: true
49
+ - name: Run tests
50
+ run: bundle exec rake
51
+ test_rails_6:
52
+ runs-on: ubuntu-latest
53
+ strategy:
54
+ matrix:
55
+ gemfile: ['Gemfile.rails-6.0', 'Gemfile.rails-6.1']
56
+ env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
57
+ BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
58
+ steps:
59
+ - uses: actions/checkout@v2
60
+ - name: Set up Ruby
61
+ uses: ruby/setup-ruby@v1
62
+ with:
63
+ ruby-version: 2.7
64
+ bundler-cache: true
65
+ - name: Run tests
66
+ run: bundle exec rake
67
+ test_rails_7:
68
+ runs-on: ubuntu-latest
69
+ strategy:
70
+ matrix:
71
+ gemfile: ['Gemfile.rails-7.0']
72
+ env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
73
+ BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
74
+ steps:
75
+ - uses: actions/checkout@v2
76
+ - name: Set up Ruby
77
+ uses: ruby/setup-ruby@v1
78
+ with:
79
+ ruby-version: 3.1
80
+ bundler-cache: true
81
+ - name: Run tests
82
+ run: bundle exec rake
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  ## Next Release
2
2
 
3
+ ## 7.0.1 (01/15/2022)
4
+
5
+ * Get rid of *_whitelist methods
6
+ * Hack ActiveRecord::Associations::Preloader::Batch in rails 7
7
+
8
+ ## 7.0.0 (12/18/2021)
9
+
10
+ * Support rails 7
11
+ * Fix Mongoid 7 view iteration
12
+ * Move CI from Travis to Github Actions
13
+
14
+ ## 6.1.5 (08/16/2021)
15
+
16
+ * Rename whitelist to safelist
17
+ * Fix onload called twice
18
+ * Support Rack::Files::Iterator responses
19
+ * Ensure HABTM associations are not incorrectly labeled n+1
20
+
21
+ ## 6.1.4 (02/26/2021)
22
+
23
+ * Added an option to stop adding HTTP headers to API requests
24
+
3
25
  ## 6.1.3 (01/21/2021)
4
26
 
5
27
  * Consider ThroughAssociation at SingularAssociation like CollectionAssociation
data/Gemfile.rails-7.0 ADDED
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'rails', '~> 7.0.0'
6
+ gem 'sqlite3'
7
+ gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
8
+ gem 'activerecord-import'
9
+
10
+ gem "rspec"
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Bullet
2
2
 
3
+ ![Main workflow](https://github.com/flyerhzm/bullet/actions/workflows/main.yml/badge.svg)
3
4
  [![Gem Version](https://badge.fury.io/rb/bullet.svg)](http://badge.fury.io/rb/bullet)
4
- [![Build Status](https://secure.travis-ci.org/flyerhzm/bullet.svg)](http://travis-ci.org/flyerhzm/bullet)
5
5
  [![AwesomeCode Status for flyerhzm/bullet](https://awesomecode.io/projects/6755235b-e2c1-459e-bf92-b8b13d0c0472/status)](https://awesomecode.io/repos/flyerhzm/bullet)
6
6
  [![Coderwall Endorse](http://api.coderwall.com/flyerhzm/endorsecount.png)](http://coderwall.com/flyerhzm)
7
7
 
@@ -67,6 +67,7 @@ config.after_initialize do
67
67
  Bullet.rails_logger = true
68
68
  Bullet.honeybadger = true
69
69
  Bullet.bugsnag = true
70
+ Bullet.appsignal = true
70
71
  Bullet.airbrake = true
71
72
  Bullet.rollbar = true
72
73
  Bullet.add_footer = true
@@ -90,10 +91,12 @@ The code above will enable all of the Bullet notification systems:
90
91
  * `Bullet.honeybadger`: add notifications to Honeybadger
91
92
  * `Bullet.bugsnag`: add notifications to bugsnag
92
93
  * `Bullet.airbrake`: add notifications to airbrake
94
+ * `Bullet.appsignal`: add notifications to AppSignal
93
95
  * `Bullet.rollbar`: add notifications to rollbar
94
96
  * `Bullet.sentry`: add notifications to sentry
95
97
  * `Bullet.add_footer`: adds the details in the bottom left corner of the page. Double click the footer or use close button to hide footer.
96
- * `Bullet.skip_html_injection`: prevents Bullet from injecting XHR into the returned HTML. This must be false for receiving alerts or console logging.
98
+ * `Bullet.skip_html_injection`: prevents Bullet from injecting code into the returned HTML. This must be false for receiving alerts, showing the footer or console logging.
99
+ * `Bullet.skip_http_headers`: don't add headers to API requests, and remove the javascript that relies on them. Note that this prevents bullet from logging warnings to the browser console or updating the footer.
97
100
  * `Bullet.stacktrace_includes`: include paths with any of these substrings in the stack trace, even if they are not in your main app
98
101
  * `Bullet.stacktrace_excludes`: ignore paths with any of these substrings in the stack trace, even if they are not in your main app.
99
102
  Each item can be a string (match substring), a regex, or an array where the first item is a path to match, and the second
@@ -118,15 +121,15 @@ Bullet.unused_eager_loading_enable = false
118
121
  Bullet.counter_cache_enable = false
119
122
  ```
120
123
 
121
- ## Whitelist
124
+ ## Safe list
122
125
 
123
126
  Sometimes Bullet may notify you of query problems you don't care to fix, or
124
- which come from outside your code. You can whitelist these to ignore them:
127
+ which come from outside your code. You can add them to a safe list to ignore them:
125
128
 
126
129
  ```ruby
127
- Bullet.add_whitelist :type => :n_plus_one_query, :class_name => "Post", :association => :comments
128
- Bullet.add_whitelist :type => :unused_eager_loading, :class_name => "Post", :association => :comments
129
- Bullet.add_whitelist :type => :counter_cache, :class_name => "Country", :association => :cities
130
+ Bullet.add_safelist :type => :n_plus_one_query, :class_name => "Post", :association => :comments
131
+ Bullet.add_safelist :type => :unused_eager_loading, :class_name => "Post", :association => :comments
132
+ Bullet.add_safelist :type => :counter_cache, :class_name => "Country", :association => :cities
130
133
  ```
131
134
 
132
135
  If you want to skip bullet in some specific controller actions, you can
@@ -267,8 +270,7 @@ Bullet outputs some details info, to enable debug mode, set
267
270
  ## Demo
268
271
 
269
272
  Bullet is designed to function as you browse through your application in development. To see it in action,
270
- you can visit [https://github.com/flyerhzm/bullet_test](https://github.com/flyerhzm/bullet_test) or
271
- follow these steps to create, detect, and fix example query problems.
273
+ you can follow these steps to create, detect, and fix example query problems.
272
274
 
273
275
  1\. Create an example application
274
276
 
@@ -479,4 +481,4 @@ Meanwhile, there's a line appended to `log/bullet.log`
479
481
  Post => [:comments]
480
482
  ```
481
483
 
482
- Copyright (c) 2009 - 2019 Richard Huang (flyerhzm@gmail.com), released under the MIT license
484
+ Copyright (c) 2009 - 2022 Richard Huang (flyerhzm@gmail.com), released under the MIT license
@@ -30,6 +30,7 @@ module Bullet
30
30
 
31
31
  ::ActiveRecord::Relation.class_eval do
32
32
  alias_method :origin_to_a, :to_a
33
+
33
34
  # if select a collection of objects, then these objects have possible to cause N+1 query.
34
35
  # if select only one object, then the only one object has impossible to cause N+1 query.
35
36
  def to_a
@@ -52,6 +52,7 @@ module Bullet
52
52
 
53
53
  ::ActiveRecord::Relation.class_eval do
54
54
  alias_method :origin_to_a, :to_a
55
+
55
56
  # if select a collection of objects, then these objects have possible to cause N+1 query.
56
57
  # if select only one object, then the only one object has impossible to cause N+1 query.
57
58
  def to_a
@@ -75,23 +75,6 @@ module Bullet
75
75
  end
76
76
  )
77
77
 
78
- ::ActiveRecord::FinderMethods.prepend(
79
- Module.new do
80
- # add includes in scope
81
- def find_with_associations
82
- return super { |r| yield r } if block_given?
83
-
84
- records = super
85
- if Bullet.start?
86
- associations = (eager_load_values + includes_values).uniq
87
- records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
88
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
89
- end
90
- records
91
- end
92
- end
93
- )
94
-
95
78
  ::ActiveRecord::Associations::JoinDependency.prepend(
96
79
  Module.new do
97
80
  def instantiate(result_set, &block)
@@ -149,6 +132,17 @@ module Bullet
149
132
  end
150
133
  )
151
134
 
135
+ ::ActiveRecord::Associations::Association.prepend(
136
+ Module.new do
137
+ def inversed_from(record)
138
+ if Bullet.start?
139
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
140
+ end
141
+ super
142
+ end
143
+ end
144
+ )
145
+
152
146
  ::ActiveRecord::Associations::CollectionAssociation.prepend(
153
147
  Module.new do
154
148
  def load_target
@@ -102,23 +102,6 @@ module Bullet
102
102
  end
103
103
  )
104
104
 
105
- ::ActiveRecord::FinderMethods.prepend(
106
- Module.new do
107
- # add includes in scope
108
- def find_with_associations
109
- return super { |r| yield r } if block_given?
110
-
111
- records = super
112
- if Bullet.start?
113
- associations = (eager_load_values + includes_values).uniq
114
- records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
115
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
116
- end
117
- records
118
- end
119
- end
120
- )
121
-
122
105
  ::ActiveRecord::Associations::JoinDependency.prepend(
123
106
  Module.new do
124
107
  def instantiate(result_set, &block)
@@ -176,6 +159,17 @@ module Bullet
176
159
  end
177
160
  )
178
161
 
162
+ ::ActiveRecord::Associations::Association.prepend(
163
+ Module.new do
164
+ def inversed_from(record)
165
+ if Bullet.start?
166
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
167
+ end
168
+ super
169
+ end
170
+ end
171
+ )
172
+
179
173
  ::ActiveRecord::Associations::CollectionAssociation.prepend(
180
174
  Module.new do
181
175
  def load_target
@@ -102,23 +102,6 @@ module Bullet
102
102
  end
103
103
  )
104
104
 
105
- ::ActiveRecord::FinderMethods.prepend(
106
- Module.new do
107
- # add includes in scope
108
- def find_with_associations
109
- return super { |r| yield r } if block_given?
110
-
111
- records = super
112
- if Bullet.start?
113
- associations = (eager_load_values + includes_values).uniq
114
- records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
115
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
116
- end
117
- records
118
- end
119
- end
120
- )
121
-
122
105
  ::ActiveRecord::Associations::JoinDependency.prepend(
123
106
  Module.new do
124
107
  def instantiate(result_set, strict_loading_value, &block)
@@ -176,6 +159,17 @@ module Bullet
176
159
  end
177
160
  )
178
161
 
162
+ ::ActiveRecord::Associations::Association.prepend(
163
+ Module.new do
164
+ def inversed_from(record)
165
+ if Bullet.start?
166
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
167
+ end
168
+ super
169
+ end
170
+ end
171
+ )
172
+
179
173
  ::ActiveRecord::Associations::CollectionAssociation.prepend(
180
174
  Module.new do
181
175
  def load_target
@@ -0,0 +1,275 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bullet
4
+ module SaveWithBulletSupport
5
+ def _create_record(*)
6
+ super do
7
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
8
+ yield(self) if block_given?
9
+ end
10
+ end
11
+ end
12
+
13
+ module ActiveRecord
14
+ def self.enable
15
+ require 'active_record'
16
+ ::ActiveRecord::Base.extend(
17
+ Module.new do
18
+ def find_by_sql(sql, binds = [], preparable: nil, &block)
19
+ result = super
20
+ if Bullet.start?
21
+ if result.is_a? Array
22
+ if result.size > 1
23
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
24
+ Bullet::Detector::CounterCache.add_possible_objects(result)
25
+ elsif result.size == 1
26
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
27
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
28
+ end
29
+ elsif result.is_a? ::ActiveRecord::Base
30
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
31
+ Bullet::Detector::CounterCache.add_impossible_object(result)
32
+ end
33
+ end
34
+ result
35
+ end
36
+ end
37
+ )
38
+
39
+ ::ActiveRecord::Base.prepend(SaveWithBulletSupport)
40
+
41
+ ::ActiveRecord::Relation.prepend(
42
+ Module.new do
43
+ # if select a collection of objects, then these objects have possible to cause N+1 query.
44
+ # if select only one object, then the only one object has impossible to cause N+1 query.
45
+ def records
46
+ result = super
47
+ if Bullet.start?
48
+ if result.first.class.name !~ /^HABTM_/
49
+ if result.size > 1
50
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
51
+ Bullet::Detector::CounterCache.add_possible_objects(result)
52
+ elsif result.size == 1
53
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
54
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
55
+ end
56
+ end
57
+ end
58
+ result
59
+ end
60
+ end
61
+ )
62
+
63
+ ::ActiveRecord::Associations::Preloader::Batch.prepend(
64
+ Module.new do
65
+ def call
66
+ if Bullet.start?
67
+ @preloaders.each do |preloader|
68
+ preloader.records.each { |record| Bullet::Detector::Association.add_object_associations(record, preloader.associations) }
69
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(preloader.records, preloader.associations)
70
+ end
71
+ end
72
+ super
73
+ end
74
+ end
75
+ )
76
+
77
+ ::ActiveRecord::Associations::Preloader::Branch.prepend(
78
+ Module.new do
79
+ def preloaders_for_reflection(reflection, reflection_records)
80
+ if Bullet.start?
81
+ reflection_records.compact!
82
+ if reflection_records.first.class.name !~ /^HABTM_/
83
+ reflection_records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }
84
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)
85
+ end
86
+ end
87
+ super
88
+ end
89
+ end
90
+ )
91
+
92
+ ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
93
+ Module.new do
94
+ def preloaded_records
95
+ if Bullet.start? && !defined?(@preloaded_records)
96
+ source_preloaders.each do |source_preloader|
97
+ reflection_name = source_preloader.send(:reflection).name
98
+ source_preloader.send(:owners).each do |owner|
99
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
100
+ end
101
+ end
102
+ end
103
+ super
104
+ end
105
+ end
106
+ )
107
+
108
+ ::ActiveRecord::Associations::JoinDependency.prepend(
109
+ Module.new do
110
+ def instantiate(result_set, strict_loading_value, &block)
111
+ @bullet_eager_loadings = {}
112
+ records = super
113
+
114
+ if Bullet.start?
115
+ @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
116
+ objects = eager_loadings_hash.keys
117
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
118
+ objects,
119
+ eager_loadings_hash[objects.first].to_a
120
+ )
121
+ end
122
+ end
123
+ records
124
+ end
125
+
126
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
127
+ if Bullet.start?
128
+ unless ar_parent.nil?
129
+ parent.children.each do |node|
130
+ key = aliases.column_alias(node, node.primary_key)
131
+ id = row[key]
132
+ next unless id.nil?
133
+
134
+ associations = node.reflection.name
135
+ Bullet::Detector::Association.add_object_associations(ar_parent, associations)
136
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
137
+ @bullet_eager_loadings[ar_parent.class] ||= {}
138
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
139
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << associations
140
+ end
141
+ end
142
+ end
143
+
144
+ super
145
+ end
146
+
147
+ # call join associations
148
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
149
+ result = super
150
+
151
+ if Bullet.start?
152
+ associations = node.reflection.name
153
+ Bullet::Detector::Association.add_object_associations(record, associations)
154
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
155
+ @bullet_eager_loadings[record.class] ||= {}
156
+ @bullet_eager_loadings[record.class][record] ||= Set.new
157
+ @bullet_eager_loadings[record.class][record] << associations
158
+ end
159
+
160
+ result
161
+ end
162
+ end
163
+ )
164
+
165
+ ::ActiveRecord::Associations::Association.prepend(
166
+ Module.new do
167
+ def inversed_from(record)
168
+ if Bullet.start?
169
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
170
+ end
171
+ super
172
+ end
173
+ end
174
+ )
175
+
176
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
177
+ Module.new do
178
+ def load_target
179
+ records = super
180
+
181
+ if Bullet.start?
182
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
183
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
184
+ association = owner.association(reflection.through_reflection.name)
185
+ Array(association.target).each do |through_record|
186
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
187
+ end
188
+
189
+ if reflection.through_reflection != through_reflection
190
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
191
+ end
192
+ end
193
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
194
+ if records.first.class.name !~ /^HABTM_/
195
+ if records.size > 1
196
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
197
+ Bullet::Detector::CounterCache.add_possible_objects(records)
198
+ elsif records.size == 1
199
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
200
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
201
+ end
202
+ end
203
+ end
204
+ records
205
+ end
206
+
207
+ def empty?
208
+ if Bullet.start? && !reflection.has_cached_counter?
209
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
210
+ end
211
+ super
212
+ end
213
+
214
+ def include?(object)
215
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
216
+ super
217
+ end
218
+ end
219
+ )
220
+
221
+ ::ActiveRecord::Associations::SingularAssociation.prepend(
222
+ Module.new do
223
+ # call has_one and belongs_to associations
224
+ def reader
225
+ result = super
226
+
227
+ if Bullet.start?
228
+ if owner.class.name !~ /^HABTM_/
229
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
230
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
231
+ association = owner.association(reflection.through_reflection.name)
232
+ Array(association.target).each do |through_record|
233
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
234
+ end
235
+
236
+ if reflection.through_reflection != through_reflection
237
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
238
+ end
239
+ end
240
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
241
+
242
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
243
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
244
+ else
245
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
246
+ end
247
+ end
248
+ end
249
+ result
250
+ end
251
+ end
252
+ )
253
+
254
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(
255
+ Module.new do
256
+ def empty?
257
+ result = super
258
+ if Bullet.start? && !reflection.has_cached_counter?
259
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
260
+ end
261
+ result
262
+ end
263
+
264
+ def count_records
265
+ result = reflection.has_cached_counter?
266
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
267
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
268
+ end
269
+ super
270
+ end
271
+ end
272
+ )
273
+ end
274
+ end
275
+ end
@@ -20,6 +20,7 @@
20
20
  if (this.onload) {
21
21
  this._storedOnload = this.onload;
22
22
  }
23
+ this.onload = null
23
24
  this.addEventListener("load", bulletXHROnload);
24
25
  return Reflect.apply(oldSend, this, arguments);
25
26
  }
@@ -29,6 +29,8 @@ module Bullet
29
29
  'active_record60'
30
30
  elsif active_record61?
31
31
  'active_record61'
32
+ elsif active_record70?
33
+ 'active_record70'
32
34
  else
33
35
  raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
34
36
  end
@@ -64,6 +66,10 @@ module Bullet
64
66
  active_record? && ::ActiveRecord::VERSION::MAJOR == 6
65
67
  end
66
68
 
69
+ def active_record7?
70
+ active_record? && ::ActiveRecord::VERSION::MAJOR == 7
71
+ end
72
+
67
73
  def active_record40?
68
74
  active_record4? && ::ActiveRecord::VERSION::MINOR == 0
69
75
  end
@@ -96,6 +102,10 @@ module Bullet
96
102
  active_record6? && ::ActiveRecord::VERSION::MINOR == 1
97
103
  end
98
104
 
105
+ def active_record70?
106
+ active_record7? && ::ActiveRecord::VERSION::MINOR == 0
107
+ end
108
+
99
109
  def mongoid4x?
100
110
  mongoid? && ::Mongoid::VERSION =~ /\A4/
101
111
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Bullet
4
4
  module Detector
5
- class Base; end
5
+ class Base
6
+ end
6
7
  end
7
8
  end
@@ -54,7 +54,7 @@ module Bullet
54
54
  private
55
55
 
56
56
  def create_notification(klazz, associations)
57
- notify_associations = Array(associations) - Bullet.get_whitelist_associations(:counter_cache, klazz)
57
+ notify_associations = Array(associations) - Bullet.get_safelist_associations(:counter_cache, klazz)
58
58
 
59
59
  if notify_associations.present?
60
60
  notice = Bullet::Notification::CounterCache.new klazz, notify_associations