bullet 5.5.0 → 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 +5 -5
- data/.github/workflows/main.yml +66 -0
- data/CHANGELOG.md +83 -1
- data/Gemfile +6 -1
- data/Gemfile.mongoid-7.0 +15 -0
- data/Gemfile.rails-4.0 +1 -1
- data/Gemfile.rails-4.1 +1 -1
- data/Gemfile.rails-4.2 +1 -1
- data/Gemfile.rails-5.0 +2 -2
- data/Gemfile.rails-5.1 +15 -0
- data/Gemfile.rails-5.2 +15 -0
- data/Gemfile.rails-6.0 +15 -0
- data/Gemfile.rails-6.1 +15 -0
- data/Guardfile +2 -2
- data/Hacking.md +1 -1
- data/README.md +58 -37
- data/Rakefile +20 -21
- data/bullet.gemspec +21 -15
- data/lib/bullet/active_job.rb +13 -0
- data/lib/bullet/active_record4.rb +15 -34
- data/lib/bullet/active_record41.rb +14 -31
- data/lib/bullet/active_record42.rb +23 -36
- data/lib/bullet/active_record5.rb +178 -158
- data/lib/bullet/active_record52.rb +251 -0
- data/lib/bullet/active_record60.rb +278 -0
- data/lib/bullet/active_record61.rb +278 -0
- data/lib/bullet/bullet_xhr.js +64 -0
- data/lib/bullet/dependency.rb +60 -32
- data/lib/bullet/detector/association.rb +41 -32
- data/lib/bullet/detector/base.rb +2 -0
- data/lib/bullet/detector/counter_cache.rb +25 -17
- data/lib/bullet/detector/n_plus_one_query.rb +38 -24
- data/lib/bullet/detector/unused_eager_loading.rb +35 -26
- data/lib/bullet/detector.rb +2 -0
- data/lib/bullet/ext/object.rb +9 -5
- data/lib/bullet/ext/string.rb +3 -1
- data/lib/bullet/mongoid4x.rb +9 -8
- data/lib/bullet/mongoid5x.rb +9 -8
- data/lib/bullet/mongoid6x.rb +13 -12
- data/lib/bullet/mongoid7x.rb +57 -0
- data/lib/bullet/notification/base.rb +28 -25
- data/lib/bullet/notification/counter_cache.rb +3 -1
- data/lib/bullet/notification/n_plus_one_query.rb +8 -7
- data/lib/bullet/notification/unused_eager_loading.rb +8 -7
- data/lib/bullet/notification.rb +4 -1
- data/lib/bullet/notification_collector.rb +2 -1
- data/lib/bullet/rack.rb +66 -35
- data/lib/bullet/registry/association.rb +2 -0
- data/lib/bullet/registry/base.rb +2 -0
- data/lib/bullet/registry/object.rb +2 -0
- data/lib/bullet/registry.rb +2 -0
- data/lib/bullet/stack_trace_filter.rb +50 -20
- data/lib/bullet/version.rb +3 -2
- data/lib/bullet.rb +136 -55
- data/lib/generators/bullet/install_generator.rb +48 -0
- data/perf/benchmark.rb +19 -22
- data/rails/init.rb +2 -0
- data/spec/bullet/detector/association_spec.rb +6 -4
- data/spec/bullet/detector/base_spec.rb +2 -0
- data/spec/bullet/detector/counter_cache_spec.rb +17 -15
- data/spec/bullet/detector/n_plus_one_query_spec.rb +75 -46
- data/spec/bullet/detector/unused_eager_loading_spec.rb +45 -26
- data/spec/bullet/ext/object_spec.rb +18 -11
- data/spec/bullet/ext/string_spec.rb +7 -5
- data/spec/bullet/notification/base_spec.rb +38 -39
- data/spec/bullet/notification/counter_cache_spec.rb +5 -3
- data/spec/bullet/notification/n_plus_one_query_spec.rb +20 -5
- data/spec/bullet/notification/unused_eager_loading_spec.rb +9 -3
- data/spec/bullet/notification_collector_spec.rb +12 -10
- data/spec/bullet/rack_spec.rb +200 -48
- data/spec/bullet/registry/association_spec.rb +12 -10
- data/spec/bullet/registry/base_spec.rb +22 -20
- data/spec/bullet/registry/object_spec.rb +6 -4
- data/spec/bullet_spec.rb +64 -33
- data/spec/integration/{active_record4 → active_record}/association_spec.rb +238 -223
- data/spec/integration/counter_cache_spec.rb +26 -44
- data/spec/integration/mongoid/association_spec.rb +53 -65
- data/spec/models/address.rb +2 -0
- data/spec/models/attachment.rb +5 -0
- data/spec/models/author.rb +2 -0
- data/spec/models/base_user.rb +2 -0
- data/spec/models/category.rb +2 -0
- data/spec/models/city.rb +2 -0
- data/spec/models/client.rb +4 -0
- data/spec/models/comment.rb +3 -1
- data/spec/models/company.rb +2 -0
- data/spec/models/country.rb +2 -0
- data/spec/models/deal.rb +5 -0
- data/spec/models/document.rb +4 -2
- data/spec/models/entry.rb +2 -0
- data/spec/models/firm.rb +3 -0
- data/spec/models/folder.rb +2 -0
- data/spec/models/group.rb +4 -0
- data/spec/models/mongoid/address.rb +3 -1
- data/spec/models/mongoid/category.rb +4 -2
- data/spec/models/mongoid/comment.rb +3 -1
- data/spec/models/mongoid/company.rb +3 -1
- data/spec/models/mongoid/entry.rb +3 -1
- data/spec/models/mongoid/post.rb +6 -4
- data/spec/models/mongoid/user.rb +2 -0
- data/spec/models/newspaper.rb +3 -1
- data/spec/models/page.rb +2 -0
- data/spec/models/person.rb +2 -0
- data/spec/models/pet.rb +2 -0
- data/spec/models/post.rb +19 -0
- data/spec/models/relationship.rb +2 -0
- data/spec/models/reply.rb +2 -0
- data/spec/models/student.rb +2 -0
- data/spec/models/submission.rb +3 -0
- data/spec/models/teacher.rb +2 -0
- data/spec/models/user.rb +3 -0
- data/spec/models/writer.rb +2 -0
- data/spec/spec_helper.rb +19 -23
- data/spec/support/bullet_ext.rb +10 -9
- data/spec/support/mongo_seed.rb +38 -57
- data/spec/support/rack_double.rb +13 -19
- data/spec/support/sqlite_seed.rb +104 -76
- data/tasks/bullet_tasks.rake +4 -2
- data/test.sh +5 -0
- data/update.sh +3 -0
- metadata +34 -17
- data/.travis.yml +0 -16
- data/spec/integration/active_record5/association_spec.rb +0 -768
data/lib/bullet/dependency.rb
CHANGED
|
@@ -1,47 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Dependency
|
|
3
5
|
def mongoid?
|
|
4
|
-
@mongoid ||= defined?
|
|
6
|
+
@mongoid ||= defined?(::Mongoid)
|
|
5
7
|
end
|
|
6
8
|
|
|
7
9
|
def active_record?
|
|
8
|
-
@active_record ||= defined?
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def rails?
|
|
12
|
-
@rails ||= defined? ::Rails
|
|
10
|
+
@active_record ||= defined?(::ActiveRecord)
|
|
13
11
|
end
|
|
14
12
|
|
|
15
13
|
def active_record_version
|
|
16
|
-
@active_record_version ||=
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
@active_record_version ||=
|
|
15
|
+
begin
|
|
16
|
+
if active_record40?
|
|
17
|
+
'active_record4'
|
|
18
|
+
elsif active_record41?
|
|
19
|
+
'active_record41'
|
|
20
|
+
elsif active_record42?
|
|
21
|
+
'active_record42'
|
|
22
|
+
elsif active_record50?
|
|
23
|
+
'active_record5'
|
|
24
|
+
elsif active_record51?
|
|
25
|
+
'active_record5'
|
|
26
|
+
elsif active_record52?
|
|
27
|
+
'active_record52'
|
|
28
|
+
elsif active_record60?
|
|
29
|
+
'active_record60'
|
|
30
|
+
elsif active_record61?
|
|
31
|
+
'active_record61'
|
|
32
|
+
else
|
|
33
|
+
raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
31
36
|
end
|
|
32
37
|
|
|
33
38
|
def mongoid_version
|
|
34
|
-
@mongoid_version ||=
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
@mongoid_version ||=
|
|
40
|
+
begin
|
|
41
|
+
if mongoid4x?
|
|
42
|
+
'mongoid4x'
|
|
43
|
+
elsif mongoid5x?
|
|
44
|
+
'mongoid5x'
|
|
45
|
+
elsif mongoid6x?
|
|
46
|
+
'mongoid6x'
|
|
47
|
+
elsif mongoid7x?
|
|
48
|
+
'mongoid7x'
|
|
49
|
+
else
|
|
50
|
+
raise "Bullet does not support mongoid #{::Mongoid::VERSION} yet"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
45
53
|
end
|
|
46
54
|
|
|
47
55
|
def active_record4?
|
|
@@ -52,6 +60,10 @@ module Bullet
|
|
|
52
60
|
active_record? && ::ActiveRecord::VERSION::MAJOR == 5
|
|
53
61
|
end
|
|
54
62
|
|
|
63
|
+
def active_record6?
|
|
64
|
+
active_record? && ::ActiveRecord::VERSION::MAJOR == 6
|
|
65
|
+
end
|
|
66
|
+
|
|
55
67
|
def active_record40?
|
|
56
68
|
active_record4? && ::ActiveRecord::VERSION::MINOR == 0
|
|
57
69
|
end
|
|
@@ -72,6 +84,18 @@ module Bullet
|
|
|
72
84
|
active_record5? && ::ActiveRecord::VERSION::MINOR == 1
|
|
73
85
|
end
|
|
74
86
|
|
|
87
|
+
def active_record52?
|
|
88
|
+
active_record5? && ::ActiveRecord::VERSION::MINOR == 2
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def active_record60?
|
|
92
|
+
active_record6? && ::ActiveRecord::VERSION::MINOR == 0
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def active_record61?
|
|
96
|
+
active_record6? && ::ActiveRecord::VERSION::MINOR == 1
|
|
97
|
+
end
|
|
98
|
+
|
|
75
99
|
def mongoid4x?
|
|
76
100
|
mongoid? && ::Mongoid::VERSION =~ /\A4/
|
|
77
101
|
end
|
|
@@ -83,5 +107,9 @@ module Bullet
|
|
|
83
107
|
def mongoid6x?
|
|
84
108
|
mongoid? && ::Mongoid::VERSION =~ /\A6/
|
|
85
109
|
end
|
|
110
|
+
|
|
111
|
+
def mongoid7x?
|
|
112
|
+
mongoid? && ::Mongoid::VERSION =~ /\A7/
|
|
113
|
+
end
|
|
86
114
|
end
|
|
87
115
|
end
|
|
@@ -1,22 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Detector
|
|
3
5
|
class Association < Base
|
|
4
|
-
class <<self
|
|
6
|
+
class << self
|
|
5
7
|
def add_object_associations(object, associations)
|
|
6
8
|
return unless Bullet.start?
|
|
7
9
|
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
|
|
8
|
-
return unless object.
|
|
10
|
+
return unless object.bullet_primary_key_value
|
|
9
11
|
|
|
10
|
-
Bullet.debug(
|
|
12
|
+
Bullet.debug(
|
|
13
|
+
'Detector::Association#add_object_associations',
|
|
14
|
+
"object: #{object.bullet_key}, associations: #{associations}"
|
|
15
|
+
)
|
|
11
16
|
object_associations.add(object.bullet_key, associations)
|
|
12
17
|
end
|
|
13
18
|
|
|
14
19
|
def add_call_object_associations(object, associations)
|
|
15
20
|
return unless Bullet.start?
|
|
16
21
|
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
|
|
17
|
-
return unless object.
|
|
22
|
+
return unless object.bullet_primary_key_value
|
|
18
23
|
|
|
19
|
-
Bullet.debug(
|
|
24
|
+
Bullet.debug(
|
|
25
|
+
'Detector::Association#add_call_object_associations',
|
|
26
|
+
"object: #{object.bullet_key}, associations: #{associations}"
|
|
27
|
+
)
|
|
20
28
|
call_object_associations.add(object.bullet_key, associations)
|
|
21
29
|
end
|
|
22
30
|
|
|
@@ -37,36 +45,37 @@ module Bullet
|
|
|
37
45
|
end
|
|
38
46
|
|
|
39
47
|
private
|
|
40
|
-
# object_associations keep the object relationships
|
|
41
|
-
# that the object has many associations.
|
|
42
|
-
# e.g. { "Post:1" => [:comments] }
|
|
43
|
-
# the object_associations keep all associations that may be or may no be
|
|
44
|
-
# unpreload associations or unused preload associations.
|
|
45
|
-
def object_associations
|
|
46
|
-
Thread.current[:bullet_object_associations]
|
|
47
|
-
end
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
# object_associations keep the object relationships
|
|
50
|
+
# that the object has many associations.
|
|
51
|
+
# e.g. { "Post:1" => [:comments] }
|
|
52
|
+
# the object_associations keep all associations that may be or may no be
|
|
53
|
+
# unpreload associations or unused preload associations.
|
|
54
|
+
def object_associations
|
|
55
|
+
Thread.current[:bullet_object_associations]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# call_object_associations keep the object relationships
|
|
59
|
+
# that object.associations is called.
|
|
60
|
+
# e.g. { "Post:1" => [:comments] }
|
|
61
|
+
# they are used to detect unused preload associations.
|
|
62
|
+
def call_object_associations
|
|
63
|
+
Thread.current[:bullet_call_object_associations]
|
|
64
|
+
end
|
|
56
65
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
# inversed_objects keeps object relationships
|
|
67
|
+
# that association is inversed.
|
|
68
|
+
# e.g. { "Comment:1" => ["post"] }
|
|
69
|
+
def inversed_objects
|
|
70
|
+
Thread.current[:bullet_inversed_objects]
|
|
71
|
+
end
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
# eager_loadings keep the object relationships
|
|
74
|
+
# that the associations are preloaded by find :include.
|
|
75
|
+
# e.g. { ["Post:1", "Post:2"] => [:comments, :user] }
|
|
76
|
+
def eager_loadings
|
|
77
|
+
Thread.current[:bullet_eager_loadings]
|
|
78
|
+
end
|
|
70
79
|
end
|
|
71
80
|
end
|
|
72
81
|
end
|
data/lib/bullet/detector/base.rb
CHANGED
|
@@ -1,38 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Detector
|
|
3
5
|
class CounterCache < Base
|
|
4
|
-
class <<self
|
|
6
|
+
class << self
|
|
5
7
|
def add_counter_cache(object, associations)
|
|
6
8
|
return unless Bullet.start?
|
|
7
9
|
return unless Bullet.counter_cache_enable?
|
|
8
|
-
return unless object.
|
|
10
|
+
return unless object.bullet_primary_key_value
|
|
9
11
|
|
|
10
|
-
Bullet.debug(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
Bullet.debug(
|
|
13
|
+
'Detector::CounterCache#add_counter_cache',
|
|
14
|
+
"object: #{object.bullet_key}, associations: #{associations}"
|
|
15
|
+
)
|
|
16
|
+
create_notification object.class.to_s, associations if conditions_met?(object, associations)
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
def add_possible_objects(object_or_objects)
|
|
17
20
|
return unless Bullet.start?
|
|
18
21
|
return unless Bullet.counter_cache_enable?
|
|
22
|
+
|
|
19
23
|
objects = Array(object_or_objects)
|
|
20
|
-
return if objects.map(&:
|
|
24
|
+
return if objects.map(&:bullet_primary_key_value).compact.empty?
|
|
21
25
|
|
|
22
|
-
Bullet.debug(
|
|
26
|
+
Bullet.debug(
|
|
27
|
+
'Detector::CounterCache#add_possible_objects',
|
|
28
|
+
"objects: #{objects.map(&:bullet_key).join(', ')}"
|
|
29
|
+
)
|
|
23
30
|
objects.each { |object| possible_objects.add object.bullet_key }
|
|
24
31
|
end
|
|
25
32
|
|
|
26
33
|
def add_impossible_object(object)
|
|
27
34
|
return unless Bullet.start?
|
|
28
35
|
return unless Bullet.counter_cache_enable?
|
|
29
|
-
return unless object.
|
|
36
|
+
return unless object.bullet_primary_key_value
|
|
30
37
|
|
|
31
|
-
Bullet.debug(
|
|
38
|
+
Bullet.debug('Detector::CounterCache#add_impossible_object', "object: #{object.bullet_key}")
|
|
32
39
|
impossible_objects.add object.bullet_key
|
|
33
40
|
end
|
|
34
41
|
|
|
35
|
-
def conditions_met?(object,
|
|
42
|
+
def conditions_met?(object, _associations)
|
|
36
43
|
possible_objects.include?(object.bullet_key) && !impossible_objects.include?(object.bullet_key)
|
|
37
44
|
end
|
|
38
45
|
|
|
@@ -45,14 +52,15 @@ module Bullet
|
|
|
45
52
|
end
|
|
46
53
|
|
|
47
54
|
private
|
|
48
|
-
def create_notification(klazz, associations)
|
|
49
|
-
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:counter_cache, klazz)
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
def create_notification(klazz, associations)
|
|
57
|
+
notify_associations = Array(associations) - Bullet.get_safelist_associations(:counter_cache, klazz)
|
|
58
|
+
|
|
59
|
+
if notify_associations.present?
|
|
60
|
+
notice = Bullet::Notification::CounterCache.new klazz, notify_associations
|
|
61
|
+
Bullet.notification_collector.add notice
|
|
55
62
|
end
|
|
63
|
+
end
|
|
56
64
|
end
|
|
57
65
|
end
|
|
58
66
|
end
|
|
@@ -1,23 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Detector
|
|
3
5
|
class NPlusOneQuery < Association
|
|
4
6
|
extend Dependency
|
|
5
7
|
extend StackTraceFilter
|
|
6
8
|
|
|
7
|
-
class <<self
|
|
9
|
+
class << self
|
|
8
10
|
# executed when object.assocations is called.
|
|
9
11
|
# first, it keeps this method call for object.association.
|
|
10
12
|
# then, it checks if this associations call is unpreload.
|
|
11
13
|
# if it is, keeps this unpreload associations and caller.
|
|
12
14
|
def call_association(object, associations)
|
|
13
15
|
return unless Bullet.start?
|
|
14
|
-
return unless
|
|
16
|
+
return unless Bullet.n_plus_one_query_enable?
|
|
17
|
+
return unless object.bullet_primary_key_value
|
|
15
18
|
return if inversed_objects.include?(object.bullet_key, associations)
|
|
19
|
+
|
|
16
20
|
add_call_object_associations(object, associations)
|
|
17
21
|
|
|
18
|
-
Bullet.debug(
|
|
22
|
+
Bullet.debug(
|
|
23
|
+
'Detector::NPlusOneQuery#call_association',
|
|
24
|
+
"object: #{object.bullet_key}, associations: #{associations}"
|
|
25
|
+
)
|
|
19
26
|
if !excluded_stacktrace_path? && conditions_met?(object, associations)
|
|
20
|
-
Bullet.debug(
|
|
27
|
+
Bullet.debug('detect n + 1 query', "object: #{object.bullet_key}, associations: #{associations}")
|
|
21
28
|
create_notification caller_in_project, object.class.to_s, associations
|
|
22
29
|
end
|
|
23
30
|
end
|
|
@@ -25,28 +32,36 @@ module Bullet
|
|
|
25
32
|
def add_possible_objects(object_or_objects)
|
|
26
33
|
return unless Bullet.start?
|
|
27
34
|
return unless Bullet.n_plus_one_query_enable?
|
|
35
|
+
|
|
28
36
|
objects = Array(object_or_objects)
|
|
29
|
-
return if objects.map(&:
|
|
37
|
+
return if objects.map(&:bullet_primary_key_value).compact.empty?
|
|
38
|
+
return if objects.all? { |obj| obj.class.name =~ /^HABTM_/ }
|
|
30
39
|
|
|
31
|
-
Bullet.debug(
|
|
40
|
+
Bullet.debug(
|
|
41
|
+
'Detector::NPlusOneQuery#add_possible_objects',
|
|
42
|
+
"objects: #{objects.map(&:bullet_key).join(', ')}"
|
|
43
|
+
)
|
|
32
44
|
objects.each { |object| possible_objects.add object.bullet_key }
|
|
33
45
|
end
|
|
34
46
|
|
|
35
47
|
def add_impossible_object(object)
|
|
36
48
|
return unless Bullet.start?
|
|
37
49
|
return unless Bullet.n_plus_one_query_enable?
|
|
38
|
-
return unless object.
|
|
50
|
+
return unless object.bullet_primary_key_value
|
|
39
51
|
|
|
40
|
-
Bullet.debug(
|
|
52
|
+
Bullet.debug('Detector::NPlusOneQuery#add_impossible_object', "object: #{object.bullet_key}")
|
|
41
53
|
impossible_objects.add object.bullet_key
|
|
42
54
|
end
|
|
43
55
|
|
|
44
56
|
def add_inversed_object(object, association)
|
|
45
57
|
return unless Bullet.start?
|
|
46
58
|
return unless Bullet.n_plus_one_query_enable?
|
|
47
|
-
return unless object.
|
|
59
|
+
return unless object.bullet_primary_key_value
|
|
48
60
|
|
|
49
|
-
Bullet.debug(
|
|
61
|
+
Bullet.debug(
|
|
62
|
+
'Detector::NPlusOneQuery#add_inversed_object',
|
|
63
|
+
"object: #{object.bullet_key}, association: #{association}"
|
|
64
|
+
)
|
|
50
65
|
inversed_objects.add object.bullet_key, association
|
|
51
66
|
end
|
|
52
67
|
|
|
@@ -66,28 +81,27 @@ module Bullet
|
|
|
66
81
|
# check if object => associations already exists in object_associations.
|
|
67
82
|
def association?(object, associations)
|
|
68
83
|
value = object_associations[object.bullet_key]
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return true if result
|
|
76
|
-
end
|
|
84
|
+
value&.each do |v|
|
|
85
|
+
# associations == v comparison order is important here because
|
|
86
|
+
# v variable might be a squeel node where :== method is redefined,
|
|
87
|
+
# so it does not compare values at all and return unexpected results
|
|
88
|
+
result = v.is_a?(Hash) ? v.key?(associations) : associations == v
|
|
89
|
+
return true if result
|
|
77
90
|
end
|
|
78
91
|
|
|
79
92
|
false
|
|
80
93
|
end
|
|
81
94
|
|
|
82
95
|
private
|
|
83
|
-
def create_notification(callers, klazz, associations)
|
|
84
|
-
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:n_plus_one_query, klazz)
|
|
85
96
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
97
|
+
def create_notification(callers, klazz, associations)
|
|
98
|
+
notify_associations = Array(associations) - Bullet.get_safelist_associations(:n_plus_one_query, klazz)
|
|
99
|
+
|
|
100
|
+
if notify_associations.present?
|
|
101
|
+
notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
|
|
102
|
+
Bullet.notification_collector.add(notice)
|
|
90
103
|
end
|
|
104
|
+
end
|
|
91
105
|
end
|
|
92
106
|
end
|
|
93
107
|
end
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Detector
|
|
3
5
|
class UnusedEagerLoading < Association
|
|
4
6
|
extend Dependency
|
|
5
7
|
extend StackTraceFilter
|
|
6
8
|
|
|
7
|
-
class <<self
|
|
9
|
+
class << self
|
|
8
10
|
# check if there are unused preload associations.
|
|
9
11
|
# get related_objects from eager_loadings associated with object and associations
|
|
10
12
|
# get call_object_association from associations of call_object_associations whose object is in related_objects
|
|
@@ -17,7 +19,7 @@ module Bullet
|
|
|
17
19
|
object_association_diff = diff_object_associations bullet_key, associations
|
|
18
20
|
next if object_association_diff.empty?
|
|
19
21
|
|
|
20
|
-
Bullet.debug(
|
|
22
|
+
Bullet.debug('detect unused preload', "object: #{bullet_key}, associations: #{object_association_diff}")
|
|
21
23
|
create_notification(caller_in_project, bullet_key.bullet_class_name, object_association_diff)
|
|
22
24
|
end
|
|
23
25
|
end
|
|
@@ -25,22 +27,27 @@ module Bullet
|
|
|
25
27
|
def add_eager_loadings(objects, associations)
|
|
26
28
|
return unless Bullet.start?
|
|
27
29
|
return unless Bullet.unused_eager_loading_enable?
|
|
28
|
-
return if objects.map(&:
|
|
30
|
+
return if objects.map(&:bullet_primary_key_value).compact.empty?
|
|
29
31
|
|
|
30
|
-
Bullet.debug(
|
|
32
|
+
Bullet.debug(
|
|
33
|
+
'Detector::UnusedEagerLoading#add_eager_loadings',
|
|
34
|
+
"objects: #{objects.map(&:bullet_key).join(', ')}, associations: #{associations}"
|
|
35
|
+
)
|
|
31
36
|
bullet_keys = objects.map(&:bullet_key)
|
|
32
37
|
|
|
33
|
-
to_add
|
|
34
|
-
|
|
38
|
+
to_add = []
|
|
39
|
+
to_merge = []
|
|
40
|
+
to_delete = []
|
|
41
|
+
eager_loadings.each do |k, _v|
|
|
35
42
|
key_objects_overlap = k & bullet_keys
|
|
36
43
|
|
|
37
44
|
next if key_objects_overlap.empty?
|
|
38
45
|
|
|
39
|
-
bullet_keys
|
|
46
|
+
bullet_keys -= k
|
|
40
47
|
if key_objects_overlap == k
|
|
41
48
|
to_add << [k, associations]
|
|
42
49
|
else
|
|
43
|
-
to_merge << [key_objects_overlap, (
|
|
50
|
+
to_merge << [key_objects_overlap, (eager_loadings[k].dup << associations)]
|
|
44
51
|
|
|
45
52
|
keys_without_objects = k - key_objects_overlap
|
|
46
53
|
to_merge << [keys_without_objects, eager_loadings[k]]
|
|
@@ -56,29 +63,31 @@ module Bullet
|
|
|
56
63
|
end
|
|
57
64
|
|
|
58
65
|
private
|
|
59
|
-
def create_notification(callers, klazz, associations)
|
|
60
|
-
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:unused_eager_loading, klazz)
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
Bullet.notification_collector.add(notice)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
+
def create_notification(callers, klazz, associations)
|
|
68
|
+
notify_associations = Array(associations) - Bullet.get_safelist_associations(:unused_eager_loading, klazz)
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
coa = call_object_associations[related_bullet_key]
|
|
72
|
-
next if coa.nil?
|
|
73
|
-
all.merge coa
|
|
74
|
-
end
|
|
75
|
-
all.to_a
|
|
70
|
+
if notify_associations.present?
|
|
71
|
+
notice = Bullet::Notification::UnusedEagerLoading.new(callers, klazz, notify_associations)
|
|
72
|
+
Bullet.notification_collector.add(notice)
|
|
76
73
|
end
|
|
74
|
+
end
|
|
77
75
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
def call_associations(bullet_key, associations)
|
|
77
|
+
all = Set.new
|
|
78
|
+
eager_loadings.similarly_associated(bullet_key, associations).each do |related_bullet_key|
|
|
79
|
+
coa = call_object_associations[related_bullet_key]
|
|
80
|
+
next if coa.nil?
|
|
81
|
+
|
|
82
|
+
all.merge coa
|
|
81
83
|
end
|
|
84
|
+
all.to_a
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def diff_object_associations(bullet_key, associations)
|
|
88
|
+
potential_associations = associations - call_associations(bullet_key, associations)
|
|
89
|
+
potential_associations.reject { |a| a.is_a?(Hash) }
|
|
90
|
+
end
|
|
82
91
|
end
|
|
83
92
|
end
|
|
84
93
|
end
|
data/lib/bullet/detector.rb
CHANGED
data/lib/bullet/ext/object.rb
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
class Object
|
|
2
4
|
def bullet_key
|
|
3
|
-
"#{self.class}:#{
|
|
5
|
+
"#{self.class}:#{bullet_primary_key_value}"
|
|
4
6
|
end
|
|
5
7
|
|
|
6
|
-
def
|
|
8
|
+
def bullet_primary_key_value
|
|
9
|
+
return if respond_to?(:persisted?) && !persisted?
|
|
10
|
+
|
|
7
11
|
if self.class.respond_to?(:primary_keys) && self.class.primary_keys
|
|
8
|
-
self.class.primary_keys.map { |primary_key|
|
|
12
|
+
self.class.primary_keys.map { |primary_key| send primary_key }.join(',')
|
|
9
13
|
elsif self.class.respond_to?(:primary_key) && self.class.primary_key
|
|
10
|
-
|
|
14
|
+
send self.class.primary_key
|
|
11
15
|
else
|
|
12
|
-
|
|
16
|
+
id
|
|
13
17
|
end
|
|
14
18
|
end
|
|
15
19
|
end
|
data/lib/bullet/ext/string.rb
CHANGED
data/lib/bullet/mongoid4x.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Mongoid
|
|
3
5
|
def self.enable
|
|
@@ -21,20 +23,21 @@ module Bullet
|
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def each(&block)
|
|
24
|
-
|
|
26
|
+
return to_enum unless block
|
|
27
|
+
|
|
28
|
+
records = []
|
|
29
|
+
origin_each { |record| records << record }
|
|
25
30
|
if records.length > 1
|
|
26
31
|
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
27
32
|
elsif records.size == 1
|
|
28
33
|
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
29
34
|
end
|
|
30
|
-
|
|
35
|
+
records.each(&block)
|
|
31
36
|
end
|
|
32
37
|
|
|
33
38
|
def eager_load(docs)
|
|
34
39
|
associations = criteria.inclusions.map(&:name)
|
|
35
|
-
docs.each
|
|
36
|
-
Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
|
|
37
|
-
end
|
|
40
|
+
docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }
|
|
38
41
|
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
|
|
39
42
|
origin_eager_load(docs)
|
|
40
43
|
end
|
|
@@ -45,9 +48,7 @@ module Bullet
|
|
|
45
48
|
|
|
46
49
|
def get_relation(name, metadata, object, reload = false)
|
|
47
50
|
result = origin_get_relation(name, metadata, object, reload)
|
|
48
|
-
if metadata.macro !~ /embed/
|
|
49
|
-
Bullet::Detector::NPlusOneQuery.call_association(self, name)
|
|
50
|
-
end
|
|
51
|
+
Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/
|
|
51
52
|
result
|
|
52
53
|
end
|
|
53
54
|
end
|