bullet 6.1.0 → 7.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +82 -0
  3. data/CHANGELOG.md +31 -0
  4. data/Gemfile.rails-6.0 +1 -1
  5. data/Gemfile.rails-6.1 +15 -0
  6. data/Gemfile.rails-7.0 +10 -0
  7. data/README.md +17 -7
  8. data/lib/bullet/active_job.rb +5 -1
  9. data/lib/bullet/active_record41.rb +1 -0
  10. data/lib/bullet/active_record42.rb +1 -0
  11. data/lib/bullet/active_record52.rb +22 -17
  12. data/lib/bullet/active_record60.rb +22 -17
  13. data/lib/bullet/active_record61.rb +272 -0
  14. data/lib/bullet/active_record70.rb +261 -0
  15. data/lib/bullet/bullet_xhr.js +18 -17
  16. data/lib/bullet/dependency.rb +16 -0
  17. data/lib/bullet/detector/base.rb +2 -1
  18. data/lib/bullet/detector/counter_cache.rb +1 -1
  19. data/lib/bullet/detector/n_plus_one_query.rb +3 -3
  20. data/lib/bullet/detector/unused_eager_loading.rb +1 -1
  21. data/lib/bullet/mongoid4x.rb +1 -1
  22. data/lib/bullet/mongoid5x.rb +1 -1
  23. data/lib/bullet/mongoid6x.rb +1 -1
  24. data/lib/bullet/mongoid7x.rb +24 -7
  25. data/lib/bullet/notification.rb +2 -1
  26. data/lib/bullet/rack.rb +26 -18
  27. data/lib/bullet/stack_trace_filter.rb +7 -9
  28. data/lib/bullet/version.rb +1 -1
  29. data/lib/bullet.rb +69 -23
  30. data/lib/generators/bullet/install_generator.rb +23 -25
  31. data/perf/benchmark.rb +4 -1
  32. data/spec/bullet/detector/counter_cache_spec.rb +1 -1
  33. data/spec/bullet/detector/unused_eager_loading_spec.rb +10 -10
  34. data/spec/bullet/ext/object_spec.rb +1 -1
  35. data/spec/bullet/notification/n_plus_one_query_spec.rb +1 -3
  36. data/spec/bullet/rack_spec.rb +130 -3
  37. data/spec/bullet_spec.rb +39 -10
  38. data/spec/integration/active_record/association_spec.rb +54 -10
  39. data/spec/integration/counter_cache_spec.rb +4 -4
  40. data/spec/integration/mongoid/association_spec.rb +4 -4
  41. data/spec/models/attachment.rb +5 -0
  42. data/spec/models/deal.rb +5 -0
  43. data/spec/models/folder.rb +2 -1
  44. data/spec/models/group.rb +2 -1
  45. data/spec/models/page.rb +2 -1
  46. data/spec/models/post.rb +2 -0
  47. data/spec/models/submission.rb +1 -0
  48. data/spec/models/user.rb +1 -0
  49. data/spec/models/writer.rb +2 -1
  50. data/spec/spec_helper.rb +0 -2
  51. data/spec/support/mongo_seed.rb +1 -0
  52. data/spec/support/sqlite_seed.rb +20 -0
  53. data/test.sh +2 -0
  54. metadata +15 -7
  55. data/.travis.yml +0 -33
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd668fdfd675ea8437eee0242974d7cb1cfacc0479be4cc5f3f58ee7d05a6d63
4
- data.tar.gz: 3d841fb8471fe18b66135fa087ee3d1874f412c114d09ba05ba150e2daaf9775
3
+ metadata.gz: 6fbdfd6fe2f412c6632b877352fdf912b56fd54e255101dc16ae414a218fc2ee
4
+ data.tar.gz: 0a765e969ccae1ef685051350324052225fea00e80d7d37a6b8cce6d8ad1f28e
5
5
  SHA512:
6
- metadata.gz: 714c614f2cf44f332f4f9996638c10dca565c92a2279316509256fefba2ca824fc9c71d2a3c3e4ca28edfc45849f76b61e521158e0051c365f5b8a81f41e9d8d
7
- data.tar.gz: a3b1a7e58b6e5554a641aa4b8edf16dbb4983b2f7a072c5b7275ee04a7251e4d095fed37f82d2945ef759603c8aeaf8a394d9e7fcf9f70a723e6235ab5f3e646
6
+ metadata.gz: 35c561cc6534f355a56d1aa0e880bb51941c126ce8a9c704bbc8c0f59c67cf5890a00d650919d5a458a913f4d6a125b17e57c92130d48baa9b6a90db190db763
7
+ data.tar.gz: 4f29d4316862401ad1d5d4edfdf21f6f8045a37e21acc4d12f3cdd80e528b12924a9f0a2ab5fdb366432ea5c8e656210887112fc338da8539a34586da88710e9
@@ -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.0
80
+ bundler-cache: true
81
+ - name: Run tests
82
+ run: bundle exec rake
data/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  ## Next Release
2
2
 
3
+ ## 7.0.0 (12/18/2021)
4
+
5
+ * Support rails 7
6
+ * Fix Mongoid 7 view iteration
7
+ * Move CI from Travis to Github Actions
8
+
9
+ ## 6.1.5 (08/16/2021)
10
+
11
+ * Rename whitelist to safelist
12
+ * Fix onload called twice
13
+ * Support Rack::Files::Iterator responses
14
+ * Ensure HABTM associations are not incorrectly labeled n+1
15
+
16
+ ## 6.1.4 (02/26/2021)
17
+
18
+ * Added an option to stop adding HTTP headers to API requests
19
+
20
+ ## 6.1.3 (01/21/2021)
21
+
22
+ * Consider ThroughAssociation at SingularAssociation like CollectionAssociation
23
+ * Add xhr_script only when add_footer is enabled
24
+
25
+ ## 6.1.2 (12/12/2020)
26
+
27
+ * Revert "Make whitelist thread safe"
28
+
29
+ ## 6.1.1 (12/12/2020)
30
+
31
+ * Add support Rails 6.1
32
+ * Make whitelist thread safe
33
+
3
34
  ## 6.1.0 (12/28/2019)
4
35
 
5
36
  * Add skip_html_injection flag
data/Gemfile.rails-6.0 CHANGED
@@ -2,7 +2,7 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'rails', '6.0.0'
5
+ gem 'rails', '~> 6.0.0'
6
6
  gem 'sqlite3'
7
7
  gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
8
8
  gem 'activerecord-import'
data/Gemfile.rails-6.1 ADDED
@@ -0,0 +1,15 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'rails', '~> 6.1.0'
6
+ gem 'sqlite3'
7
+ gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
8
+ gem 'activerecord-import'
9
+
10
+ gem "rspec"
11
+
12
+ platforms :rbx do
13
+ gem 'rubysl', '~> 2.0'
14
+ gem 'rubinius-developer_tools'
15
+ end
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
 
@@ -37,6 +37,13 @@ or add it into a Gemfile (Bundler):
37
37
  gem 'bullet', group: 'development'
38
38
  ```
39
39
 
40
+ enable the Bullet gem with generate command
41
+
42
+ ```ruby
43
+ bundle exec rails g bullet:install
44
+ ```
45
+ The generate command will auto generate the default configuration and may ask to include in the test environment as well. See below for custom configuration.
46
+
40
47
  **Note**: make sure `bullet` gem is added after activerecord (rails) and
41
48
  mongoid.
42
49
 
@@ -60,6 +67,7 @@ config.after_initialize do
60
67
  Bullet.rails_logger = true
61
68
  Bullet.honeybadger = true
62
69
  Bullet.bugsnag = true
70
+ Bullet.appsignal = true
63
71
  Bullet.airbrake = true
64
72
  Bullet.rollbar = true
65
73
  Bullet.add_footer = true
@@ -83,10 +91,12 @@ The code above will enable all of the Bullet notification systems:
83
91
  * `Bullet.honeybadger`: add notifications to Honeybadger
84
92
  * `Bullet.bugsnag`: add notifications to bugsnag
85
93
  * `Bullet.airbrake`: add notifications to airbrake
94
+ * `Bullet.appsignal`: add notifications to AppSignal
86
95
  * `Bullet.rollbar`: add notifications to rollbar
87
96
  * `Bullet.sentry`: add notifications to sentry
88
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.
89
- * `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.
90
100
  * `Bullet.stacktrace_includes`: include paths with any of these substrings in the stack trace, even if they are not in your main app
91
101
  * `Bullet.stacktrace_excludes`: ignore paths with any of these substrings in the stack trace, even if they are not in your main app.
92
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
@@ -111,15 +121,15 @@ Bullet.unused_eager_loading_enable = false
111
121
  Bullet.counter_cache_enable = false
112
122
  ```
113
123
 
114
- ## Whitelist
124
+ ## Safe list
115
125
 
116
126
  Sometimes Bullet may notify you of query problems you don't care to fix, or
117
- 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:
118
128
 
119
129
  ```ruby
120
- Bullet.add_whitelist :type => :n_plus_one_query, :class_name => "Post", :association => :comments
121
- Bullet.add_whitelist :type => :unused_eager_loading, :class_name => "Post", :association => :comments
122
- 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
123
133
  ```
124
134
 
125
135
  If you want to skip bullet in some specific controller actions, you can
@@ -3,7 +3,11 @@
3
3
  module Bullet
4
4
  module ActiveJob
5
5
  def self.included(base)
6
- base.class_eval { around_perform { |_job, block| Bullet.profile { block.call } } }
6
+ base.class_eval do
7
+ around_perform do |_job, block|
8
+ Bullet.profile { block.call }
9
+ end
10
+ end
7
11
  end
8
12
  end
9
13
  end
@@ -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
@@ -202,6 +196,17 @@ module Bullet
202
196
 
203
197
  if Bullet.start?
204
198
  if owner.class.name !~ /^HABTM_/ && !@inversed
199
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
200
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
201
+ association = owner.association reflection.through_reflection.name
202
+ Array(association.target).each do |through_record|
203
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
204
+ end
205
+
206
+ if reflection.through_reflection != through_reflection
207
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
208
+ end
209
+ end
205
210
  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
206
211
 
207
212
  if Bullet::Detector::NPlusOneQuery.impossible?(owner)
@@ -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
@@ -229,6 +223,17 @@ module Bullet
229
223
 
230
224
  if Bullet.start?
231
225
  if owner.class.name !~ /^HABTM_/ && !@inversed
226
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
227
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
228
+ association = owner.association(reflection.through_reflection.name)
229
+ Array(association.target).each do |through_record|
230
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
231
+ end
232
+
233
+ if reflection.through_reflection != through_reflection
234
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
235
+ end
236
+ end
232
237
  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
233
238
 
234
239
  if Bullet::Detector::NPlusOneQuery.impossible?(owner)
@@ -0,0 +1,272 @@
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.prepend(
64
+ Module.new do
65
+ def preloaders_for_one(association, records, scope, polymorphic_parent)
66
+ if Bullet.start?
67
+ records.compact!
68
+ if records.first.class.name !~ /^HABTM_/
69
+ records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }
70
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
71
+ end
72
+ end
73
+ super
74
+ end
75
+
76
+ def preloaders_for_reflection(reflection, records, scope)
77
+ if Bullet.start?
78
+ records.compact!
79
+ if records.first.class.name !~ /^HABTM_/
80
+ records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }
81
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, reflection.name)
82
+ end
83
+ end
84
+ super
85
+ end
86
+ end
87
+ )
88
+
89
+ ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
90
+ Module.new do
91
+ def preloaded_records
92
+ if Bullet.start? && !defined?(@preloaded_records)
93
+ source_preloaders.each do |source_preloader|
94
+ reflection_name = source_preloader.send(:reflection).name
95
+ source_preloader.send(:owners).each do |owner|
96
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
97
+ end
98
+ end
99
+ end
100
+ super
101
+ end
102
+ end
103
+ )
104
+
105
+ ::ActiveRecord::Associations::JoinDependency.prepend(
106
+ Module.new do
107
+ def instantiate(result_set, strict_loading_value, &block)
108
+ @bullet_eager_loadings = {}
109
+ records = super
110
+
111
+ if Bullet.start?
112
+ @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
113
+ objects = eager_loadings_hash.keys
114
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
115
+ objects,
116
+ eager_loadings_hash[objects.first].to_a
117
+ )
118
+ end
119
+ end
120
+ records
121
+ end
122
+
123
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
124
+ if Bullet.start?
125
+ unless ar_parent.nil?
126
+ parent.children.each do |node|
127
+ key = aliases.column_alias(node, node.primary_key)
128
+ id = row[key]
129
+ next unless id.nil?
130
+
131
+ associations = node.reflection.name
132
+ Bullet::Detector::Association.add_object_associations(ar_parent, associations)
133
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
134
+ @bullet_eager_loadings[ar_parent.class] ||= {}
135
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
136
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << associations
137
+ end
138
+ end
139
+ end
140
+
141
+ super
142
+ end
143
+
144
+ # call join associations
145
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
146
+ result = super
147
+
148
+ if Bullet.start?
149
+ associations = node.reflection.name
150
+ Bullet::Detector::Association.add_object_associations(record, associations)
151
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
152
+ @bullet_eager_loadings[record.class] ||= {}
153
+ @bullet_eager_loadings[record.class][record] ||= Set.new
154
+ @bullet_eager_loadings[record.class][record] << associations
155
+ end
156
+
157
+ result
158
+ end
159
+ end
160
+ )
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
+
173
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
174
+ Module.new do
175
+ def load_target
176
+ records = super
177
+
178
+ if Bullet.start?
179
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
180
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
181
+ association = owner.association(reflection.through_reflection.name)
182
+ Array(association.target).each do |through_record|
183
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
184
+ end
185
+
186
+ if reflection.through_reflection != through_reflection
187
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
188
+ end
189
+ end
190
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
191
+ if records.first.class.name !~ /^HABTM_/
192
+ if records.size > 1
193
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
194
+ Bullet::Detector::CounterCache.add_possible_objects(records)
195
+ elsif records.size == 1
196
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
197
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
198
+ end
199
+ end
200
+ end
201
+ records
202
+ end
203
+
204
+ def empty?
205
+ if Bullet.start? && !reflection.has_cached_counter?
206
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
207
+ end
208
+ super
209
+ end
210
+
211
+ def include?(object)
212
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
213
+ super
214
+ end
215
+ end
216
+ )
217
+
218
+ ::ActiveRecord::Associations::SingularAssociation.prepend(
219
+ Module.new do
220
+ # call has_one and belongs_to associations
221
+ def target
222
+ result = super()
223
+
224
+ if Bullet.start?
225
+ if owner.class.name !~ /^HABTM_/ && !@inversed
226
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
227
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
228
+ association = owner.association(reflection.through_reflection.name)
229
+ Array(association.target).each do |through_record|
230
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
231
+ end
232
+
233
+ if reflection.through_reflection != through_reflection
234
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
235
+ end
236
+ end
237
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
238
+
239
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
240
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
241
+ else
242
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
243
+ end
244
+ end
245
+ end
246
+ result
247
+ end
248
+ end
249
+ )
250
+
251
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(
252
+ Module.new do
253
+ def empty?
254
+ result = super
255
+ if Bullet.start? && !reflection.has_cached_counter?
256
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
257
+ end
258
+ result
259
+ end
260
+
261
+ def count_records
262
+ result = reflection.has_cached_counter?
263
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
264
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
265
+ end
266
+ super
267
+ end
268
+ end
269
+ )
270
+ end
271
+ end
272
+ end