bullet 4.3.1 → 4.5.0
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/.travis.yml +4 -4
- data/CHANGELOG.md +31 -0
- data/{Hacking.textile → Hacking.md} +10 -5
- data/{README.textile → README.md} +174 -144
- data/lib/bullet/active_record2.rb +9 -9
- data/lib/bullet/active_record3.rb +9 -9
- data/lib/bullet/active_record3x.rb +11 -11
- data/lib/bullet/active_record4.rb +94 -0
- data/lib/bullet/dependency.rb +3 -1
- data/lib/bullet/detector/association.rb +2 -43
- data/lib/bullet/detector/{counter.rb → counter_cache.rb} +13 -3
- data/lib/bullet/detector/n_plus_one_query.rb +21 -2
- data/lib/bullet/detector/{unused_eager_association.rb → unused_eager_loading.rb} +40 -3
- data/lib/bullet/detector.rb +2 -2
- data/lib/bullet/mongoid2x.rb +5 -5
- data/lib/bullet/mongoid3x.rb +6 -6
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +42 -7
- data/spec/bullet/detector/association_spec.rb +0 -49
- data/spec/bullet/detector/counter_cache_spec.rb +64 -0
- data/spec/bullet/detector/n_plus_one_query_spec.rb +23 -1
- data/spec/bullet/detector/unused_eager_loading_spec.rb +95 -0
- data/spec/integration/association_spec.rb +155 -63
- data/spec/integration/{counter_spec.rb → counter_cache_spec.rb} +26 -2
- data/spec/integration/mongoid/association_spec.rb +24 -24
- data/spec/integration/rails2/association_spec.rb +49 -49
- data/spec/integration/rails2/{counter_spec.rb → counter_cache_spec.rb} +13 -1
- metadata +17 -15
- data/spec/bullet/detector/counter_spec.rb +0 -64
- data/spec/bullet/detector/unused_eager_association_spec.rb +0 -62
|
@@ -12,11 +12,11 @@ module Bullet
|
|
|
12
12
|
|
|
13
13
|
if records
|
|
14
14
|
if records.size > 1
|
|
15
|
-
Bullet::Detector::
|
|
16
|
-
Bullet::Detector::
|
|
15
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
16
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
|
17
17
|
elsif records.size == 1
|
|
18
|
-
Bullet::Detector::
|
|
19
|
-
Bullet::Detector::
|
|
18
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
19
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -35,7 +35,7 @@ module Bullet
|
|
|
35
35
|
records.each do |record|
|
|
36
36
|
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
37
37
|
end
|
|
38
|
-
Bullet::Detector::
|
|
38
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
39
39
|
origin_preload_associations(records, associations, preload_options={})
|
|
40
40
|
end
|
|
41
41
|
end
|
|
@@ -50,7 +50,7 @@ module Bullet
|
|
|
50
50
|
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
51
51
|
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
52
52
|
end
|
|
53
|
-
Bullet::Detector::
|
|
53
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
54
54
|
records
|
|
55
55
|
end
|
|
56
56
|
end
|
|
@@ -94,7 +94,7 @@ module Bullet
|
|
|
94
94
|
# avoid stack level too deep
|
|
95
95
|
result = origin_load_target
|
|
96
96
|
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless caller.any? {|c| c.include?("load_target") }
|
|
97
|
-
Bullet::Detector::
|
|
97
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
|
|
98
98
|
result
|
|
99
99
|
end
|
|
100
100
|
end
|
|
@@ -103,7 +103,7 @@ module Bullet
|
|
|
103
103
|
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
|
104
104
|
def has_cached_counter?
|
|
105
105
|
result = origin_has_cached_counter?
|
|
106
|
-
Bullet::Detector::
|
|
106
|
+
Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) unless result
|
|
107
107
|
result
|
|
108
108
|
end
|
|
109
109
|
end
|
|
@@ -112,7 +112,7 @@ module Bullet
|
|
|
112
112
|
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
|
113
113
|
def has_cached_counter?
|
|
114
114
|
result = origin_has_cached_counter?
|
|
115
|
-
Bullet::Detector::
|
|
115
|
+
Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) unless result
|
|
116
116
|
result
|
|
117
117
|
end
|
|
118
118
|
end
|
|
@@ -9,11 +9,11 @@ module Bullet
|
|
|
9
9
|
def to_a
|
|
10
10
|
records = origin_to_a
|
|
11
11
|
if records.size > 1
|
|
12
|
-
Bullet::Detector::
|
|
13
|
-
Bullet::Detector::
|
|
12
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
13
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
|
14
14
|
elsif records.size == 1
|
|
15
|
-
Bullet::Detector::
|
|
16
|
-
Bullet::Detector::
|
|
15
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
16
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
|
17
17
|
end
|
|
18
18
|
records
|
|
19
19
|
end
|
|
@@ -29,7 +29,7 @@ module Bullet
|
|
|
29
29
|
records.each do |record|
|
|
30
30
|
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
31
31
|
end
|
|
32
|
-
Bullet::Detector::
|
|
32
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
33
33
|
origin_preload_associations(records, associations, preload_options={})
|
|
34
34
|
end
|
|
35
35
|
end
|
|
@@ -44,7 +44,7 @@ module Bullet
|
|
|
44
44
|
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
45
45
|
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
46
46
|
end
|
|
47
|
-
Bullet::Detector::
|
|
47
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
48
48
|
records
|
|
49
49
|
end
|
|
50
50
|
end
|
|
@@ -88,7 +88,7 @@ module Bullet
|
|
|
88
88
|
# avoid stack level too deep
|
|
89
89
|
result = origin_load_target
|
|
90
90
|
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless caller.any? { |c| c.include?("load_target") }
|
|
91
|
-
Bullet::Detector::
|
|
91
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
|
|
92
92
|
result
|
|
93
93
|
end
|
|
94
94
|
end
|
|
@@ -98,7 +98,7 @@ module Bullet
|
|
|
98
98
|
|
|
99
99
|
def has_cached_counter?
|
|
100
100
|
result = origin_has_cached_counter?
|
|
101
|
-
Bullet::Detector::
|
|
101
|
+
Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) unless result
|
|
102
102
|
result
|
|
103
103
|
end
|
|
104
104
|
end
|
|
@@ -107,7 +107,7 @@ module Bullet
|
|
|
107
107
|
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
|
108
108
|
def has_cached_counter?
|
|
109
109
|
result = origin_has_cached_counter?
|
|
110
|
-
Bullet::Detector::
|
|
110
|
+
Bullet::Detector::CounterCache.add_counter_cache(@owner, @reflection.name) unless result
|
|
111
111
|
result
|
|
112
112
|
end
|
|
113
113
|
end
|
|
@@ -9,11 +9,11 @@ module Bullet
|
|
|
9
9
|
def to_a
|
|
10
10
|
records = origin_to_a
|
|
11
11
|
if records.size > 1
|
|
12
|
-
Bullet::Detector::
|
|
13
|
-
Bullet::Detector::
|
|
12
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
13
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
|
14
14
|
elsif records.size == 1
|
|
15
|
-
Bullet::Detector::
|
|
16
|
-
Bullet::Detector::
|
|
15
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
16
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
|
17
17
|
end
|
|
18
18
|
records
|
|
19
19
|
end
|
|
@@ -23,14 +23,14 @@ module Bullet
|
|
|
23
23
|
# include query for one to many associations.
|
|
24
24
|
# keep this eager loadings.
|
|
25
25
|
alias_method :origin_initialize, :initialize
|
|
26
|
-
def initialize(records, associations,
|
|
27
|
-
origin_initialize(records, associations,
|
|
26
|
+
def initialize(records, associations, preload_scope = nil)
|
|
27
|
+
origin_initialize(records, associations, preload_scope)
|
|
28
28
|
records = [records].flatten.compact.uniq
|
|
29
29
|
return if records.empty?
|
|
30
30
|
records.each do |record|
|
|
31
31
|
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
32
32
|
end
|
|
33
|
-
Bullet::Detector::
|
|
33
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -39,12 +39,12 @@ module Bullet
|
|
|
39
39
|
alias_method :origin_find_with_associations, :find_with_associations
|
|
40
40
|
def find_with_associations
|
|
41
41
|
records = origin_find_with_associations
|
|
42
|
-
associations = (
|
|
42
|
+
associations = (eager_load_values + includes_values).uniq
|
|
43
43
|
records.each do |record|
|
|
44
44
|
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
45
45
|
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
46
46
|
end
|
|
47
|
-
Bullet::Detector::
|
|
47
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
48
48
|
records
|
|
49
49
|
end
|
|
50
50
|
end
|
|
@@ -75,7 +75,7 @@ module Bullet
|
|
|
75
75
|
def reader(force_reload = false)
|
|
76
76
|
result = origin_reader(force_reload)
|
|
77
77
|
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
78
|
-
Bullet::Detector::
|
|
78
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
|
|
79
79
|
result
|
|
80
80
|
end
|
|
81
81
|
end
|
|
@@ -85,7 +85,7 @@ module Bullet
|
|
|
85
85
|
|
|
86
86
|
def has_cached_counter?(reflection = reflection)
|
|
87
87
|
result = origin_has_cached_counter?(reflection)
|
|
88
|
-
Bullet::Detector::
|
|
88
|
+
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) unless result
|
|
89
89
|
result
|
|
90
90
|
end
|
|
91
91
|
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module Bullet
|
|
2
|
+
module ActiveRecord
|
|
3
|
+
def self.enable
|
|
4
|
+
require 'active_record'
|
|
5
|
+
::ActiveRecord::Relation.class_eval do
|
|
6
|
+
alias_method :origin_to_a, :to_a
|
|
7
|
+
# if select a collection of objects, then these objects have possible to cause N+1 query.
|
|
8
|
+
# if select only one object, then the only one object has impossible to cause N+1 query.
|
|
9
|
+
def to_a
|
|
10
|
+
records = origin_to_a
|
|
11
|
+
if records.size > 1
|
|
12
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
13
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
|
14
|
+
elsif records.size == 1
|
|
15
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
16
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
|
17
|
+
end
|
|
18
|
+
records
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
::ActiveRecord::Associations::Preloader.class_eval do
|
|
23
|
+
# include query for one to many associations.
|
|
24
|
+
# keep this eager loadings.
|
|
25
|
+
alias_method :origin_initialize, :initialize
|
|
26
|
+
def initialize(records, associations, preload_scope = nil)
|
|
27
|
+
origin_initialize(records, associations, preload_scope)
|
|
28
|
+
records = [records].flatten.compact.uniq
|
|
29
|
+
return if records.empty?
|
|
30
|
+
records.each do |record|
|
|
31
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
32
|
+
end
|
|
33
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
::ActiveRecord::FinderMethods.class_eval do
|
|
38
|
+
# add includes in scope
|
|
39
|
+
alias_method :origin_find_with_associations, :find_with_associations
|
|
40
|
+
def find_with_associations
|
|
41
|
+
records = origin_find_with_associations
|
|
42
|
+
associations = (eager_load_values + includes_values).uniq
|
|
43
|
+
records.each do |record|
|
|
44
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
45
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
46
|
+
end
|
|
47
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
48
|
+
records
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
::ActiveRecord::Associations::JoinDependency.class_eval do
|
|
53
|
+
alias_method :origin_construct_association, :construct_association
|
|
54
|
+
# call join associations
|
|
55
|
+
def construct_association(record, join, row)
|
|
56
|
+
associations = join.reflection.name
|
|
57
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
58
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
59
|
+
origin_construct_association(record, join, row)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
::ActiveRecord::Associations::CollectionAssociation.class_eval do
|
|
64
|
+
# call one to many associations
|
|
65
|
+
alias_method :origin_load_target, :load_target
|
|
66
|
+
def load_target
|
|
67
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
68
|
+
origin_load_target
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
::ActiveRecord::Associations::SingularAssociation.class_eval do
|
|
73
|
+
# call has_one and belongs_to associations
|
|
74
|
+
alias_method :origin_reader, :reader
|
|
75
|
+
def reader(force_reload = false)
|
|
76
|
+
result = origin_reader(force_reload)
|
|
77
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
78
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
|
|
79
|
+
result
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
::ActiveRecord::Associations::HasManyAssociation.class_eval do
|
|
84
|
+
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
|
85
|
+
|
|
86
|
+
def has_cached_counter?(reflection = reflection)
|
|
87
|
+
result = origin_has_cached_counter?(reflection)
|
|
88
|
+
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) unless result
|
|
89
|
+
result
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
data/lib/bullet/dependency.rb
CHANGED
|
@@ -24,8 +24,10 @@ module Bullet
|
|
|
24
24
|
'active_record2'
|
|
25
25
|
elsif active_record30?
|
|
26
26
|
'active_record3'
|
|
27
|
-
elsif active_record31? || active_record32?
|
|
27
|
+
elsif active_record31? || active_record32?
|
|
28
28
|
'active_record3x'
|
|
29
|
+
elsif active_record4?
|
|
30
|
+
'active_record4'
|
|
29
31
|
end
|
|
30
32
|
end
|
|
31
33
|
end
|
|
@@ -20,56 +20,15 @@ module Bullet
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def add_object_associations(object, associations)
|
|
23
|
+
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
|
|
23
24
|
object_associations.add(object.bullet_ar_key, associations) if object.id
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
def add_call_object_associations(object, associations)
|
|
28
|
+
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
|
|
27
29
|
call_object_associations.add(object.bullet_ar_key, associations) if object.id
|
|
28
30
|
end
|
|
29
31
|
|
|
30
|
-
def add_possible_objects(object_or_objects)
|
|
31
|
-
return unless object_or_objects
|
|
32
|
-
if object_or_objects.is_a? Array
|
|
33
|
-
object_or_objects.each { |object| possible_objects.add object.bullet_ar_key }
|
|
34
|
-
else
|
|
35
|
-
possible_objects.add object_or_objects.bullet_ar_key if object_or_objects.id
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def add_impossible_object(object)
|
|
40
|
-
impossible_objects.add object.bullet_ar_key if object.id
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def add_eager_loadings(objects, associations)
|
|
44
|
-
bullet_ar_keys = objects.map(&:bullet_ar_key)
|
|
45
|
-
|
|
46
|
-
to_add = nil
|
|
47
|
-
to_merge, to_delete = [], []
|
|
48
|
-
eager_loadings.each do |k, v|
|
|
49
|
-
key_objects_overlap = k & bullet_ar_keys
|
|
50
|
-
|
|
51
|
-
next if key_objects_overlap.empty?
|
|
52
|
-
|
|
53
|
-
if key_objects_overlap == k
|
|
54
|
-
to_add = [k, associations]
|
|
55
|
-
break
|
|
56
|
-
else
|
|
57
|
-
to_merge << [key_objects_overlap, ( eager_loadings[k].dup << associations )]
|
|
58
|
-
|
|
59
|
-
keys_without_objects = k - bullet_ar_keys
|
|
60
|
-
to_merge << [keys_without_objects, eager_loadings[k]]
|
|
61
|
-
to_delete << k
|
|
62
|
-
bullet_ar_keys = bullet_ar_keys - k
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
eager_loadings.add *to_add if to_add
|
|
67
|
-
to_merge.each { |k,val| eager_loadings.merge k, val }
|
|
68
|
-
to_delete.each { |k| eager_loadings.delete k }
|
|
69
|
-
|
|
70
|
-
eager_loadings.add bullet_ar_keys, associations unless bullet_ar_keys.empty?
|
|
71
|
-
end
|
|
72
|
-
|
|
73
32
|
private
|
|
74
33
|
# object_associations keep the object relationships
|
|
75
34
|
# that the object has many associations.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Bullet
|
|
2
2
|
module Detector
|
|
3
|
-
class
|
|
3
|
+
class CounterCache < Base
|
|
4
4
|
class <<self
|
|
5
5
|
def clear
|
|
6
6
|
@@possible_objects = nil
|
|
@@ -8,12 +8,16 @@ module Bullet
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def add_counter_cache(object, associations)
|
|
11
|
+
return unless Bullet.counter_cache_enable?
|
|
12
|
+
|
|
11
13
|
if conditions_met?(object.bullet_ar_key, associations)
|
|
12
14
|
create_notification object.class.to_s, associations
|
|
13
15
|
end
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
def add_possible_objects(object_or_objects)
|
|
19
|
+
return unless Bullet.counter_cache_enable?
|
|
20
|
+
|
|
17
21
|
if object_or_objects.is_a? Array
|
|
18
22
|
object_or_objects.each { |object| possible_objects.add object.bullet_ar_key }
|
|
19
23
|
else
|
|
@@ -22,13 +26,19 @@ module Bullet
|
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
def add_impossible_object(object)
|
|
29
|
+
return unless Bullet.counter_cache_enable?
|
|
30
|
+
|
|
25
31
|
impossible_objects.add object.bullet_ar_key
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
private
|
|
29
35
|
def create_notification(klazz, associations)
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:counter_cache, klazz)
|
|
37
|
+
|
|
38
|
+
if notify_associations.present?
|
|
39
|
+
notice = Bullet::Notification::CounterCache.new klazz, notify_associations
|
|
40
|
+
Bullet.notification_collector.add notice
|
|
41
|
+
end
|
|
32
42
|
end
|
|
33
43
|
|
|
34
44
|
def possible_objects
|
|
@@ -15,10 +15,29 @@ module Bullet
|
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
def add_possible_objects(object_or_objects)
|
|
19
|
+
return unless Bullet.n_plus_one_query_enable?
|
|
20
|
+
return if object_or_objects.blank?
|
|
21
|
+
if object_or_objects.is_a? Array
|
|
22
|
+
object_or_objects.each { |object| possible_objects.add object.bullet_ar_key }
|
|
23
|
+
else
|
|
24
|
+
possible_objects.add object_or_objects.bullet_ar_key if object_or_objects.id
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def add_impossible_object(object)
|
|
29
|
+
return unless Bullet.n_plus_one_query_enable?
|
|
30
|
+
impossible_objects.add object.bullet_ar_key if object.id
|
|
31
|
+
end
|
|
32
|
+
|
|
18
33
|
private
|
|
19
34
|
def create_notification(callers, klazz, associations)
|
|
20
|
-
|
|
21
|
-
|
|
35
|
+
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:n_plus_one_query, klazz)
|
|
36
|
+
|
|
37
|
+
if notify_associations.present?
|
|
38
|
+
notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
|
|
39
|
+
Bullet.notification_collector.add(notice)
|
|
40
|
+
end
|
|
22
41
|
end
|
|
23
42
|
|
|
24
43
|
# decide whether the object.associations is unpreloaded or not.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
module Bullet
|
|
2
2
|
module Detector
|
|
3
|
-
class
|
|
3
|
+
class UnusedEagerLoading < Association
|
|
4
4
|
class <<self
|
|
5
5
|
# check if there are unused preload associations.
|
|
6
6
|
# get related_objects from eager_loadings associated with object and associations
|
|
7
7
|
# get call_object_association from associations of call_object_associations whose object is in related_objects
|
|
8
8
|
# if association not in call_object_association, then the object => association - call_object_association is ununsed preload assocations
|
|
9
9
|
def check_unused_preload_associations
|
|
10
|
+
return unless Bullet.unused_eager_loading_enable?
|
|
11
|
+
|
|
10
12
|
@@checked = true
|
|
11
13
|
object_associations.each do |bullet_ar_key, associations|
|
|
12
14
|
object_association_diff = diff_object_associations bullet_ar_key, associations
|
|
@@ -16,10 +18,45 @@ module Bullet
|
|
|
16
18
|
end
|
|
17
19
|
end
|
|
18
20
|
|
|
21
|
+
def add_eager_loadings(objects, associations)
|
|
22
|
+
return unless Bullet.unused_eager_loading_enable?
|
|
23
|
+
bullet_ar_keys = objects.map(&:bullet_ar_key)
|
|
24
|
+
|
|
25
|
+
to_add = nil
|
|
26
|
+
to_merge, to_delete = [], []
|
|
27
|
+
eager_loadings.each do |k, v|
|
|
28
|
+
key_objects_overlap = k & bullet_ar_keys
|
|
29
|
+
|
|
30
|
+
next if key_objects_overlap.empty?
|
|
31
|
+
|
|
32
|
+
if key_objects_overlap == k
|
|
33
|
+
to_add = [k, associations]
|
|
34
|
+
break
|
|
35
|
+
else
|
|
36
|
+
to_merge << [key_objects_overlap, ( eager_loadings[k].dup << associations )]
|
|
37
|
+
|
|
38
|
+
keys_without_objects = k - bullet_ar_keys
|
|
39
|
+
to_merge << [keys_without_objects, eager_loadings[k]]
|
|
40
|
+
to_delete << k
|
|
41
|
+
bullet_ar_keys = bullet_ar_keys - k
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
eager_loadings.add *to_add if to_add
|
|
46
|
+
to_merge.each { |k,val| eager_loadings.merge k, val }
|
|
47
|
+
to_delete.each { |k| eager_loadings.delete k }
|
|
48
|
+
|
|
49
|
+
eager_loadings.add bullet_ar_keys, associations unless bullet_ar_keys.empty?
|
|
50
|
+
end
|
|
51
|
+
|
|
19
52
|
private
|
|
20
53
|
def create_notification(klazz, associations)
|
|
21
|
-
|
|
22
|
-
|
|
54
|
+
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:unused_eager_loading, klazz)
|
|
55
|
+
|
|
56
|
+
if notify_associations.present?
|
|
57
|
+
notice = Bullet::Notification::UnusedEagerLoading.new(klazz, notify_associations)
|
|
58
|
+
Bullet.notification_collector.add(notice)
|
|
59
|
+
end
|
|
23
60
|
end
|
|
24
61
|
|
|
25
62
|
def call_associations(bullet_ar_key, associations)
|
data/lib/bullet/detector.rb
CHANGED
|
@@ -3,7 +3,7 @@ module Bullet
|
|
|
3
3
|
autoload :Base, 'bullet/detector/base'
|
|
4
4
|
autoload :Association, 'bullet/detector/association'
|
|
5
5
|
autoload :NPlusOneQuery, 'bullet/detector/n_plus_one_query'
|
|
6
|
-
autoload :
|
|
7
|
-
autoload :
|
|
6
|
+
autoload :UnusedEagerLoading, 'bullet/detector/unused_eager_loading'
|
|
7
|
+
autoload :CounterCache, 'bullet/detector/counter_cache'
|
|
8
8
|
end
|
|
9
9
|
end
|
data/lib/bullet/mongoid2x.rb
CHANGED
|
@@ -11,22 +11,22 @@ module Bullet
|
|
|
11
11
|
|
|
12
12
|
def first
|
|
13
13
|
result = origin_first
|
|
14
|
-
Bullet::Detector::
|
|
14
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
15
15
|
result
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def last
|
|
19
19
|
result = origin_last
|
|
20
|
-
Bullet::Detector::
|
|
20
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
21
21
|
result
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def iterate(&block)
|
|
25
25
|
records = execute.to_a
|
|
26
26
|
if records.size > 1
|
|
27
|
-
Bullet::Detector::
|
|
27
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
28
28
|
elsif records.size == 1
|
|
29
|
-
Bullet::Detector::
|
|
29
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
30
30
|
end
|
|
31
31
|
origin_iterate(&block)
|
|
32
32
|
end
|
|
@@ -36,7 +36,7 @@ module Bullet
|
|
|
36
36
|
docs.each do |doc|
|
|
37
37
|
Bullet::Detector::Association.add_object_associations(doc, associations)
|
|
38
38
|
end
|
|
39
|
-
Bullet::Detector::
|
|
39
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
|
|
40
40
|
origin_eager_load(docs)
|
|
41
41
|
end
|
|
42
42
|
end
|
data/lib/bullet/mongoid3x.rb
CHANGED
|
@@ -10,22 +10,22 @@ module Bullet
|
|
|
10
10
|
|
|
11
11
|
def first
|
|
12
12
|
result = origin_first
|
|
13
|
-
Bullet::Detector::
|
|
13
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
14
14
|
result
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def last
|
|
18
18
|
result = origin_last
|
|
19
|
-
Bullet::Detector::
|
|
19
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
20
20
|
result
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def each(&block)
|
|
24
24
|
records = query.map{ |doc| ::Mongoid::Factory.from_db(klass, doc) }
|
|
25
25
|
if records.length > 1
|
|
26
|
-
Bullet::Detector::
|
|
26
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
27
27
|
elsif records.size == 1
|
|
28
|
-
Bullet::Detector::
|
|
28
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
29
29
|
end
|
|
30
30
|
origin_each(&block)
|
|
31
31
|
end
|
|
@@ -33,9 +33,9 @@ module Bullet
|
|
|
33
33
|
def eager_load(docs)
|
|
34
34
|
associations = criteria.inclusions.map(&:name)
|
|
35
35
|
docs.each do |doc|
|
|
36
|
-
Bullet::Detector::
|
|
36
|
+
Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
|
|
37
37
|
end
|
|
38
|
-
Bullet::Detector::
|
|
38
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
|
|
39
39
|
origin_eager_load(docs)
|
|
40
40
|
end
|
|
41
41
|
end
|
data/lib/bullet/version.rb
CHANGED
data/lib/bullet.rb
CHANGED
|
@@ -30,18 +30,19 @@ module Bullet
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
class <<self
|
|
33
|
-
|
|
34
|
-
attr_reader :notification_collector
|
|
33
|
+
attr_writer :enable, :n_plus_one_query_enable, :unused_eager_loading_enable, :counter_cache_enable
|
|
34
|
+
attr_reader :notification_collector, :whitelist
|
|
35
35
|
|
|
36
36
|
delegate :alert=, :console=, :growl=, :rails_logger=, :xmpp=, :airbrake=, :to => UniformNotifier
|
|
37
37
|
|
|
38
38
|
DETECTORS = [ Bullet::Detector::NPlusOneQuery,
|
|
39
|
-
Bullet::Detector::
|
|
40
|
-
Bullet::Detector::
|
|
39
|
+
Bullet::Detector::UnusedEagerLoading,
|
|
40
|
+
Bullet::Detector::CounterCache ]
|
|
41
41
|
|
|
42
42
|
def enable=(enable)
|
|
43
|
-
@enable = enable
|
|
43
|
+
@enable = @n_plus_one_query_enable = @unused_eager_loading_enable = @counter_cache_enable = enable
|
|
44
44
|
if enable?
|
|
45
|
+
reset_whitelist
|
|
45
46
|
if mongoid?
|
|
46
47
|
Bullet::Mongoid.enable
|
|
47
48
|
end
|
|
@@ -53,7 +54,32 @@ module Bullet
|
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def enable?
|
|
56
|
-
|
|
57
|
+
!!@enable
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def n_plus_one_query_enable?
|
|
61
|
+
self.enable? && !!@n_plus_one_query_enable
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def unused_eager_loading_enable?
|
|
65
|
+
self.enable? && !!@unused_eager_loading_enable
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def counter_cache_enable?
|
|
69
|
+
self.enable? && !!@counter_cache_enable
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def add_whitelist(options)
|
|
73
|
+
@whitelist[options[:type]][options[:class_name].classify] ||= []
|
|
74
|
+
@whitelist[options[:type]][options[:class_name].classify] << options[:association].to_s.tableize.to_sym
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def get_whitelist_associations(type, class_name)
|
|
78
|
+
Array(@whitelist[type][class_name])
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def reset_whitelist
|
|
82
|
+
@whitelist = {:n_plus_one_query => {}, :unused_eager_loading => {}, :counter_cache => {}}
|
|
57
83
|
end
|
|
58
84
|
|
|
59
85
|
def bullet_logger=(active)
|
|
@@ -82,7 +108,7 @@ module Bullet
|
|
|
82
108
|
end
|
|
83
109
|
|
|
84
110
|
def notification?
|
|
85
|
-
Bullet::Detector::
|
|
111
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
86
112
|
notification_collector.notifications_present?
|
|
87
113
|
end
|
|
88
114
|
|
|
@@ -101,6 +127,15 @@ module Bullet
|
|
|
101
127
|
end
|
|
102
128
|
end
|
|
103
129
|
|
|
130
|
+
def warnings
|
|
131
|
+
notification_collector.collection.inject({}) do |warnings, notification|
|
|
132
|
+
warning_type = notification.class.to_s.split(':').last.tableize
|
|
133
|
+
warnings[warning_type] ||= []
|
|
134
|
+
warnings[warning_type] << notification
|
|
135
|
+
warnings
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
104
139
|
private
|
|
105
140
|
def for_each_active_notifier_with_notification
|
|
106
141
|
UniformNotifier.active_notifiers.each do |notifier|
|