bullet 6.1.1 → 6.1.5
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/main.yml +66 -0
- data/CHANGELOG.md +20 -0
- data/README.md +10 -7
- data/lib/bullet.rb +68 -21
- data/lib/bullet/active_record41.rb +1 -0
- data/lib/bullet/active_record42.rb +1 -0
- data/lib/bullet/active_record52.rb +11 -0
- data/lib/bullet/active_record60.rb +11 -0
- data/lib/bullet/active_record61.rb +11 -0
- data/lib/bullet/bullet_xhr.js +1 -0
- data/lib/bullet/detector/base.rb +2 -1
- data/lib/bullet/detector/counter_cache.rb +1 -1
- data/lib/bullet/detector/n_plus_one_query.rb +3 -3
- data/lib/bullet/detector/unused_eager_loading.rb +1 -1
- data/lib/bullet/mongoid4x.rb +1 -1
- data/lib/bullet/mongoid5x.rb +1 -1
- data/lib/bullet/mongoid6x.rb +1 -1
- data/lib/bullet/mongoid7x.rb +1 -1
- data/lib/bullet/notification.rb +2 -1
- data/lib/bullet/rack.rb +5 -3
- data/lib/bullet/stack_trace_filter.rb +7 -9
- data/lib/bullet/version.rb +1 -1
- data/perf/benchmark.rb +4 -1
- data/spec/bullet/detector/unused_eager_loading_spec.rb +6 -2
- data/spec/bullet/ext/object_spec.rb +1 -1
- data/spec/bullet/rack_spec.rb +88 -17
- data/spec/bullet_spec.rb +39 -10
- data/spec/integration/active_record/association_spec.rb +53 -8
- data/spec/integration/counter_cache_spec.rb +3 -3
- data/spec/integration/mongoid/association_spec.rb +1 -1
- data/spec/models/attachment.rb +5 -0
- data/spec/models/deal.rb +5 -0
- data/spec/models/folder.rb +2 -1
- data/spec/models/group.rb +2 -1
- data/spec/models/page.rb +2 -1
- data/spec/models/post.rb +2 -0
- data/spec/models/submission.rb +1 -0
- data/spec/models/user.rb +1 -0
- data/spec/models/writer.rb +2 -1
- data/spec/spec_helper.rb +0 -2
- data/spec/support/mongo_seed.rb +1 -0
- data/spec/support/sqlite_seed.rb +20 -0
- data/test.sh +1 -0
- metadata +8 -4
- data/.travis.yml +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b21c2c4ca3caf6c41961a7182a5c8d37f6b89aa78b8dfb7badfb940f11a23191
|
4
|
+
data.tar.gz: 0f432034f9b4cb2fe6c6572481c797aefcfa69373839fbd0985a7efd2a0bbea9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2457987f11f034fa457030cf7161d7508fb26230cf9e88a2571c54a934ad9a1330b48dd3d38eede3033bd7ef6c920e564dcbfa43a15354bf72d14ed799aefe2
|
7
|
+
data.tar.gz: ed6690fcb3bd778d6d5a27538a0ee78d298c749be1e445c305b9884448ca13f97e5a62c76ac48229a250041bb09ba609d6b154b12cf42a0f3dca66fbb9c83021
|
@@ -0,0 +1,66 @@
|
|
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
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,25 @@
|
|
1
1
|
## Next Release
|
2
2
|
|
3
|
+
## 6.1.5 (08/16/2021)
|
4
|
+
|
5
|
+
* Rename whitelist to safelist
|
6
|
+
* Fix onload called twice
|
7
|
+
* Support Rack::Files::Iterator responses
|
8
|
+
* Ensure HABTM associations are not incorrectly labeled n+1
|
9
|
+
|
10
|
+
## 6.1.4 (02/26/2021)
|
11
|
+
|
12
|
+
* Added an option to stop adding HTTP headers to API requests
|
13
|
+
|
14
|
+
## 6.1.3 (01/21/2021)
|
15
|
+
|
16
|
+
* Consider ThroughAssociation at SingularAssociation like CollectionAssociation
|
17
|
+
* Add xhr_script only when add_footer is enabled
|
18
|
+
|
19
|
+
## 6.1.2 (12/12/2020)
|
20
|
+
|
21
|
+
* Revert "Make whitelist thread safe"
|
22
|
+
|
3
23
|
## 6.1.1 (12/12/2020)
|
4
24
|
|
5
25
|
* Add support Rails 6.1
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Bullet
|
2
2
|
|
3
|
+

|
3
4
|
[](http://badge.fury.io/rb/bullet)
|
4
|
-
[](http://travis-ci.org/flyerhzm/bullet)
|
5
5
|
[](https://awesomecode.io/repos/flyerhzm/bullet)
|
6
6
|
[](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
|
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
|
-
##
|
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
|
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.
|
128
|
-
Bullet.
|
129
|
-
Bullet.
|
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
|
data/lib/bullet.rb
CHANGED
@@ -20,9 +20,6 @@ module Bullet
|
|
20
20
|
autoload :Registry, 'bullet/registry'
|
21
21
|
autoload :NotificationCollector, 'bullet/notification_collector'
|
22
22
|
|
23
|
-
BULLET_DEBUG = 'BULLET_DEBUG'
|
24
|
-
TRUE = 'true'
|
25
|
-
|
26
23
|
if defined?(Rails::Railtie)
|
27
24
|
class BulletRailtie < Rails::Railtie
|
28
25
|
initializer 'bullet.configure_rails_initialization' do |app|
|
@@ -38,9 +35,11 @@ module Bullet
|
|
38
35
|
:stacktrace_includes,
|
39
36
|
:stacktrace_excludes,
|
40
37
|
:skip_html_injection
|
41
|
-
|
38
|
+
attr_reader :safelist
|
39
|
+
attr_accessor :add_footer, :orm_patches_applied, :skip_http_headers
|
42
40
|
|
43
|
-
available_notifiers =
|
41
|
+
available_notifiers =
|
42
|
+
UniformNotifier::AVAILABLE_NOTIFIERS.select { |notifier| notifier != :raise }.map { |notifier| "#{notifier}=" }
|
44
43
|
available_notifiers_options = { to: UniformNotifier }
|
45
44
|
delegate(*available_notifiers, **available_notifiers_options)
|
46
45
|
|
@@ -58,7 +57,7 @@ module Bullet
|
|
58
57
|
@enable = @n_plus_one_query_enable = @unused_eager_loading_enable = @counter_cache_enable = enable
|
59
58
|
|
60
59
|
if enable?
|
61
|
-
|
60
|
+
reset_safelist
|
62
61
|
unless orm_patches_applied
|
63
62
|
self.orm_patches_applied = true
|
64
63
|
Bullet::Mongoid.enable if mongoid?
|
@@ -71,8 +70,9 @@ module Bullet
|
|
71
70
|
!!@enable
|
72
71
|
end
|
73
72
|
|
73
|
+
# Rails.root might be nil if `railties` is a dependency on a project that does not use Rails
|
74
74
|
def app_root
|
75
|
-
(defined?(::Rails.root) ? Rails.root.to_s : Dir.pwd).to_s
|
75
|
+
@app_root ||= (defined?(::Rails.root) && !::Rails.root.nil? ? Rails.root.to_s : Dir.pwd).to_s
|
76
76
|
end
|
77
77
|
|
78
78
|
def n_plus_one_query_enable?
|
@@ -88,36 +88,81 @@ module Bullet
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def stacktrace_includes
|
91
|
-
@stacktrace_includes
|
91
|
+
@stacktrace_includes ||= []
|
92
92
|
end
|
93
93
|
|
94
94
|
def stacktrace_excludes
|
95
|
-
@stacktrace_excludes
|
95
|
+
@stacktrace_excludes ||= []
|
96
|
+
end
|
97
|
+
|
98
|
+
def add_safelist(options)
|
99
|
+
reset_safelist
|
100
|
+
@safelist[options[:type]][options[:class_name]] ||= []
|
101
|
+
@safelist[options[:type]][options[:class_name]] << options[:association].to_sym
|
102
|
+
end
|
103
|
+
|
104
|
+
def delete_safelist(options)
|
105
|
+
reset_safelist
|
106
|
+
@safelist[options[:type]][options[:class_name]] ||= []
|
107
|
+
@safelist[options[:type]][options[:class_name]].delete(options[:association].to_sym)
|
108
|
+
@safelist[options[:type]].delete_if { |_key, val| val.empty? }
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_safelist_associations(type, class_name)
|
112
|
+
Array(@safelist[type][class_name])
|
113
|
+
end
|
114
|
+
|
115
|
+
def reset_safelist
|
116
|
+
@safelist ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }
|
117
|
+
end
|
118
|
+
|
119
|
+
def clear_safelist
|
120
|
+
@safelist = nil
|
96
121
|
end
|
97
122
|
|
98
123
|
def add_whitelist(options)
|
99
|
-
|
100
|
-
|
101
|
-
|
124
|
+
ActiveSupport::Deprecation.warn(<<~WARN.strip
|
125
|
+
add_whitelist is deprecated in favor of add_safelist. It will be removed from the next major release.
|
126
|
+
WARN
|
127
|
+
)
|
128
|
+
|
129
|
+
add_safelist(options)
|
102
130
|
end
|
103
131
|
|
104
132
|
def delete_whitelist(options)
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
133
|
+
ActiveSupport::Deprecation.warn(<<~WARN.strip
|
134
|
+
delete_whitelist is deprecated in favor of delete_safelist. It will be removed from the next major release.
|
135
|
+
WARN
|
136
|
+
)
|
137
|
+
|
138
|
+
delete_safelist(options)
|
109
139
|
end
|
110
140
|
|
111
141
|
def get_whitelist_associations(type, class_name)
|
112
|
-
|
142
|
+
ActiveSupport::Deprecation.warn(<<~WARN.strip
|
143
|
+
get_whitelist_associations is deprecated in favor of get_safelist_associations. It will be removed from the next major release.
|
144
|
+
WARN
|
145
|
+
)
|
146
|
+
|
147
|
+
get_safelist_associations(type, class_name)
|
113
148
|
end
|
114
149
|
|
115
150
|
def reset_whitelist
|
116
|
-
|
151
|
+
ActiveSupport::Deprecation.warn(<<~WARN.strip
|
152
|
+
reset_whitelist is deprecated in favor of reset_safelist. It will be removed from the next major release.
|
153
|
+
WARN
|
154
|
+
)
|
155
|
+
|
156
|
+
reset_safelist
|
117
157
|
end
|
118
158
|
|
119
159
|
def clear_whitelist
|
120
|
-
|
160
|
+
ActiveSupport::Deprecation.warn(<<~WARN.strip
|
161
|
+
clear_whitelist is deprecated in favor of clear_safelist. It will be removed from the next major release.
|
162
|
+
WARN
|
163
|
+
)
|
164
|
+
|
165
|
+
clear_safelist
|
121
166
|
end
|
122
167
|
|
123
168
|
def bullet_logger=(active)
|
@@ -131,7 +176,7 @@ module Bullet
|
|
131
176
|
end
|
132
177
|
|
133
178
|
def debug(title, message)
|
134
|
-
puts "[Bullet][#{title}] #{message}" if ENV[BULLET_DEBUG] ==
|
179
|
+
puts "[Bullet][#{title}] #{message}" if ENV['BULLET_DEBUG'] == 'true'
|
135
180
|
end
|
136
181
|
|
137
182
|
def start_request
|
@@ -240,7 +285,9 @@ module Bullet
|
|
240
285
|
end
|
241
286
|
|
242
287
|
def inject_into_page?
|
243
|
-
|
288
|
+
return false if defined?(@skip_html_injection) && @skip_html_injection
|
289
|
+
|
290
|
+
console_enabled? || add_footer
|
244
291
|
end
|
245
292
|
|
246
293
|
private
|
@@ -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
|
@@ -202,6 +202,17 @@ module Bullet
|
|
202
202
|
|
203
203
|
if Bullet.start?
|
204
204
|
if owner.class.name !~ /^HABTM_/ && !@inversed
|
205
|
+
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
206
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
207
|
+
association = owner.association reflection.through_reflection.name
|
208
|
+
Array(association.target).each do |through_record|
|
209
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
210
|
+
end
|
211
|
+
|
212
|
+
if reflection.through_reflection != through_reflection
|
213
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
214
|
+
end
|
215
|
+
end
|
205
216
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
206
217
|
|
207
218
|
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
|
@@ -229,6 +229,17 @@ module Bullet
|
|
229
229
|
|
230
230
|
if Bullet.start?
|
231
231
|
if owner.class.name !~ /^HABTM_/ && !@inversed
|
232
|
+
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
233
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
234
|
+
association = owner.association(reflection.through_reflection.name)
|
235
|
+
Array(association.target).each do |through_record|
|
236
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
237
|
+
end
|
238
|
+
|
239
|
+
if reflection.through_reflection != through_reflection
|
240
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
241
|
+
end
|
242
|
+
end
|
232
243
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
233
244
|
|
234
245
|
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
|
@@ -229,6 +229,17 @@ module Bullet
|
|
229
229
|
|
230
230
|
if Bullet.start?
|
231
231
|
if owner.class.name !~ /^HABTM_/ && !@inversed
|
232
|
+
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
233
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
234
|
+
association = owner.association(reflection.through_reflection.name)
|
235
|
+
Array(association.target).each do |through_record|
|
236
|
+
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
237
|
+
end
|
238
|
+
|
239
|
+
if reflection.through_reflection != through_reflection
|
240
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
241
|
+
end
|
242
|
+
end
|
232
243
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
|
233
244
|
|
234
245
|
if Bullet::Detector::NPlusOneQuery.impossible?(owner)
|
data/lib/bullet/bullet_xhr.js
CHANGED
data/lib/bullet/detector/base.rb
CHANGED
@@ -54,7 +54,7 @@ module Bullet
|
|
54
54
|
private
|
55
55
|
|
56
56
|
def create_notification(klazz, associations)
|
57
|
-
notify_associations = Array(associations) - Bullet.
|
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
|
@@ -35,6 +35,7 @@ module Bullet
|
|
35
35
|
|
36
36
|
objects = Array(object_or_objects)
|
37
37
|
return if objects.map(&:bullet_primary_key_value).compact.empty?
|
38
|
+
return if objects.all? { |obj| obj.class.name =~ /^HABTM_/ }
|
38
39
|
|
39
40
|
Bullet.debug(
|
40
41
|
'Detector::NPlusOneQuery#add_possible_objects',
|
@@ -84,8 +85,7 @@ module Bullet
|
|
84
85
|
# associations == v comparison order is important here because
|
85
86
|
# v variable might be a squeel node where :== method is redefined,
|
86
87
|
# so it does not compare values at all and return unexpected results
|
87
|
-
result =
|
88
|
-
v.is_a?(Hash) ? v.key?(associations) : associations == v
|
88
|
+
result = v.is_a?(Hash) ? v.key?(associations) : associations == v
|
89
89
|
return true if result
|
90
90
|
end
|
91
91
|
|
@@ -95,7 +95,7 @@ module Bullet
|
|
95
95
|
private
|
96
96
|
|
97
97
|
def create_notification(callers, klazz, associations)
|
98
|
-
notify_associations = Array(associations) - Bullet.
|
98
|
+
notify_associations = Array(associations) - Bullet.get_safelist_associations(:n_plus_one_query, klazz)
|
99
99
|
|
100
100
|
if notify_associations.present?
|
101
101
|
notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
|
@@ -65,7 +65,7 @@ module Bullet
|
|
65
65
|
private
|
66
66
|
|
67
67
|
def create_notification(callers, klazz, associations)
|
68
|
-
notify_associations = Array(associations) - Bullet.
|
68
|
+
notify_associations = Array(associations) - Bullet.get_safelist_associations(:unused_eager_loading, klazz)
|
69
69
|
|
70
70
|
if notify_associations.present?
|
71
71
|
notice = Bullet::Notification::UnusedEagerLoading.new(callers, klazz, notify_associations)
|
data/lib/bullet/mongoid4x.rb
CHANGED
data/lib/bullet/mongoid5x.rb
CHANGED
data/lib/bullet/mongoid6x.rb
CHANGED
data/lib/bullet/mongoid7x.rb
CHANGED
data/lib/bullet/notification.rb
CHANGED
data/lib/bullet/rack.rb
CHANGED
@@ -22,9 +22,11 @@ module Bullet
|
|
22
22
|
response_body = response_body(response)
|
23
23
|
response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
|
24
24
|
response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
|
25
|
-
|
25
|
+
if Bullet.add_footer && !Bullet.skip_http_headers
|
26
|
+
response_body = append_to_html_body(response_body, xhr_script)
|
27
|
+
end
|
26
28
|
headers['Content-Length'] = response_body.bytesize.to_s
|
27
|
-
|
29
|
+
elsif !Bullet.skip_http_headers
|
28
30
|
set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
|
29
31
|
set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
|
30
32
|
end
|
@@ -82,7 +84,7 @@ module Bullet
|
|
82
84
|
def response_body(response)
|
83
85
|
if response.respond_to?(:body)
|
84
86
|
Array === response.body ? response.body.first : response.body
|
85
|
-
|
87
|
+
elsif response.respond_to?(:first)
|
86
88
|
response.first
|
87
89
|
end
|
88
90
|
end
|
@@ -1,8 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require "bundler"
|
2
3
|
|
3
4
|
module Bullet
|
4
5
|
module StackTraceFilter
|
5
6
|
VENDOR_PATH = '/vendor'
|
7
|
+
IS_RUBY_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
|
6
8
|
|
7
9
|
def caller_in_project
|
8
10
|
vendor_root = Bullet.app_root + VENDOR_PATH
|
@@ -10,8 +12,9 @@ module Bullet
|
|
10
12
|
select_caller_locations do |location|
|
11
13
|
caller_path = location_as_path(location)
|
12
14
|
caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&
|
13
|
-
!caller_path.include?(bundler_path) ||
|
14
|
-
|
15
|
+
!caller_path.include?(bundler_path) || Bullet.stacktrace_includes.any? { |include_pattern|
|
16
|
+
pattern_matches?(location, include_pattern)
|
17
|
+
}
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
@@ -47,20 +50,15 @@ module Bullet
|
|
47
50
|
end
|
48
51
|
|
49
52
|
def location_as_path(location)
|
50
|
-
|
53
|
+
IS_RUBY_19 ? location : location.absolute_path.to_s
|
51
54
|
end
|
52
55
|
|
53
56
|
def select_caller_locations
|
54
|
-
if
|
57
|
+
if IS_RUBY_19
|
55
58
|
caller.select { |caller_path| yield caller_path }
|
56
59
|
else
|
57
60
|
caller_locations.select { |location| yield location }
|
58
61
|
end
|
59
62
|
end
|
60
|
-
|
61
|
-
def ruby_19?
|
62
|
-
@ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0') if @ruby_19.nil?
|
63
|
-
@ruby_19
|
64
|
-
end
|
65
63
|
end
|
66
64
|
end
|
data/lib/bullet/version.rb
CHANGED
data/perf/benchmark.rb
CHANGED
@@ -30,7 +30,10 @@ end
|
|
30
30
|
|
31
31
|
# create database bullet_benchmark;
|
32
32
|
ActiveRecord::Base.establish_connection(
|
33
|
-
adapter: 'mysql2',
|
33
|
+
adapter: 'mysql2',
|
34
|
+
database: 'bullet_benchmark',
|
35
|
+
server: '/tmp/mysql.socket',
|
36
|
+
username: 'root'
|
34
37
|
)
|
35
38
|
|
36
39
|
ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
|
@@ -19,7 +19,9 @@ module Bullet
|
|
19
19
|
it 'should get call associations if object and association are both in eager_loadings and call_object_associations' do
|
20
20
|
UnusedEagerLoading.add_eager_loadings([@post], :association)
|
21
21
|
UnusedEagerLoading.add_call_object_associations(@post, :association)
|
22
|
-
expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq(
|
22
|
+
expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq(
|
23
|
+
[:association]
|
24
|
+
)
|
23
25
|
end
|
24
26
|
|
25
27
|
it 'should not get call associations if not exist in call_object_associations' do
|
@@ -30,7 +32,9 @@ module Bullet
|
|
30
32
|
|
31
33
|
context '.diff_object_associations' do
|
32
34
|
it 'should return associations not exist in call_association' do
|
33
|
-
expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq(
|
35
|
+
expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq(
|
36
|
+
[:association]
|
37
|
+
)
|
34
38
|
end
|
35
39
|
|
36
40
|
it 'should return empty if associations exist in call_association' do
|
@@ -10,7 +10,7 @@ describe Object do
|
|
10
10
|
end
|
11
11
|
|
12
12
|
if mongoid?
|
13
|
-
it 'should return class with
|
13
|
+
it 'should return class with namespace and id composition' do
|
14
14
|
post = Mongoid::Post.first
|
15
15
|
expect(post.bullet_key).to eq("Mongoid::Post:#{post.id}")
|
16
16
|
end
|
data/spec/bullet/rack_spec.rb
CHANGED
@@ -69,7 +69,6 @@ module Bullet
|
|
69
69
|
expect(Bullet).to receive(:notification?).and_return(true)
|
70
70
|
expect(Bullet).to receive(:console_enabled?).and_return(true)
|
71
71
|
expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
|
72
|
-
expect(middleware).to receive(:xhr_script).and_return('')
|
73
72
|
expect(Bullet).to receive(:perform_out_of_channel_notifications)
|
74
73
|
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
75
74
|
expect(headers['Content-Length']).to eq('56')
|
@@ -84,37 +83,44 @@ module Bullet
|
|
84
83
|
allow(Bullet).to receive(:console_enabled?).and_return(true)
|
85
84
|
expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
|
86
85
|
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
87
|
-
expect(headers['Content-Length']).to eq(
|
86
|
+
expect(headers['Content-Length']).to eq('58')
|
88
87
|
end
|
89
88
|
|
90
89
|
context 'with injection notifiers' do
|
91
90
|
before do
|
92
91
|
expect(Bullet).to receive(:notification?).and_return(true)
|
93
92
|
allow(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
|
94
|
-
allow(middleware).to receive(:xhr_script).and_return('')
|
93
|
+
allow(middleware).to receive(:xhr_script).and_return('<script></script>')
|
95
94
|
allow(middleware).to receive(:footer_note).and_return('footer')
|
96
95
|
expect(Bullet).to receive(:perform_out_of_channel_notifications)
|
97
96
|
end
|
98
97
|
|
99
98
|
it 'should change response body if add_footer is true' do
|
100
|
-
expect(Bullet).to receive(:add_footer).
|
99
|
+
expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
|
101
100
|
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
102
101
|
|
103
|
-
expect(headers['Content-Length']).to eq((
|
104
|
-
expect(response
|
105
|
-
expect(response.first).to include('<bullet></bullet><')
|
102
|
+
expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
|
103
|
+
expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
|
106
104
|
end
|
107
105
|
|
108
106
|
it 'should change response body for html safe string if add_footer is true' do
|
109
|
-
expect(Bullet).to receive(:add_footer).
|
110
|
-
app.response =
|
111
|
-
|
112
|
-
|
107
|
+
expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
|
108
|
+
app.response =
|
109
|
+
Support::ResponseDouble.new.tap do |response|
|
110
|
+
response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
|
111
|
+
end
|
113
112
|
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
114
113
|
|
115
|
-
expect(headers['Content-Length']).to eq((
|
116
|
-
expect(response
|
117
|
-
|
114
|
+
expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
|
115
|
+
expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should add the footer-text header for non-html requests when add_footer is true' do
|
119
|
+
allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
|
120
|
+
allow(Bullet).to receive(:footer_info).and_return(['footer text'])
|
121
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
122
|
+
_, headers, _response = middleware.call({})
|
123
|
+
expect(headers).to include('X-bullet-footer-text' => '["footer text"]')
|
118
124
|
end
|
119
125
|
|
120
126
|
it 'should change response body if console_enabled is true' do
|
@@ -126,20 +132,71 @@ module Bullet
|
|
126
132
|
|
127
133
|
it 'should change response body for html safe string if console_enabled is true' do
|
128
134
|
expect(Bullet).to receive(:console_enabled?).and_return(true)
|
129
|
-
app.response =
|
130
|
-
|
131
|
-
|
135
|
+
app.response =
|
136
|
+
Support::ResponseDouble.new.tap do |response|
|
137
|
+
response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
|
138
|
+
end
|
132
139
|
_, headers, response = middleware.call('Content-Type' => 'text/html')
|
133
140
|
expect(headers['Content-Length']).to eq('56')
|
134
141
|
expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
|
135
142
|
end
|
136
143
|
|
144
|
+
it 'should add headers for non-html requests when console_enabled is true' do
|
145
|
+
allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
|
146
|
+
allow(Bullet).to receive(:text_notifications).and_return(['text notifications'])
|
147
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
148
|
+
_, headers, _response = middleware.call({})
|
149
|
+
expect(headers).to include('X-bullet-console-text' => '["text notifications"]')
|
150
|
+
end
|
151
|
+
|
137
152
|
it "shouldn't change response body unnecessarily" do
|
138
153
|
expected_response = Support::ResponseDouble.new 'Actual body'
|
139
154
|
app.response = expected_response
|
140
155
|
_, _, response = middleware.call({})
|
141
156
|
expect(response).to eq(expected_response)
|
142
157
|
end
|
158
|
+
|
159
|
+
it "shouldn't add headers unnecessarily" do
|
160
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
161
|
+
_, headers, _response = middleware.call({})
|
162
|
+
expect(headers).not_to include('X-bullet-footer-text')
|
163
|
+
expect(headers).not_to include('X-bullet-console-text')
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'when skip_http_headers is enabled' do
|
167
|
+
before do
|
168
|
+
allow(Bullet).to receive(:skip_http_headers).and_return(true)
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'should include the footer but not the xhr script tag if add_footer is true' do
|
172
|
+
expect(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
|
173
|
+
_, headers, response = middleware.call({})
|
174
|
+
|
175
|
+
expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
|
176
|
+
expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet></body></html>])
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'should not include the xhr script tag if console_enabled is true' do
|
180
|
+
expect(Bullet).to receive(:console_enabled?).and_return(true)
|
181
|
+
_, headers, response = middleware.call({})
|
182
|
+
expect(headers['Content-Length']).to eq('56')
|
183
|
+
expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'should not add the footer-text header for non-html requests when add_footer is true' do
|
187
|
+
allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
|
188
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
189
|
+
_, headers, _response = middleware.call({})
|
190
|
+
expect(headers).not_to include('X-bullet-footer-text')
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'should not add headers for non-html requests when console_enabled is true' do
|
194
|
+
allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
|
195
|
+
app.headers = { 'Content-Type' => 'application/json' }
|
196
|
+
_, headers, _response = middleware.call({})
|
197
|
+
expect(headers).not_to include('X-bullet-console-text')
|
198
|
+
end
|
199
|
+
end
|
143
200
|
end
|
144
201
|
|
145
202
|
context 'when skip_html_injection is enabled' do
|
@@ -204,6 +261,20 @@ module Bullet
|
|
204
261
|
expect(middleware.response_body(response)).to eq body_string
|
205
262
|
end
|
206
263
|
end
|
264
|
+
|
265
|
+
begin
|
266
|
+
require 'rack/files'
|
267
|
+
|
268
|
+
context 'when `response` is a Rack::Files::Iterator' do
|
269
|
+
let(:response) { instance_double(::Rack::Files::Iterator) }
|
270
|
+
before { allow(response).to receive(:is_a?).with(::Rack::Files::Iterator) { true } }
|
271
|
+
|
272
|
+
it 'should return nil' do
|
273
|
+
expect(middleware.response_body(response)).to be_nil
|
274
|
+
end
|
275
|
+
end
|
276
|
+
rescue LoadError
|
277
|
+
end
|
207
278
|
end
|
208
279
|
end
|
209
280
|
end
|
data/spec/bullet_spec.rb
CHANGED
@@ -74,31 +74,60 @@ describe Bullet, focused: true do
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
+
describe '#add_safelist' do
|
78
|
+
context "for 'special' class names" do
|
79
|
+
it 'is added to the safelist successfully' do
|
80
|
+
Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
81
|
+
expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
77
86
|
describe '#add_whitelist' do
|
78
87
|
context "for 'special' class names" do
|
79
|
-
it 'is added to the
|
88
|
+
it 'is added to the safelist successfully' do
|
80
89
|
Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
81
|
-
expect(Bullet.
|
90
|
+
expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#delete_safelist' do
|
96
|
+
context "for 'special' class names" do
|
97
|
+
it 'is deleted from the safelist successfully' do
|
98
|
+
Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
99
|
+
Bullet.delete_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
100
|
+
expect(Bullet.safelist[:n_plus_one_query]).to eq({})
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'when exists multiple definitions' do
|
105
|
+
it 'is deleted from the safelist successfully' do
|
106
|
+
Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
107
|
+
Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
|
108
|
+
Bullet.delete_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
|
109
|
+
expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
|
110
|
+
expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to_not include :team
|
82
111
|
end
|
83
112
|
end
|
84
113
|
end
|
85
114
|
|
86
115
|
describe '#delete_whitelist' do
|
87
116
|
context "for 'special' class names" do
|
88
|
-
it 'is deleted from the
|
89
|
-
Bullet.
|
117
|
+
it 'is deleted from the safelist successfully' do
|
118
|
+
Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
90
119
|
Bullet.delete_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
91
|
-
expect(
|
120
|
+
expect(Bullet.safelist[:n_plus_one_query]).to eq({})
|
92
121
|
end
|
93
122
|
end
|
94
123
|
|
95
124
|
context 'when exists multiple definitions' do
|
96
|
-
it 'is deleted from the
|
97
|
-
Bullet.
|
98
|
-
Bullet.
|
125
|
+
it 'is deleted from the safelist successfully' do
|
126
|
+
Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
|
127
|
+
Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
|
99
128
|
Bullet.delete_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
|
100
|
-
expect(Bullet.
|
101
|
-
expect(Bullet.
|
129
|
+
expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
|
130
|
+
expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to_not include :team
|
102
131
|
end
|
103
132
|
end
|
104
133
|
end
|
@@ -129,7 +129,7 @@ if active_record?
|
|
129
129
|
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
130
130
|
end
|
131
131
|
|
132
|
-
it 'should detect unused preload with post =>
|
132
|
+
it 'should detect unused preload with post => comments, no category => posts' do
|
133
133
|
Category.includes(posts: :comments).each { |category| category.posts.map(&:name) }
|
134
134
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
135
135
|
expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Post, :comments)
|
@@ -202,7 +202,7 @@ if active_record?
|
|
202
202
|
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
203
203
|
end
|
204
204
|
|
205
|
-
it 'should detect preload with post =>
|
205
|
+
it 'should detect preload with post => comments' do
|
206
206
|
Post.first.comments.map(&:name)
|
207
207
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
208
208
|
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
@@ -401,6 +401,15 @@ if active_record?
|
|
401
401
|
end
|
402
402
|
|
403
403
|
describe Bullet::Detector::Association, 'has_and_belongs_to_many' do
|
404
|
+
context 'posts <=> deals' do
|
405
|
+
it 'should detect preload associations with join tables that have identifier' do
|
406
|
+
Post.includes(:deals).each { |post| post.deals.map(&:name) }
|
407
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
408
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
409
|
+
|
410
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
411
|
+
end
|
412
|
+
end
|
404
413
|
context 'students <=> teachers' do
|
405
414
|
it 'should detect non preload associations' do
|
406
415
|
Student.all.each { |student| student.teachers.map(&:name) }
|
@@ -561,6 +570,42 @@ if active_record?
|
|
561
570
|
end
|
562
571
|
end
|
563
572
|
|
573
|
+
describe Bullet::Detector::Association, 'has_one :through' do
|
574
|
+
context 'user => attachment' do
|
575
|
+
it 'should detect non preload associations' do
|
576
|
+
User.all.each { |user| user.submission_attachment.file_name }
|
577
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
578
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
579
|
+
|
580
|
+
expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(User, :submission_attachment)
|
581
|
+
end
|
582
|
+
|
583
|
+
it 'should detect preload associations' do
|
584
|
+
User.includes(:submission_attachment).each { |user| user.submission_attachment.file_name }
|
585
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
586
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
587
|
+
|
588
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
589
|
+
end
|
590
|
+
|
591
|
+
it 'should not detect preload associations' do
|
592
|
+
User.all.map(&:name)
|
593
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
594
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
595
|
+
|
596
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
597
|
+
end
|
598
|
+
|
599
|
+
it 'should detect unused preload associations' do
|
600
|
+
User.includes(:submission_attachment).map(&:name)
|
601
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
602
|
+
expect(Bullet::Detector::Association).to be_unused_preload_associations_for(User, :submission_attachment)
|
603
|
+
|
604
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
605
|
+
end
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
564
609
|
describe Bullet::Detector::Association, 'call one association that in possible objects' do
|
565
610
|
it 'should not detect preload association' do
|
566
611
|
Post.all
|
@@ -693,9 +738,9 @@ if active_record?
|
|
693
738
|
end
|
694
739
|
end
|
695
740
|
|
696
|
-
context '
|
697
|
-
before { Bullet.
|
698
|
-
after { Bullet.
|
741
|
+
context 'add n plus one query to safelist' do
|
742
|
+
before { Bullet.add_safelist type: :n_plus_one_query, class_name: 'Post', association: :comments }
|
743
|
+
after { Bullet.clear_safelist }
|
699
744
|
|
700
745
|
it 'should not detect n plus one query' do
|
701
746
|
Post.all.each { |post| post.comments.map(&:name) }
|
@@ -714,9 +759,9 @@ if active_record?
|
|
714
759
|
end
|
715
760
|
end
|
716
761
|
|
717
|
-
context '
|
718
|
-
before { Bullet.
|
719
|
-
after { Bullet.
|
762
|
+
context 'add unused eager loading to safelist' do
|
763
|
+
before { Bullet.add_safelist type: :unused_eager_loading, class_name: 'Post', association: :comments }
|
764
|
+
after { Bullet.clear_safelist }
|
720
765
|
|
721
766
|
it 'should not detect unused eager loading' do
|
722
767
|
Post.includes(:comments).map(&:name)
|
@@ -55,9 +55,9 @@ if !mongoid? && active_record?
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
context '
|
59
|
-
before { Bullet.
|
60
|
-
after { Bullet.
|
58
|
+
context 'safelist' do
|
59
|
+
before { Bullet.add_safelist type: :counter_cache, class_name: 'Country', association: :cities }
|
60
|
+
after { Bullet.clear_safelist }
|
61
61
|
|
62
62
|
it 'should not detect counter cache' do
|
63
63
|
Country.all.each { |country| country.cities.size }
|
@@ -118,7 +118,7 @@ if mongoid?
|
|
118
118
|
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
119
119
|
end
|
120
120
|
|
121
|
-
it 'should detect preload with post =>
|
121
|
+
it 'should detect preload with post => comments' do
|
122
122
|
Mongoid::Post.first.comments.map(&:name)
|
123
123
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
124
124
|
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
data/spec/models/deal.rb
ADDED
data/spec/models/folder.rb
CHANGED
data/spec/models/group.rb
CHANGED
data/spec/models/page.rb
CHANGED
data/spec/models/post.rb
CHANGED
@@ -4,6 +4,7 @@ class Post < ActiveRecord::Base
|
|
4
4
|
belongs_to :category, inverse_of: :posts
|
5
5
|
belongs_to :writer
|
6
6
|
has_many :comments, inverse_of: :post
|
7
|
+
has_and_belongs_to_many :deals
|
7
8
|
|
8
9
|
validates :category, presence: true
|
9
10
|
|
@@ -21,6 +22,7 @@ class Post < ActiveRecord::Base
|
|
21
22
|
next unless trigger_after_save
|
22
23
|
|
23
24
|
temp_comment = Comment.new(post: self)
|
25
|
+
|
24
26
|
# this triggers self to be "possible", even though it's
|
25
27
|
# not saved yet
|
26
28
|
temp_comment.post
|
data/spec/models/submission.rb
CHANGED
data/spec/models/user.rb
CHANGED
data/spec/models/writer.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
data/spec/support/mongo_seed.rb
CHANGED
@@ -45,6 +45,7 @@ module Support
|
|
45
45
|
Mongoid.configure do |config|
|
46
46
|
config.load_configuration(clients: { default: { database: 'bullet', hosts: %w[localhost:27017] } })
|
47
47
|
end
|
48
|
+
|
48
49
|
# Increase the level from DEBUG in order to avoid excessive logging to the screen
|
49
50
|
Mongo::Logger.logger.level = Logger::WARN
|
50
51
|
end
|
data/spec/support/sqlite_seed.rb
CHANGED
@@ -21,6 +21,13 @@ module Support
|
|
21
21
|
post2 = category2.posts.create(name: 'second', writer: writer2)
|
22
22
|
post3 = category2.posts.create(name: 'third', writer: writer2)
|
23
23
|
|
24
|
+
deal1 = Deal.new(name: 'Deal 1')
|
25
|
+
deal1.posts << post1
|
26
|
+
deal1.posts << post2
|
27
|
+
deal2 = Deal.new(name: 'Deal 2')
|
28
|
+
post1.deals << deal1
|
29
|
+
post1.deals << deal2
|
30
|
+
|
24
31
|
comment1 = post1.comments.create(name: 'first', author: writer1)
|
25
32
|
comment2 = post1.comments.create(name: 'first2', author: writer1)
|
26
33
|
comment3 = post1.comments.create(name: 'first3', author: writer1)
|
@@ -95,6 +102,9 @@ module Support
|
|
95
102
|
submission1.replies.create(name: 'reply2')
|
96
103
|
submission2.replies.create(name: 'reply3')
|
97
104
|
submission2.replies.create(name: 'reply4')
|
105
|
+
|
106
|
+
submission1.create_attachment(file_name: 'submission1 file')
|
107
|
+
submission2.create_attachment(file_name: 'submission2 file')
|
98
108
|
end
|
99
109
|
|
100
110
|
def setup_db
|
@@ -153,6 +163,11 @@ module Support
|
|
153
163
|
t.column :hotel_id, :integer
|
154
164
|
end
|
155
165
|
|
166
|
+
create_table :deals_posts do |t|
|
167
|
+
t.column :deal_id, :integer
|
168
|
+
t.column :post_id, :integer
|
169
|
+
end
|
170
|
+
|
156
171
|
create_table :documents do |t|
|
157
172
|
t.string :name
|
158
173
|
t.string :type
|
@@ -240,6 +255,11 @@ module Support
|
|
240
255
|
t.column :name, :string
|
241
256
|
t.column :category_id, :integer
|
242
257
|
end
|
258
|
+
|
259
|
+
create_table :attachments do |t|
|
260
|
+
t.column :file_name, :string
|
261
|
+
t.column :submission_id, :integer
|
262
|
+
end
|
243
263
|
end
|
244
264
|
end
|
245
265
|
end
|
data/test.sh
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#bundle update rails && bundle exec rspec spec
|
2
2
|
#BUNDLE_GEMFILE=Gemfile.mongoid bundle update mongoid && BUNDLE_GEMFILE=Gemfile.mongoid bundle exec rspec spec
|
3
|
+
BUNDLE_GEMFILE=Gemfile.rails-6.1 bundle && BUNDLE_GEMFILE=Gemfile.rails-6.1 bundle exec rspec spec
|
3
4
|
BUNDLE_GEMFILE=Gemfile.rails-6.0 bundle && BUNDLE_GEMFILE=Gemfile.rails-6.0 bundle exec rspec spec
|
4
5
|
BUNDLE_GEMFILE=Gemfile.rails-5.2 bundle && BUNDLE_GEMFILE=Gemfile.rails-5.2 bundle exec rspec spec
|
5
6
|
BUNDLE_GEMFILE=Gemfile.rails-5.1 bundle && BUNDLE_GEMFILE=Gemfile.rails-5.1 bundle exec rspec spec
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bullet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.1.
|
4
|
+
version: 6.1.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Huang
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-08-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -45,9 +45,9 @@ executables: []
|
|
45
45
|
extensions: []
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
|
+
- ".github/workflows/main.yml"
|
48
49
|
- ".gitignore"
|
49
50
|
- ".rspec"
|
50
|
-
- ".travis.yml"
|
51
51
|
- CHANGELOG.md
|
52
52
|
- Gemfile
|
53
53
|
- Gemfile.mongoid
|
@@ -129,6 +129,7 @@ files:
|
|
129
129
|
- spec/integration/counter_cache_spec.rb
|
130
130
|
- spec/integration/mongoid/association_spec.rb
|
131
131
|
- spec/models/address.rb
|
132
|
+
- spec/models/attachment.rb
|
132
133
|
- spec/models/author.rb
|
133
134
|
- spec/models/base_user.rb
|
134
135
|
- spec/models/category.rb
|
@@ -137,6 +138,7 @@ files:
|
|
137
138
|
- spec/models/comment.rb
|
138
139
|
- spec/models/company.rb
|
139
140
|
- spec/models/country.rb
|
141
|
+
- spec/models/deal.rb
|
140
142
|
- spec/models/document.rb
|
141
143
|
- spec/models/entry.rb
|
142
144
|
- spec/models/firm.rb
|
@@ -190,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
190
192
|
- !ruby/object:Gem::Version
|
191
193
|
version: 1.3.6
|
192
194
|
requirements: []
|
193
|
-
rubygems_version: 3.
|
195
|
+
rubygems_version: 3.2.22
|
194
196
|
signing_key:
|
195
197
|
specification_version: 4
|
196
198
|
summary: help to kill N+1 queries and unused eager loading.
|
@@ -216,6 +218,7 @@ test_files:
|
|
216
218
|
- spec/integration/counter_cache_spec.rb
|
217
219
|
- spec/integration/mongoid/association_spec.rb
|
218
220
|
- spec/models/address.rb
|
221
|
+
- spec/models/attachment.rb
|
219
222
|
- spec/models/author.rb
|
220
223
|
- spec/models/base_user.rb
|
221
224
|
- spec/models/category.rb
|
@@ -224,6 +227,7 @@ test_files:
|
|
224
227
|
- spec/models/comment.rb
|
225
228
|
- spec/models/company.rb
|
226
229
|
- spec/models/country.rb
|
230
|
+
- spec/models/deal.rb
|
227
231
|
- spec/models/document.rb
|
228
232
|
- spec/models/entry.rb
|
229
233
|
- spec/models/firm.rb
|
data/.travis.yml
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
language: ruby
|
2
|
-
rvm:
|
3
|
-
- 2.3.0
|
4
|
-
- 2.6.0
|
5
|
-
gemfile:
|
6
|
-
- Gemfile.rails-6.0
|
7
|
-
- Gemfile.rails-5.2
|
8
|
-
- Gemfile.rails-5.1
|
9
|
-
- Gemfile.rails-5.0
|
10
|
-
- Gemfile.rails-4.2
|
11
|
-
- Gemfile.rails-4.1
|
12
|
-
- Gemfile.rails-4.0
|
13
|
-
matrix:
|
14
|
-
exclude:
|
15
|
-
- rvm: 2.3.0
|
16
|
-
gemfile: Gemfile.rails-6.0
|
17
|
-
- rvm: 2.6.0
|
18
|
-
gemfile: Gemfile.rails-5.2
|
19
|
-
- rvm: 2.6.0
|
20
|
-
gemfile: Gemfile.rails-5.1
|
21
|
-
- rvm: 2.6.0
|
22
|
-
gemfile: Gemfile.rails-5.0
|
23
|
-
- rvm: 2.6.0
|
24
|
-
gemfile: Gemfile.rails-4.2
|
25
|
-
- rvm: 2.6.0
|
26
|
-
gemfile: Gemfile.rails-4.1
|
27
|
-
- rvm: 2.6.0
|
28
|
-
gemfile: Gemfile.rails-4.0
|
29
|
-
env:
|
30
|
-
- DB=sqlite
|
31
|
-
before_install:
|
32
|
-
- "find /home/travis/.rvm/rubies -wholename '*default/bundler-*.gemspec' -delete"
|
33
|
-
- gem install bundler -v '< 2'
|