bullet 4.4.0 → 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/CHANGELOG.md +31 -0
- data/{Hacking.textile → Hacking.md} +10 -5
- data/{README.textile → README.md} +174 -144
- data/lib/bullet.rb +42 -7
- data/lib/bullet/active_record2.rb +9 -9
- data/lib/bullet/active_record3.rb +9 -9
- data/lib/bullet/active_record3x.rb +8 -8
- data/lib/bullet/active_record4.rb +8 -8
- data/lib/bullet/detector.rb +2 -2
- 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/mongoid2x.rb +5 -5
- data/lib/bullet/mongoid3x.rb +6 -6
- data/lib/bullet/version.rb +1 -1
- 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 +141 -49
- data/spec/integration/{counter_spec.rb → counter_cache_spec.rb} +25 -1
- 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 +15 -14
- data/spec/bullet/detector/counter_spec.rb +0 -64
- data/spec/bullet/detector/unused_eager_association_spec.rb +0 -62
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|
|
@@ -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
|
@@ -30,7 +30,7 @@ module Bullet
|
|
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
|
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -30,7 +30,7 @@ module Bullet
|
|
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
|
|
@@ -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
|
@@ -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
|
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
|
@@ -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.
|