bullet 5.7.0 → 5.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -1
- data/Gemfile.rails-5.2 +1 -1
- data/Guardfile +1 -1
- data/README.md +3 -0
- data/Rakefile +4 -4
- data/bullet.gemspec +2 -3
- data/lib/bullet.rb +23 -23
- data/lib/bullet/active_record4.rb +3 -1
- data/lib/bullet/active_record41.rb +1 -1
- data/lib/bullet/active_record42.rb +8 -9
- data/lib/bullet/active_record5.rb +27 -28
- data/lib/bullet/active_record52.rb +27 -28
- data/lib/bullet/detector/association.rb +12 -12
- data/lib/bullet/detector/counter_cache.rb +7 -7
- data/lib/bullet/detector/n_plus_one_query.rb +6 -6
- data/lib/bullet/detector/unused_eager_loading.rb +23 -21
- data/lib/bullet/ext/object.rb +4 -4
- data/lib/bullet/ext/string.rb +1 -1
- data/lib/bullet/notification/base.rb +14 -14
- data/lib/bullet/notification/n_plus_one_query.rb +4 -4
- data/lib/bullet/notification/unused_eager_loading.rb +4 -4
- data/lib/bullet/notification_collector.rb +0 -1
- data/lib/bullet/version.rb +1 -2
- data/perf/benchmark.rb +7 -7
- data/spec/bullet/detector/n_plus_one_query_spec.rb +8 -8
- data/spec/bullet/ext/object_spec.rb +1 -1
- data/spec/bullet/notification/base_spec.rb +6 -6
- data/spec/bullet/notification/counter_cache_spec.rb +1 -1
- data/spec/bullet/notification/n_plus_one_query_spec.rb +1 -1
- data/spec/bullet/notification/unused_eager_loading_spec.rb +1 -1
- data/spec/bullet/rack_spec.rb +9 -10
- data/spec/bullet/registry/association_spec.rb +2 -2
- data/spec/bullet/registry/base_spec.rb +3 -3
- data/spec/bullet_spec.rb +7 -7
- data/spec/integration/active_record/association_spec.rb +16 -16
- data/spec/integration/counter_cache_spec.rb +2 -2
- data/spec/models/mongoid/address.rb +1 -1
- data/spec/models/mongoid/category.rb +2 -2
- data/spec/models/mongoid/comment.rb +1 -1
- data/spec/models/mongoid/company.rb +1 -1
- data/spec/models/mongoid/entry.rb +1 -1
- data/spec/models/mongoid/post.rb +4 -4
- data/spec/spec_helper.rb +1 -1
- data/spec/support/mongo_seed.rb +23 -23
- data/spec/support/rack_double.rb +7 -16
- data/spec/support/sqlite_seed.rb +73 -73
- metadata +5 -5
@@ -43,31 +43,31 @@ module Bullet
|
|
43
43
|
# e.g. { "Post:1" => [:comments] }
|
44
44
|
# the object_associations keep all associations that may be or may no be
|
45
45
|
# unpreload associations or unused preload associations.
|
46
|
-
|
47
|
-
|
48
|
-
|
46
|
+
def object_associations
|
47
|
+
Thread.current[:bullet_object_associations]
|
48
|
+
end
|
49
49
|
|
50
50
|
# call_object_associations keep the object relationships
|
51
51
|
# that object.associations is called.
|
52
52
|
# e.g. { "Post:1" => [:comments] }
|
53
53
|
# they are used to detect unused preload associations.
|
54
|
-
|
55
|
-
|
56
|
-
|
54
|
+
def call_object_associations
|
55
|
+
Thread.current[:bullet_call_object_associations]
|
56
|
+
end
|
57
57
|
|
58
58
|
# inversed_objects keeps object relationships
|
59
59
|
# that association is inversed.
|
60
60
|
# e.g. { "Comment:1" => ["post"] }
|
61
|
-
|
62
|
-
|
63
|
-
|
61
|
+
def inversed_objects
|
62
|
+
Thread.current[:bullet_inversed_objects]
|
63
|
+
end
|
64
64
|
|
65
65
|
# eager_loadings keep the object relationships
|
66
66
|
# that the associations are preloaded by find :include.
|
67
67
|
# e.g. { ["Post:1", "Post:2"] => [:comments, :user] }
|
68
|
-
|
69
|
-
|
70
|
-
|
68
|
+
def eager_loadings
|
69
|
+
Thread.current[:bullet_eager_loadings]
|
70
|
+
end
|
71
71
|
end
|
72
72
|
end
|
73
73
|
end
|
@@ -32,7 +32,7 @@ module Bullet
|
|
32
32
|
impossible_objects.add object.bullet_key
|
33
33
|
end
|
34
34
|
|
35
|
-
def conditions_met?(object,
|
35
|
+
def conditions_met?(object, _associations)
|
36
36
|
possible_objects.include?(object.bullet_key) && !impossible_objects.include?(object.bullet_key)
|
37
37
|
end
|
38
38
|
|
@@ -46,14 +46,14 @@ module Bullet
|
|
46
46
|
|
47
47
|
private
|
48
48
|
|
49
|
-
|
50
|
-
|
49
|
+
def create_notification(klazz, associations)
|
50
|
+
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:counter_cache, klazz)
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
end
|
52
|
+
if notify_associations.present?
|
53
|
+
notice = Bullet::Notification::CounterCache.new klazz, notify_associations
|
54
|
+
Bullet.notification_collector.add notice
|
56
55
|
end
|
56
|
+
end
|
57
57
|
end
|
58
58
|
end
|
59
59
|
end
|
@@ -82,14 +82,14 @@ module Bullet
|
|
82
82
|
|
83
83
|
private
|
84
84
|
|
85
|
-
|
86
|
-
|
85
|
+
def create_notification(callers, klazz, associations)
|
86
|
+
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:n_plus_one_query, klazz)
|
87
87
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
88
|
+
if notify_associations.present?
|
89
|
+
notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
|
90
|
+
Bullet.notification_collector.add(notice)
|
92
91
|
end
|
92
|
+
end
|
93
93
|
end
|
94
94
|
end
|
95
95
|
end
|
@@ -30,13 +30,15 @@ module Bullet
|
|
30
30
|
Bullet.debug('Detector::UnusedEagerLoading#add_eager_loadings', "objects: #{objects.map(&:bullet_key).join(', ')}, associations: #{associations}")
|
31
31
|
bullet_keys = objects.map(&:bullet_key)
|
32
32
|
|
33
|
-
to_add
|
34
|
-
|
33
|
+
to_add = []
|
34
|
+
to_merge = []
|
35
|
+
to_delete = []
|
36
|
+
eager_loadings.each do |k, _v|
|
35
37
|
key_objects_overlap = k & bullet_keys
|
36
38
|
|
37
39
|
next if key_objects_overlap.empty?
|
38
40
|
|
39
|
-
bullet_keys
|
41
|
+
bullet_keys -= k
|
40
42
|
if key_objects_overlap == k
|
41
43
|
to_add << [k, associations]
|
42
44
|
else
|
@@ -57,29 +59,29 @@ module Bullet
|
|
57
59
|
|
58
60
|
private
|
59
61
|
|
60
|
-
|
61
|
-
|
62
|
+
def create_notification(callers, klazz, associations)
|
63
|
+
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:unused_eager_loading, klazz)
|
62
64
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
65
|
+
if notify_associations.present?
|
66
|
+
notice = Bullet::Notification::UnusedEagerLoading.new(callers, klazz, notify_associations)
|
67
|
+
Bullet.notification_collector.add(notice)
|
67
68
|
end
|
69
|
+
end
|
68
70
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
76
|
-
all.to_a
|
71
|
+
def call_associations(bullet_key, associations)
|
72
|
+
all = Set.new
|
73
|
+
eager_loadings.similarly_associated(bullet_key, associations).each do |related_bullet_key|
|
74
|
+
coa = call_object_associations[related_bullet_key]
|
75
|
+
next if coa.nil?
|
76
|
+
all.merge coa
|
77
77
|
end
|
78
|
+
all.to_a
|
79
|
+
end
|
78
80
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
81
|
+
def diff_object_associations(bullet_key, associations)
|
82
|
+
potential_associations = associations - call_associations(bullet_key, associations)
|
83
|
+
potential_associations.reject { |a| a.is_a?(Hash) }
|
84
|
+
end
|
83
85
|
end
|
84
86
|
end
|
85
87
|
end
|
data/lib/bullet/ext/object.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
class Object
|
2
2
|
def bullet_key
|
3
|
-
"#{self.class}:#{
|
3
|
+
"#{self.class}:#{primary_key_value}"
|
4
4
|
end
|
5
5
|
|
6
6
|
def primary_key_value
|
7
7
|
if self.class.respond_to?(:primary_keys) && self.class.primary_keys
|
8
|
-
self.class.primary_keys.map { |primary_key|
|
8
|
+
self.class.primary_keys.map { |primary_key| send primary_key }.join(','.freeze)
|
9
9
|
elsif self.class.respond_to?(:primary_key) && self.class.primary_key
|
10
|
-
|
10
|
+
send self.class.primary_key
|
11
11
|
else
|
12
|
-
|
12
|
+
id
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
data/lib/bullet/ext/string.rb
CHANGED
@@ -11,11 +11,11 @@ module Bullet
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def title
|
14
|
-
raise NoMethodError
|
14
|
+
raise NoMethodError, 'no method title defined'
|
15
15
|
end
|
16
16
|
|
17
17
|
def body
|
18
|
-
raise NoMethodError
|
18
|
+
raise NoMethodError, 'no method body defined'
|
19
19
|
end
|
20
20
|
|
21
21
|
def call_stack_messages
|
@@ -36,11 +36,11 @@ module Bullet
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def notify_inline
|
39
|
-
|
39
|
+
notifier.inline_notify(notification_data)
|
40
40
|
end
|
41
41
|
|
42
42
|
def notify_out_of_channel
|
43
|
-
|
43
|
+
notifier.out_of_channel_notify(notification_data)
|
44
44
|
end
|
45
45
|
|
46
46
|
def short_notice
|
@@ -49,10 +49,10 @@ module Bullet
|
|
49
49
|
|
50
50
|
def notification_data
|
51
51
|
{
|
52
|
-
:
|
53
|
-
:
|
54
|
-
:
|
55
|
-
:
|
52
|
+
user: whoami,
|
53
|
+
url: url,
|
54
|
+
title: title,
|
55
|
+
body: body_with_caller
|
56
56
|
}
|
57
57
|
end
|
58
58
|
|
@@ -66,13 +66,13 @@ module Bullet
|
|
66
66
|
|
67
67
|
protected
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
def klazz_associations_str
|
70
|
+
" #{@base_class} => [#{@associations.map(&:inspect).join(', '.freeze)}]"
|
71
|
+
end
|
72
72
|
|
73
|
-
|
74
|
-
|
75
|
-
|
73
|
+
def associations_str
|
74
|
+
":includes => #{@associations.map { |a| a.to_s.to_sym unless a.is_a? Hash }.inspect}"
|
75
|
+
end
|
76
76
|
end
|
77
77
|
end
|
78
78
|
end
|
@@ -17,15 +17,15 @@ module Bullet
|
|
17
17
|
|
18
18
|
def notification_data
|
19
19
|
super.merge(
|
20
|
-
:
|
20
|
+
backtrace: []
|
21
21
|
)
|
22
22
|
end
|
23
23
|
|
24
24
|
protected
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
def call_stack_messages
|
27
|
+
(['Call stack'] + @callers).join("\n ")
|
28
|
+
end
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
@@ -17,15 +17,15 @@ module Bullet
|
|
17
17
|
|
18
18
|
def notification_data
|
19
19
|
super.merge(
|
20
|
-
:
|
20
|
+
backtrace: []
|
21
21
|
)
|
22
22
|
end
|
23
23
|
|
24
24
|
protected
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
def call_stack_messages
|
27
|
+
(['Call stack'] + @callers).join("\n ")
|
28
|
+
end
|
29
29
|
end
|
30
30
|
end
|
31
31
|
end
|
data/lib/bullet/version.rb
CHANGED
data/perf/benchmark.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
$LOAD_PATH << 'lib'
|
2
2
|
require 'benchmark'
|
3
3
|
require 'rails'
|
4
4
|
require 'active_record'
|
@@ -27,13 +27,13 @@ class User < ActiveRecord::Base
|
|
27
27
|
end
|
28
28
|
|
29
29
|
# create database bullet_benchmark;
|
30
|
-
ActiveRecord::Base.establish_connection(:
|
30
|
+
ActiveRecord::Base.establish_connection(adapter: 'mysql2', database: 'bullet_benchmark', server: '/tmp/mysql.socket', username: 'root')
|
31
31
|
|
32
32
|
ActiveRecord::Base.connection.tables.each do |table|
|
33
33
|
ActiveRecord::Base.connection.drop_table(table)
|
34
34
|
end
|
35
35
|
|
36
|
-
ActiveRecord::Schema.define(:
|
36
|
+
ActiveRecord::Schema.define(version: 1) do
|
37
37
|
create_table :posts do |t|
|
38
38
|
t.column :title, :string
|
39
39
|
t.column :body, :string
|
@@ -56,21 +56,21 @@ posts_size = 1000
|
|
56
56
|
comments_size = 10_000
|
57
57
|
users = []
|
58
58
|
users_size.times do |i|
|
59
|
-
users << User.new(:
|
59
|
+
users << User.new(name: "user#{i}")
|
60
60
|
end
|
61
61
|
User.import users
|
62
62
|
users = User.all
|
63
63
|
|
64
64
|
posts = []
|
65
65
|
posts_size.times do |i|
|
66
|
-
posts << Post.new(:
|
66
|
+
posts << Post.new(title: "Title #{i}", body: "Body #{i}", user: users[i % 100])
|
67
67
|
end
|
68
68
|
Post.import posts
|
69
69
|
posts = Post.all
|
70
70
|
|
71
71
|
comments = []
|
72
72
|
comments_size.times do |i|
|
73
|
-
comments << Comment.new(:
|
73
|
+
comments << Comment.new(body: "Comment #{i}", post: posts[i % 1000], user: users[i % 100])
|
74
74
|
end
|
75
75
|
Comment.import comments
|
76
76
|
|
@@ -82,7 +82,7 @@ Benchmark.bm(70) do |bm|
|
|
82
82
|
bm.report("Querying & Iterating #{posts_size} Posts with #{comments_size} Comments and #{users_size} Users") do
|
83
83
|
10.times do
|
84
84
|
Bullet.start_request
|
85
|
-
Post.select('SQL_NO_CACHE *').includes(:user, :
|
85
|
+
Post.select('SQL_NO_CACHE *').includes(:user, comments: :user).each do |p|
|
86
86
|
p.title
|
87
87
|
p.user.name
|
88
88
|
p.comments.each do |c|
|
@@ -91,9 +91,9 @@ module Bullet
|
|
91
91
|
after { Bullet.stacktrace_excludes = nil }
|
92
92
|
|
93
93
|
it 'should not create notification when stacktrace contains paths that are in the exclude list' do
|
94
|
-
in_project = OpenStruct.new(:
|
95
|
-
included_path = OpenStruct.new(:
|
96
|
-
excluded_path = OpenStruct.new(:
|
94
|
+
in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
|
95
|
+
included_path = OpenStruct.new(absolute_path: '/ghi/ghi.rb')
|
96
|
+
excluded_path = OpenStruct.new(absolute_path: '/def/def.rb')
|
97
97
|
|
98
98
|
expect(NPlusOneQuery).to receive(:caller_locations).and_return([in_project, included_path, excluded_path])
|
99
99
|
expect(NPlusOneQuery).to_not receive(:create_notification)
|
@@ -104,8 +104,8 @@ module Bullet
|
|
104
104
|
|
105
105
|
context '.caller_in_project' do
|
106
106
|
it 'should include only paths that are in the project' do
|
107
|
-
in_project = OpenStruct.new(:
|
108
|
-
not_in_project = OpenStruct.new(:
|
107
|
+
in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
|
108
|
+
not_in_project = OpenStruct.new(absolute_path: '/def/def.rb')
|
109
109
|
|
110
110
|
expect(NPlusOneQuery).to receive(:caller_locations).and_return([in_project, not_in_project])
|
111
111
|
expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(true)
|
@@ -118,9 +118,9 @@ module Bullet
|
|
118
118
|
after { Bullet.stacktrace_includes = nil }
|
119
119
|
|
120
120
|
it 'should include paths that are in the stacktrace_include list' do
|
121
|
-
in_project = OpenStruct.new(:
|
122
|
-
included_gems = [OpenStruct.new(:
|
123
|
-
excluded_gem = OpenStruct.new(:
|
121
|
+
in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
|
122
|
+
included_gems = [OpenStruct.new(absolute_path: '/def/def.rb'), OpenStruct.new(absolute_path: 'xyz/xyz.rb')]
|
123
|
+
excluded_gem = OpenStruct.new(absolute_path: '/ghi/ghi.rb')
|
124
124
|
|
125
125
|
expect(NPlusOneQuery).to receive(:caller_locations).and_return([in_project, *included_gems, excluded_gem])
|
126
126
|
expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(true)
|
@@ -30,7 +30,7 @@ describe Object do
|
|
30
30
|
|
31
31
|
it 'should return value for multiple primary keys' do
|
32
32
|
post = Post.first
|
33
|
-
allow(Post).to receive(:primary_keys).and_return([
|
33
|
+
allow(Post).to receive(:primary_keys).and_return(%i[category_id writer_id])
|
34
34
|
expect(post.primary_key_value).to eq("#{post.category_id},#{post.writer_id}")
|
35
35
|
end
|
36
36
|
end
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
module Bullet
|
4
4
|
module Notification
|
5
5
|
describe Base do
|
6
|
-
subject { Base.new(Post, [
|
6
|
+
subject { Base.new(Post, %i[comments votes]) }
|
7
7
|
|
8
8
|
context '#title' do
|
9
9
|
it 'should raise NoMethodError' do
|
@@ -66,7 +66,7 @@ module Bullet
|
|
66
66
|
allow(subject).to receive(:url).and_return('url')
|
67
67
|
allow(subject).to receive(:title).and_return('title')
|
68
68
|
allow(subject).to receive(:body_with_caller).and_return('body_with_caller')
|
69
|
-
expect(subject.notification_data).to eq(:
|
69
|
+
expect(subject.notification_data).to eq(user: 'whoami', url: 'url', title: 'title', body: 'body_with_caller')
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
@@ -74,8 +74,8 @@ module Bullet
|
|
74
74
|
it 'should send full_notice to notifier' do
|
75
75
|
notifier = double
|
76
76
|
allow(subject).to receive(:notifier).and_return(notifier)
|
77
|
-
allow(subject).to receive(:notification_data).and_return(:
|
78
|
-
expect(notifier).to receive(:inline_notify).with(:
|
77
|
+
allow(subject).to receive(:notification_data).and_return(foo: :bar)
|
78
|
+
expect(notifier).to receive(:inline_notify).with(foo: :bar)
|
79
79
|
subject.notify_inline
|
80
80
|
end
|
81
81
|
end
|
@@ -84,8 +84,8 @@ module Bullet
|
|
84
84
|
it 'should send full_out_of_channel to notifier' do
|
85
85
|
notifier = double
|
86
86
|
allow(subject).to receive(:notifier).and_return(notifier)
|
87
|
-
allow(subject).to receive(:notification_data).and_return(:
|
88
|
-
expect(notifier).to receive(:out_of_channel_notify).with(:
|
87
|
+
allow(subject).to receive(:notification_data).and_return(foo: :bar)
|
88
|
+
expect(notifier).to receive(:out_of_channel_notify).with(foo: :bar)
|
89
89
|
subject.notify_out_of_channel
|
90
90
|
end
|
91
91
|
end
|