bullet 5.7.0 → 5.7.1
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/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
|