bullet 5.7.5 → 5.7.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -4
- data/README.md +6 -3
- data/Rakefile +1 -1
- data/bullet.gemspec +1 -1
- data/lib/bullet/active_record4.rb +12 -13
- data/lib/bullet/active_record41.rb +12 -13
- data/lib/bullet/active_record42.rb +12 -13
- data/lib/bullet/active_record5.rb +11 -11
- data/lib/bullet/active_record52.rb +11 -11
- data/lib/bullet/dependency.rb +1 -1
- data/lib/bullet/stack_trace_filter.rb +40 -21
- data/lib/bullet/version.rb +1 -1
- data/spec/bullet/detector/n_plus_one_query_spec.rb +23 -0
- data/spec/integration/active_record/association_spec.rb +22 -0
- data/spec/models/post.rb +15 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b32f7ec78bd32da2e77680a04a02aefb3cd6615f
|
4
|
+
data.tar.gz: d946cc0ded11f343b047bb16145cc72c703d15c9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c27d586ccdee749e304f57df64c7c17cc77ea88689d5e60b4ae53d19cf25447fe9758fc8c968ca0016a1bb8c5068af1cefd6a76bf07725bcb7c0ffc099ca345
|
7
|
+
data.tar.gz: a864ff44451a06069e812bab6b41826cef78ceed6e25949fd630a0399271d7fb3b25e2b43b619ea74956f7ec58c5886045e5766f7ec9efb8e5f2f2e0c197acac
|
data/CHANGELOG.md
CHANGED
@@ -1,21 +1,27 @@
|
|
1
1
|
## Next Release
|
2
2
|
|
3
|
-
## 5.7.
|
3
|
+
## 5.7.6 (08/15/2018)
|
4
|
+
|
5
|
+
* Extend stacktrace matching for sub-file precision
|
6
|
+
* Fix false positive in after_save/_create callbacks
|
7
|
+
* Don't triger a preload error on "manual" preloads
|
8
|
+
|
9
|
+
## 5.7.5 (03/12/2018)
|
4
10
|
|
5
11
|
* Fix duplicate logs in mongoid 4.x and 5.x version
|
6
12
|
* Add magic comment frozen_string_literal: true
|
7
13
|
|
8
|
-
## 5.7.4 (10/
|
14
|
+
## 5.7.4 (03/10/2018)
|
9
15
|
|
10
16
|
* Avoid Bullet from making extra queries in mongoid6
|
11
17
|
* Use caller for ruby 1.9 while caller_locations for 2.0+
|
12
18
|
|
13
|
-
## 5.7.3 (17/
|
19
|
+
## 5.7.3 (02/17/2018)
|
14
20
|
|
15
21
|
* Exclude configured bundler path in addition to '/vendor'
|
16
22
|
* Support rails 5.1.5
|
17
23
|
|
18
|
-
## 5.7.2 (18/
|
24
|
+
## 5.7.2 (01/18/2018)
|
19
25
|
|
20
26
|
* Fix `caller_path` in `excluded_stacktrace_path`
|
21
27
|
|
data/README.md
CHANGED
@@ -64,7 +64,7 @@ config.after_initialize do
|
|
64
64
|
Bullet.rollbar = true
|
65
65
|
Bullet.add_footer = true
|
66
66
|
Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
|
67
|
-
Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware' ]
|
67
|
+
Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware', ['my_file.rb', 'my_method'], ['my_file.rb', 16..20] ]
|
68
68
|
Bullet.slack = { webhook_url: 'http://some.slack.url', channel: '#default', username: 'notifier' }
|
69
69
|
end
|
70
70
|
```
|
@@ -87,6 +87,8 @@ The code above will enable all of the Bullet notification systems:
|
|
87
87
|
* `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.
|
88
88
|
* `Bullet.stacktrace_includes`: include paths with any of these substrings in the stack trace, even if they are not in your main app
|
89
89
|
* `Bullet.stacktrace_excludes`: ignore paths with any of these substrings in the stack trace, even if they are not in your main app.
|
90
|
+
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
|
91
|
+
item is a line number, a Range of line numbers, or a (bare) method name, to exclude only particular lines in a file.
|
90
92
|
* `Bullet.slack`: add notifications to slack
|
91
93
|
* `Bullet.raise`: raise errors, useful for making your specs fail unless they have optimized queries
|
92
94
|
|
@@ -126,10 +128,11 @@ class ApplicationController < ActionController::Base
|
|
126
128
|
around_action :skip_bullet
|
127
129
|
|
128
130
|
def skip_bullet
|
131
|
+
previous_value = Bullet.enabled?
|
129
132
|
Bullet.enable = false
|
130
133
|
yield
|
131
134
|
ensure
|
132
|
-
Bullet.enable =
|
135
|
+
Bullet.enable = previous_value
|
133
136
|
end
|
134
137
|
end
|
135
138
|
```
|
@@ -180,7 +183,7 @@ If you find Bullet does not work for you, *please disable your browser's cache*.
|
|
180
183
|
|
181
184
|
### Profile a job
|
182
185
|
|
183
|
-
The Bullet gem uses rack middleware to profile requests. If you want to use Bullet without an http server, like to profile a job, you can use
|
186
|
+
The Bullet gem uses rack middleware to profile requests. If you want to use Bullet without an http server, like to profile a job, you can use the profile method and fetch warnings
|
184
187
|
|
185
188
|
```ruby
|
186
189
|
Bullet.profile do
|
data/Rakefile
CHANGED
data/bullet.gemspec
CHANGED
@@ -48,21 +48,12 @@ module Bullet
|
|
48
48
|
end
|
49
49
|
|
50
50
|
::ActiveRecord::Persistence.class_eval do
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
51
|
+
def _create_record_with_bullet(*args)
|
52
|
+
_create_record_without_bullet(*args).tap do
|
53
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
|
55
54
|
end
|
56
55
|
end
|
57
|
-
alias_method_chain :
|
58
|
-
|
59
|
-
def save_with_bullet!(*args, &proc)
|
60
|
-
was_new_record = new_record?
|
61
|
-
save_without_bullet!(*args, &proc).tap do |result|
|
62
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
63
|
-
end
|
64
|
-
end
|
65
|
-
alias_method_chain :save!, :bullet
|
56
|
+
alias_method_chain :_create_record, :bullet
|
66
57
|
end
|
67
58
|
|
68
59
|
::ActiveRecord::Associations::Preloader.class_eval do
|
@@ -199,6 +190,14 @@ module Bullet
|
|
199
190
|
end
|
200
191
|
# rubocop:enable Style/MethodCallWithoutArgsParentheses
|
201
192
|
end
|
193
|
+
|
194
|
+
::ActiveRecord::Associations::BelongsToAssociation.class_eval do
|
195
|
+
def writer_with_bullet(record)
|
196
|
+
Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
|
197
|
+
writer_without_bullet(record)
|
198
|
+
end
|
199
|
+
alias_method_chain :writer, :bullet
|
200
|
+
end
|
202
201
|
end
|
203
202
|
end
|
204
203
|
end
|
@@ -50,21 +50,12 @@ module Bullet
|
|
50
50
|
end
|
51
51
|
|
52
52
|
::ActiveRecord::Persistence.class_eval do
|
53
|
-
def
|
54
|
-
|
55
|
-
|
56
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
53
|
+
def _create_record_with_bullet(*args)
|
54
|
+
_create_record_without_bullet(*args).tap do
|
55
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
|
57
56
|
end
|
58
57
|
end
|
59
|
-
alias_method_chain :
|
60
|
-
|
61
|
-
def save_with_bullet!(*args, &proc)
|
62
|
-
was_new_record = new_record?
|
63
|
-
save_without_bullet!(*args, &proc).tap do |result|
|
64
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
65
|
-
end
|
66
|
-
end
|
67
|
-
alias_method_chain :save!, :bullet
|
58
|
+
alias_method_chain :_create_record, :bullet
|
68
59
|
end
|
69
60
|
|
70
61
|
::ActiveRecord::Associations::Preloader.class_eval do
|
@@ -187,6 +178,14 @@ module Bullet
|
|
187
178
|
origin_count_records
|
188
179
|
end
|
189
180
|
end
|
181
|
+
|
182
|
+
::ActiveRecord::Associations::BelongsToAssociation.class_eval do
|
183
|
+
def writer_with_bullet(record)
|
184
|
+
Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
|
185
|
+
writer_without_bullet(record)
|
186
|
+
end
|
187
|
+
alias_method_chain :writer, :bullet
|
188
|
+
end
|
190
189
|
end
|
191
190
|
end
|
192
191
|
end
|
@@ -44,21 +44,12 @@ module Bullet
|
|
44
44
|
end
|
45
45
|
|
46
46
|
::ActiveRecord::Persistence.class_eval do
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
47
|
+
def _create_record_with_bullet(*args)
|
48
|
+
_create_record_without_bullet(*args).tap do
|
49
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
|
51
50
|
end
|
52
51
|
end
|
53
|
-
alias_method_chain :
|
54
|
-
|
55
|
-
def save_with_bullet!(*args, &proc)
|
56
|
-
was_new_record = new_record?
|
57
|
-
save_without_bullet!(*args, &proc).tap do |result|
|
58
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
59
|
-
end
|
60
|
-
end
|
61
|
-
alias_method_chain :save!, :bullet
|
52
|
+
alias_method_chain :_create_record, :bullet
|
62
53
|
end
|
63
54
|
|
64
55
|
::ActiveRecord::Relation.class_eval do
|
@@ -247,6 +238,14 @@ module Bullet
|
|
247
238
|
origin_count_records
|
248
239
|
end
|
249
240
|
end
|
241
|
+
|
242
|
+
::ActiveRecord::Associations::BelongsToAssociation.class_eval do
|
243
|
+
def writer_with_bullet(record)
|
244
|
+
Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
|
245
|
+
writer_without_bullet(record)
|
246
|
+
end
|
247
|
+
alias_method_chain :writer, :bullet
|
248
|
+
end
|
250
249
|
end
|
251
250
|
end
|
252
251
|
end
|
@@ -2,17 +2,10 @@
|
|
2
2
|
|
3
3
|
module Bullet
|
4
4
|
module SaveWithBulletSupport
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def save!(*args)
|
13
|
-
was_new_record = new_record?
|
14
|
-
super(*args).tap do |result|
|
15
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
5
|
+
def _create_record(*)
|
6
|
+
super do
|
7
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
|
8
|
+
yield(self) if block_given?
|
16
9
|
end
|
17
10
|
end
|
18
11
|
end
|
@@ -238,6 +231,13 @@ module Bullet
|
|
238
231
|
super
|
239
232
|
end
|
240
233
|
end)
|
234
|
+
|
235
|
+
::ActiveRecord::Associations::BelongsToAssociation.prepend(Module.new do
|
236
|
+
def writer(record)
|
237
|
+
Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
|
238
|
+
super
|
239
|
+
end
|
240
|
+
end)
|
241
241
|
end
|
242
242
|
end
|
243
243
|
end
|
@@ -2,17 +2,10 @@
|
|
2
2
|
|
3
3
|
module Bullet
|
4
4
|
module SaveWithBulletSupport
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def save!(*args)
|
13
|
-
was_new_record = new_record?
|
14
|
-
super(*args).tap do |result|
|
15
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
5
|
+
def _create_record(*)
|
6
|
+
super do
|
7
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
|
8
|
+
yield(self) if block_given?
|
16
9
|
end
|
17
10
|
end
|
18
11
|
end
|
@@ -221,6 +214,13 @@ module Bullet
|
|
221
214
|
super
|
222
215
|
end
|
223
216
|
end)
|
217
|
+
|
218
|
+
::ActiveRecord::Associations::BelongsToAssociation.prepend(Module.new do
|
219
|
+
def writer(record)
|
220
|
+
Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
|
221
|
+
super
|
222
|
+
end
|
223
|
+
end)
|
224
224
|
end
|
225
225
|
end
|
226
226
|
end
|
data/lib/bullet/dependency.rb
CHANGED
@@ -8,46 +8,65 @@ module Bullet
|
|
8
8
|
app_root = rails? ? Rails.root.to_s : Dir.pwd
|
9
9
|
vendor_root = app_root + VENDOR_PATH
|
10
10
|
bundler_path = Bundler.bundle_path.to_s
|
11
|
-
select_caller_locations do |
|
11
|
+
select_caller_locations do |location|
|
12
|
+
caller_path = location_as_path(location)
|
12
13
|
caller_path.include?(app_root) && !caller_path.include?(vendor_root) && !caller_path.include?(bundler_path) ||
|
13
|
-
Bullet.stacktrace_includes.any?
|
14
|
-
case include_pattern
|
15
|
-
when String
|
16
|
-
caller_path.include?(include_pattern)
|
17
|
-
when Regexp
|
18
|
-
caller_path =~ include_pattern
|
19
|
-
end
|
20
|
-
end
|
14
|
+
Bullet.stacktrace_includes.any? { |include_pattern| pattern_matches?(location, include_pattern) }
|
21
15
|
end
|
22
16
|
end
|
23
17
|
|
24
18
|
def excluded_stacktrace_path?
|
25
19
|
Bullet.stacktrace_excludes.any? do |exclude_pattern|
|
26
|
-
caller_in_project.any?
|
27
|
-
caller_path = location.absolute_path.to_s
|
28
|
-
case exclude_pattern
|
29
|
-
when String
|
30
|
-
caller_path.include?(exclude_pattern)
|
31
|
-
when Regexp
|
32
|
-
caller_path =~ exclude_pattern
|
33
|
-
end
|
34
|
-
end
|
20
|
+
caller_in_project.any? { |location| pattern_matches?(location, exclude_pattern) }
|
35
21
|
end
|
36
22
|
end
|
37
23
|
|
38
24
|
private
|
39
25
|
|
26
|
+
def pattern_matches?(location, pattern)
|
27
|
+
path = location_as_path(location)
|
28
|
+
case pattern
|
29
|
+
when Array
|
30
|
+
pattern_path = pattern.first
|
31
|
+
filter = pattern.last
|
32
|
+
return false unless pattern_matches?(location, pattern_path)
|
33
|
+
|
34
|
+
case filter
|
35
|
+
when Range
|
36
|
+
filter.include?(location.lineno)
|
37
|
+
when Integer
|
38
|
+
filter == location.lineno
|
39
|
+
when String
|
40
|
+
filter == location.base_label
|
41
|
+
end
|
42
|
+
when String
|
43
|
+
path.include?(pattern)
|
44
|
+
when Regexp
|
45
|
+
path =~ pattern
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def location_as_path(location)
|
50
|
+
ruby_19? ? location : location.absolute_path.to_s
|
51
|
+
end
|
52
|
+
|
40
53
|
def select_caller_locations
|
41
|
-
if
|
54
|
+
if ruby_19?
|
42
55
|
caller.select do |caller_path|
|
43
56
|
yield caller_path
|
44
57
|
end
|
45
58
|
else
|
46
59
|
caller_locations.select do |location|
|
47
|
-
|
48
|
-
yield caller_path
|
60
|
+
yield location
|
49
61
|
end
|
50
62
|
end
|
51
63
|
end
|
64
|
+
|
65
|
+
def ruby_19?
|
66
|
+
if @ruby_19.nil?
|
67
|
+
@ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
|
68
|
+
end
|
69
|
+
@ruby_19
|
70
|
+
end
|
52
71
|
end
|
53
72
|
end
|
data/lib/bullet/version.rb
CHANGED
@@ -101,6 +101,29 @@ module Bullet
|
|
101
101
|
expect(NPlusOneQuery).to_not receive(:create_notification)
|
102
102
|
NPlusOneQuery.call_association(@post, :association)
|
103
103
|
end
|
104
|
+
|
105
|
+
# just a sanity spec to make sure the following spec works correctly
|
106
|
+
it "should create notification when stacktrace contains methods that aren't in the exclude list" do
|
107
|
+
method = NPlusOneQuery.method(:excluded_stacktrace_path?).source_location
|
108
|
+
in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
|
109
|
+
excluded_path = OpenStruct.new(absolute_path: method.first, lineno: method.last)
|
110
|
+
|
111
|
+
expect(NPlusOneQuery).to receive(:caller_locations).at_least(1).and_return([in_project, excluded_path])
|
112
|
+
expect(NPlusOneQuery).to receive(:conditions_met?).and_return(true)
|
113
|
+
expect(NPlusOneQuery).to receive(:create_notification)
|
114
|
+
NPlusOneQuery.call_association(@post, :association)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should not create notification when stacktrace contains methods that are in the exclude list' do
|
118
|
+
method = NPlusOneQuery.method(:excluded_stacktrace_path?).source_location
|
119
|
+
Bullet.stacktrace_excludes = [method]
|
120
|
+
in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
|
121
|
+
excluded_path = OpenStruct.new(absolute_path: method.first, lineno: method.last)
|
122
|
+
|
123
|
+
expect(NPlusOneQuery).to receive(:caller_locations).and_return([in_project, excluded_path])
|
124
|
+
expect(NPlusOneQuery).to_not receive(:create_notification)
|
125
|
+
NPlusOneQuery.call_association(@post, :association)
|
126
|
+
end
|
104
127
|
end
|
105
128
|
end
|
106
129
|
|
@@ -356,6 +356,28 @@ if active_record?
|
|
356
356
|
|
357
357
|
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
358
358
|
end
|
359
|
+
|
360
|
+
it 'should not detect newly assigned object in an after_save' do
|
361
|
+
new_post = Post.new(category: Category.first)
|
362
|
+
|
363
|
+
new_post.trigger_after_save = true
|
364
|
+
new_post.save!
|
365
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
366
|
+
|
367
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
368
|
+
end
|
369
|
+
|
370
|
+
it 'should not detect "manual" preload' do
|
371
|
+
comment = Comment.all.to_a.first
|
372
|
+
post = Post.find(comment.post_id)
|
373
|
+
# "manually" preload with out-of-band data
|
374
|
+
comment.post = post
|
375
|
+
# loading it should not trigger anything
|
376
|
+
comment.post
|
377
|
+
|
378
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
379
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
380
|
+
end
|
359
381
|
end
|
360
382
|
|
361
383
|
context 'comment => post => category' do
|
data/spec/models/post.rb
CHANGED
@@ -14,4 +14,19 @@ class Post < ActiveRecord::Base
|
|
14
14
|
def link=(*)
|
15
15
|
comments.new
|
16
16
|
end
|
17
|
+
|
18
|
+
# see association_spec.rb 'should not detect newly assigned object in an after_save'
|
19
|
+
attr_accessor :trigger_after_save
|
20
|
+
after_save do
|
21
|
+
next unless trigger_after_save
|
22
|
+
|
23
|
+
temp_comment = Comment.new(post: self)
|
24
|
+
# this triggers self to be "possible", even though it's
|
25
|
+
# not saved yet
|
26
|
+
temp_comment.post
|
27
|
+
|
28
|
+
# category should NOT whine about not being pre-loaded, because
|
29
|
+
# it's obviously attached to a new object
|
30
|
+
category
|
31
|
+
end
|
17
32
|
end
|
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: 5.7.
|
4
|
+
version: 5.7.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Huang
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|