bullet 4.14.3 → 4.14.10
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 +28 -1
- data/CHANGELOG.md +38 -2
- data/Gemfile.mongoid +0 -2
- data/Gemfile.mongoid-2.4 +0 -2
- data/Gemfile.mongoid-2.5 +0 -2
- data/Gemfile.mongoid-2.6 +0 -2
- data/Gemfile.mongoid-2.7 +0 -2
- data/Gemfile.mongoid-2.8 +0 -2
- data/Gemfile.mongoid-3.0 +0 -2
- data/Gemfile.mongoid-3.1 +0 -2
- data/Gemfile.mongoid-4.0 +0 -2
- data/Gemfile.mongoid-5.0 +17 -0
- data/Gemfile.rails-3.1 +0 -2
- data/Gemfile.rails-3.2 +0 -2
- data/Gemfile.rails-4.0 +0 -2
- data/Gemfile.rails-4.1 +0 -2
- data/Gemfile.rails-4.2 +0 -2
- data/README.md +27 -1
- data/bullet.gemspec +1 -1
- data/lib/bullet/active_record3.rb +65 -35
- data/lib/bullet/active_record3x.rb +54 -32
- data/lib/bullet/active_record4.rb +61 -31
- data/lib/bullet/active_record41.rb +63 -32
- data/lib/bullet/active_record42.rb +105 -43
- data/lib/bullet/dependency.rb +6 -0
- data/lib/bullet/detector/association.rb +16 -16
- data/lib/bullet/detector/counter_cache.rb +13 -13
- data/lib/bullet/detector/n_plus_one_query.rb +30 -27
- data/lib/bullet/detector/unused_eager_loading.rb +1 -1
- data/lib/bullet/ext/object.rb +3 -1
- data/lib/bullet/mongoid5x.rb +56 -0
- data/lib/bullet/notification/n_plus_one_query.rb +6 -0
- data/lib/bullet/rack.rb +4 -6
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +23 -10
- data/spec/bullet/detector/counter_cache_spec.rb +8 -8
- data/spec/bullet/detector/n_plus_one_query_spec.rb +27 -27
- data/spec/bullet/ext/object_spec.rb +6 -0
- data/spec/bullet/notification/base_spec.rb +2 -2
- data/spec/bullet/rack_spec.rb +2 -2
- data/spec/bullet_spec.rb +47 -0
- data/spec/integration/active_record3/association_spec.rb +11 -2
- data/spec/integration/active_record4/association_spec.rb +32 -2
- data/spec/integration/counter_cache_spec.rb +8 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/support/mongo_seed.rb +13 -0
- data/test.sh +1 -0
- data/update.sh +1 -0
- metadata +9 -7
|
@@ -2,19 +2,50 @@ module Bullet
|
|
|
2
2
|
module ActiveRecord
|
|
3
3
|
def self.enable
|
|
4
4
|
require 'active_record'
|
|
5
|
+
::ActiveRecord::Base.class_eval do
|
|
6
|
+
class <<self
|
|
7
|
+
alias_method :origin_find, :find
|
|
8
|
+
def find(*args)
|
|
9
|
+
result = origin_find(*args)
|
|
10
|
+
if Bullet.start?
|
|
11
|
+
if result.is_a? Array
|
|
12
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
|
|
13
|
+
Bullet::Detector::CounterCache.add_possible_objects(result)
|
|
14
|
+
elsif result.is_a? ::ActiveRecord::Base
|
|
15
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
|
|
16
|
+
Bullet::Detector::CounterCache.add_impossible_object(result)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
result
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
::ActiveRecord::Persistence.class_eval do
|
|
25
|
+
def save_with_bullet(*args, &proc)
|
|
26
|
+
was_new_record = new_record?
|
|
27
|
+
save_without_bullet(*args, &proc).tap do |result|
|
|
28
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
alias_method_chain :save, :bullet
|
|
32
|
+
end
|
|
33
|
+
|
|
5
34
|
::ActiveRecord::Relation.class_eval do
|
|
6
35
|
alias_method :origin_to_a, :to_a
|
|
7
36
|
# if select a collection of objects, then these objects have possible to cause N+1 query.
|
|
8
37
|
# if select only one object, then the only one object has impossible to cause N+1 query.
|
|
9
38
|
def to_a
|
|
10
39
|
records = origin_to_a
|
|
11
|
-
if
|
|
12
|
-
if records.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
40
|
+
if Bullet.start?
|
|
41
|
+
if records.first.class.name !~ /^HABTM_/
|
|
42
|
+
if records.size > 1
|
|
43
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
44
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
|
45
|
+
elsif records.size == 1
|
|
46
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
47
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
|
48
|
+
end
|
|
18
49
|
end
|
|
19
50
|
end
|
|
20
51
|
records
|
|
@@ -25,12 +56,14 @@ module Bullet
|
|
|
25
56
|
alias_method :origin_preloaders_on, :preloaders_on
|
|
26
57
|
|
|
27
58
|
def preloaders_on(association, records, scope)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
records.
|
|
31
|
-
|
|
59
|
+
if Bullet.start?
|
|
60
|
+
records.compact!
|
|
61
|
+
if records.first.class.name !~ /^HABTM_/
|
|
62
|
+
records.each do |record|
|
|
63
|
+
Bullet::Detector::Association.add_object_associations(record, association)
|
|
64
|
+
end
|
|
65
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
|
|
32
66
|
end
|
|
33
|
-
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
|
|
34
67
|
end
|
|
35
68
|
origin_preloaders_on(association, records, scope)
|
|
36
69
|
end
|
|
@@ -40,12 +73,15 @@ module Bullet
|
|
|
40
73
|
# add includes in scope
|
|
41
74
|
alias_method :origin_find_with_associations, :find_with_associations
|
|
42
75
|
def find_with_associations
|
|
76
|
+
return origin_find_with_associations { |r| yield r } if block_given?
|
|
43
77
|
records = origin_find_with_associations
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
78
|
+
if Bullet.start?
|
|
79
|
+
associations = (eager_load_values + includes_values).uniq
|
|
80
|
+
records.each do |record|
|
|
81
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
82
|
+
end
|
|
83
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
47
84
|
end
|
|
48
|
-
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
|
49
85
|
records
|
|
50
86
|
end
|
|
51
87
|
end
|
|
@@ -58,9 +94,11 @@ module Bullet
|
|
|
58
94
|
@bullet_eager_loadings = {}
|
|
59
95
|
records = origin_instantiate(result_set, aliases)
|
|
60
96
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
97
|
+
if Bullet.start?
|
|
98
|
+
@bullet_eager_loadings.each do |klazz, eager_loadings_hash|
|
|
99
|
+
objects = eager_loadings_hash.keys
|
|
100
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
|
|
101
|
+
end
|
|
64
102
|
end
|
|
65
103
|
records
|
|
66
104
|
end
|
|
@@ -69,12 +107,14 @@ module Bullet
|
|
|
69
107
|
def construct_model(record, node, row, model_cache, id, aliases)
|
|
70
108
|
result = origin_construct_model(record, node, row, model_cache, id, aliases)
|
|
71
109
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
110
|
+
if Bullet.start?
|
|
111
|
+
associations = node.reflection.name
|
|
112
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
113
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
114
|
+
@bullet_eager_loadings[record.class] ||= {}
|
|
115
|
+
@bullet_eager_loadings[record.class][record] ||= Set.new
|
|
116
|
+
@bullet_eager_loadings[record.class][record] << associations
|
|
117
|
+
end
|
|
78
118
|
|
|
79
119
|
result
|
|
80
120
|
end
|
|
@@ -84,16 +124,18 @@ module Bullet
|
|
|
84
124
|
# call one to many associations
|
|
85
125
|
alias_method :origin_load_target, :load_target
|
|
86
126
|
def load_target
|
|
87
|
-
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
|
|
88
127
|
records = origin_load_target
|
|
89
128
|
|
|
90
|
-
if
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
129
|
+
if Bullet.start?
|
|
130
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
|
|
131
|
+
if records.first.class.name !~ /^HABTM_/
|
|
132
|
+
if records.size > 1
|
|
133
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
134
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
|
135
|
+
elsif records.size == 1
|
|
136
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
137
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
|
138
|
+
end
|
|
97
139
|
end
|
|
98
140
|
end
|
|
99
141
|
records
|
|
@@ -101,13 +143,17 @@ module Bullet
|
|
|
101
143
|
|
|
102
144
|
alias_method :origin_empty?, :empty?
|
|
103
145
|
def empty?
|
|
104
|
-
Bullet
|
|
146
|
+
if Bullet.start?
|
|
147
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
148
|
+
end
|
|
105
149
|
origin_empty?
|
|
106
150
|
end
|
|
107
151
|
|
|
108
152
|
alias_method :origin_include?, :include?
|
|
109
153
|
def include?(object)
|
|
110
|
-
Bullet
|
|
154
|
+
if Bullet.start?
|
|
155
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
156
|
+
end
|
|
111
157
|
origin_include?(object)
|
|
112
158
|
end
|
|
113
159
|
end
|
|
@@ -117,12 +163,15 @@ module Bullet
|
|
|
117
163
|
alias_method :origin_reader, :reader
|
|
118
164
|
def reader(force_reload = false)
|
|
119
165
|
result = origin_reader(force_reload)
|
|
120
|
-
if
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
166
|
+
if Bullet.start?
|
|
167
|
+
if @owner.class.name !~ /^HABTM_/ && !@inversed
|
|
168
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
169
|
+
if Bullet::Detector::NPlusOneQuery.impossible?(@owner)
|
|
170
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
171
|
+
else
|
|
172
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
|
|
173
|
+
end
|
|
174
|
+
end
|
|
126
175
|
end
|
|
127
176
|
result
|
|
128
177
|
end
|
|
@@ -134,14 +183,27 @@ module Bullet
|
|
|
134
183
|
Thread.current[:bullet_collection_empty] = true
|
|
135
184
|
result = origin_many_empty?
|
|
136
185
|
Thread.current[:bullet_collection_empty] = nil
|
|
137
|
-
Bullet
|
|
186
|
+
if Bullet.start?
|
|
187
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
188
|
+
end
|
|
138
189
|
result
|
|
139
190
|
end
|
|
140
191
|
|
|
141
192
|
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
|
142
193
|
def has_cached_counter?(reflection = reflection())
|
|
143
194
|
result = origin_has_cached_counter?(reflection)
|
|
144
|
-
if !result && !Thread.current[:bullet_collection_empty]
|
|
195
|
+
if Bullet.start? && !result && !Thread.current[:bullet_collection_empty]
|
|
196
|
+
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
|
|
197
|
+
end
|
|
198
|
+
result
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
::ActiveRecord::Associations::HasManyThroughAssociation.class_eval do
|
|
203
|
+
alias_method :origin_hmt_has_cached_counter?, :has_cached_counter?
|
|
204
|
+
def has_cached_counter?(reflection = reflection())
|
|
205
|
+
result = origin_hmt_has_cached_counter?(reflection)
|
|
206
|
+
if Bullet.start? && !result && !Thread.current[:bullet_collection_empty]
|
|
145
207
|
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
|
|
146
208
|
end
|
|
147
209
|
result
|
data/lib/bullet/dependency.rb
CHANGED
|
@@ -36,6 +36,8 @@ module Bullet
|
|
|
36
36
|
'mongoid3x'
|
|
37
37
|
elsif mongoid4x?
|
|
38
38
|
'mongoid4x'
|
|
39
|
+
elsif mongoid5x?
|
|
40
|
+
'mongoid5x'
|
|
39
41
|
end
|
|
40
42
|
end
|
|
41
43
|
end
|
|
@@ -83,5 +85,9 @@ module Bullet
|
|
|
83
85
|
def mongoid4x?
|
|
84
86
|
mongoid? && ::Mongoid::VERSION =~ /\A4/
|
|
85
87
|
end
|
|
88
|
+
|
|
89
|
+
def mongoid5x?
|
|
90
|
+
mongoid? && ::Mongoid::VERSION =~ /\A5/
|
|
91
|
+
end
|
|
86
92
|
end
|
|
87
93
|
end
|
|
@@ -20,6 +20,22 @@ module Bullet
|
|
|
20
20
|
call_object_associations.add(object.bullet_key, associations)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# possible_objects keep the class to object relationships
|
|
24
|
+
# that the objects may cause N+1 query.
|
|
25
|
+
# e.g. { Post => ["Post:1", "Post:2"] }
|
|
26
|
+
def possible_objects
|
|
27
|
+
Thread.current[:bullet_possible_objects]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# impossible_objects keep the class to objects relationships
|
|
31
|
+
# that the objects may not cause N+1 query.
|
|
32
|
+
# e.g. { Post => ["Post:1", "Post:2"] }
|
|
33
|
+
# if find collection returns only one object, then the object is impossible object,
|
|
34
|
+
# impossible_objects are used to avoid treating 1+1 query to N+1 query.
|
|
35
|
+
def impossible_objects
|
|
36
|
+
Thread.current[:bullet_impossible_objects]
|
|
37
|
+
end
|
|
38
|
+
|
|
23
39
|
private
|
|
24
40
|
# object_associations keep the object relationships
|
|
25
41
|
# that the object has many associations.
|
|
@@ -38,22 +54,6 @@ module Bullet
|
|
|
38
54
|
Thread.current[:bullet_call_object_associations]
|
|
39
55
|
end
|
|
40
56
|
|
|
41
|
-
# possible_objects keep the class to object relationships
|
|
42
|
-
# that the objects may cause N+1 query.
|
|
43
|
-
# e.g. { Post => ["Post:1", "Post:2"] }
|
|
44
|
-
def possible_objects
|
|
45
|
-
Thread.current[:bullet_possible_objects]
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# impossible_objects keep the class to objects relationships
|
|
49
|
-
# that the objects may not cause N+1 query.
|
|
50
|
-
# e.g. { Post => ["Post:1", "Post:2"] }
|
|
51
|
-
# if find collection returns only one object, then the object is impossible object,
|
|
52
|
-
# impossible_objects are used to avoid treating 1+1 query to N+1 query.
|
|
53
|
-
def impossible_objects
|
|
54
|
-
Thread.current[:bullet_impossible_objects]
|
|
55
|
-
end
|
|
56
|
-
|
|
57
57
|
# inversed_objects keeps object relationships
|
|
58
58
|
# that association is inversed.
|
|
59
59
|
# e.g. { "Comment:1" => ["post"] }
|
|
@@ -8,7 +8,7 @@ module Bullet
|
|
|
8
8
|
return unless object.primary_key_value
|
|
9
9
|
|
|
10
10
|
Bullet.debug("Detector::CounterCache#add_counter_cache", "object: #{object.bullet_key}, associations: #{associations}")
|
|
11
|
-
if conditions_met?(object
|
|
11
|
+
if conditions_met?(object, associations)
|
|
12
12
|
create_notification object.class.to_s, associations
|
|
13
13
|
end
|
|
14
14
|
end
|
|
@@ -32,6 +32,18 @@ module Bullet
|
|
|
32
32
|
impossible_objects.add object.bullet_key
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
def conditions_met?(object, associations)
|
|
36
|
+
possible_objects.include?(object.bullet_key) && !impossible_objects.include?(object.bullet_key)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def possible_objects
|
|
40
|
+
Thread.current[:bullet_counter_possible_objects]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def impossible_objects
|
|
44
|
+
Thread.current[:bullet_counter_impossible_objects]
|
|
45
|
+
end
|
|
46
|
+
|
|
35
47
|
private
|
|
36
48
|
def create_notification(klazz, associations)
|
|
37
49
|
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:counter_cache, klazz)
|
|
@@ -41,18 +53,6 @@ module Bullet
|
|
|
41
53
|
Bullet.notification_collector.add notice
|
|
42
54
|
end
|
|
43
55
|
end
|
|
44
|
-
|
|
45
|
-
def possible_objects
|
|
46
|
-
Thread.current[:bullet_counter_possible_objects]
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def impossible_objects
|
|
50
|
-
Thread.current[:bullet_counter_impossible_objects]
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def conditions_met?(bullet_key, associations)
|
|
54
|
-
possible_objects.include?(bullet_key) && !impossible_objects.include?(bullet_key)
|
|
55
|
-
end
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
end
|
|
@@ -15,7 +15,7 @@ module Bullet
|
|
|
15
15
|
add_call_object_associations(object, associations)
|
|
16
16
|
|
|
17
17
|
Bullet.debug("Detector::NPlusOneQuery#call_association", "object: #{object.bullet_key}, associations: #{associations}")
|
|
18
|
-
if conditions_met?(object
|
|
18
|
+
if conditions_met?(object, associations)
|
|
19
19
|
Bullet.debug("detect n + 1 query", "object: #{object.bullet_key}, associations: #{associations}")
|
|
20
20
|
create_notification caller_in_project, object.class.to_s, associations
|
|
21
21
|
end
|
|
@@ -49,6 +49,35 @@ module Bullet
|
|
|
49
49
|
inversed_objects.add object.bullet_key, association
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
+
# decide whether the object.associations is unpreloaded or not.
|
|
53
|
+
def conditions_met?(object, associations)
|
|
54
|
+
possible?(object) && !impossible?(object) && !association?(object, associations)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def possible?(object)
|
|
58
|
+
possible_objects.include? object.bullet_key
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def impossible?(object)
|
|
62
|
+
impossible_objects.include? object.bullet_key
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# check if object => associations already exists in object_associations.
|
|
66
|
+
def association?(object, associations)
|
|
67
|
+
value = object_associations[object.bullet_key]
|
|
68
|
+
if value
|
|
69
|
+
value.each do |v|
|
|
70
|
+
# associations == v comparision order is important here because
|
|
71
|
+
# v variable might be a squeel node where :== method is redefined,
|
|
72
|
+
# so it does not compare values at all and return unexpected results
|
|
73
|
+
result = v.is_a?(Hash) ? v.key?(associations) : associations == v
|
|
74
|
+
return true if result
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
false
|
|
79
|
+
end
|
|
80
|
+
|
|
52
81
|
private
|
|
53
82
|
def create_notification(callers, klazz, associations)
|
|
54
83
|
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:n_plus_one_query, klazz)
|
|
@@ -59,11 +88,6 @@ module Bullet
|
|
|
59
88
|
end
|
|
60
89
|
end
|
|
61
90
|
|
|
62
|
-
# decide whether the object.associations is unpreloaded or not.
|
|
63
|
-
def conditions_met?(bullet_key, associations)
|
|
64
|
-
possible?(bullet_key) && !impossible?(bullet_key) && !association?(bullet_key, associations)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
91
|
def caller_in_project
|
|
68
92
|
app_root = rails? ? Rails.root.to_s : Dir.pwd
|
|
69
93
|
vendor_root = app_root + "/vendor"
|
|
@@ -72,27 +96,6 @@ module Bullet
|
|
|
72
96
|
Bullet.stacktrace_includes.any? { |include| c.include?(include) }
|
|
73
97
|
end
|
|
74
98
|
end
|
|
75
|
-
|
|
76
|
-
def possible?(bullet_key)
|
|
77
|
-
possible_objects.include? bullet_key
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def impossible?(bullet_key)
|
|
81
|
-
impossible_objects.include? bullet_key
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# check if object => associations already exists in object_associations.
|
|
85
|
-
def association?(bullet_key, associations)
|
|
86
|
-
value = object_associations[bullet_key]
|
|
87
|
-
if value
|
|
88
|
-
value.each do |v|
|
|
89
|
-
result = v.is_a?(Hash) ? v.has_key?(associations) : v == associations
|
|
90
|
-
return true if result
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
return false
|
|
95
|
-
end
|
|
96
99
|
end
|
|
97
100
|
end
|
|
98
101
|
end
|
|
@@ -48,7 +48,7 @@ module Bullet
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
eager_loadings.add(*to_add) if to_add
|
|
51
|
-
to_merge.each { |k,val| eager_loadings.merge k, val }
|
|
51
|
+
to_merge.each { |k, val| eager_loadings.merge k, val }
|
|
52
52
|
to_delete.each { |k| eager_loadings.delete k }
|
|
53
53
|
|
|
54
54
|
eager_loadings.add bullet_keys, associations unless bullet_keys.empty?
|
data/lib/bullet/ext/object.rb
CHANGED
|
@@ -4,7 +4,9 @@ class Object
|
|
|
4
4
|
end
|
|
5
5
|
|
|
6
6
|
def primary_key_value
|
|
7
|
-
if self.class.respond_to?(:
|
|
7
|
+
if self.class.respond_to?(:primary_keys) && self.class.primary_keys
|
|
8
|
+
self.class.primary_keys.map { |primary_key| self.send primary_key }.join(',')
|
|
9
|
+
elsif self.class.respond_to?(:primary_key) && self.class.primary_key
|
|
8
10
|
self.send self.class.primary_key
|
|
9
11
|
else
|
|
10
12
|
self.id
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Bullet
|
|
2
|
+
module Mongoid
|
|
3
|
+
def self.enable
|
|
4
|
+
require 'mongoid'
|
|
5
|
+
::Mongoid::Contextual::Mongo.class_eval do
|
|
6
|
+
alias_method :origin_first, :first
|
|
7
|
+
alias_method :origin_last, :last
|
|
8
|
+
alias_method :origin_each, :each
|
|
9
|
+
alias_method :origin_eager_load, :eager_load
|
|
10
|
+
|
|
11
|
+
def first
|
|
12
|
+
result = origin_first
|
|
13
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
14
|
+
result
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def last
|
|
18
|
+
result = origin_last
|
|
19
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
20
|
+
result
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def each(&block)
|
|
24
|
+
records = view.map{ |doc| ::Mongoid::Factory.from_db(klass, doc) }
|
|
25
|
+
if records.length > 1
|
|
26
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
27
|
+
elsif records.size == 1
|
|
28
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
29
|
+
end
|
|
30
|
+
origin_each(&block)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def eager_load(docs)
|
|
34
|
+
associations = criteria.inclusions.map(&:name)
|
|
35
|
+
docs.each do |doc|
|
|
36
|
+
Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
|
|
37
|
+
end
|
|
38
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
|
|
39
|
+
origin_eager_load(docs)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
::Mongoid::Relations::Accessors.class_eval do
|
|
44
|
+
alias_method :origin_get_relation, :get_relation
|
|
45
|
+
|
|
46
|
+
def get_relation(name, metadata, object, reload = false)
|
|
47
|
+
result = origin_get_relation(name, metadata, object, reload)
|
|
48
|
+
if metadata.macro !~ /embed/
|
|
49
|
+
Bullet::Detector::NPlusOneQuery.call_association(self, name)
|
|
50
|
+
end
|
|
51
|
+
result
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -15,6 +15,12 @@ module Bullet
|
|
|
15
15
|
"N+1 Query #{@path ? "in #{@path}" : 'detected'}"
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
def notification_data
|
|
19
|
+
super.merge(
|
|
20
|
+
:backtrace => @callers
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
18
24
|
protected
|
|
19
25
|
def call_stack_messages
|
|
20
26
|
(['N+1 Query method call stack'] + @callers).join( "\n " )
|
data/lib/bullet/rack.rb
CHANGED
|
@@ -10,18 +10,16 @@ module Bullet
|
|
|
10
10
|
return @app.call(env) unless Bullet.enable?
|
|
11
11
|
Bullet.start_request
|
|
12
12
|
status, headers, response = @app.call(env)
|
|
13
|
-
return [status, headers, response] if file?(headers) || sse?(response) || empty?(response)
|
|
14
13
|
|
|
15
14
|
response_body = nil
|
|
16
15
|
if Bullet.notification?
|
|
17
|
-
if
|
|
16
|
+
if !file?(headers) && !sse?(headers) && !empty?(response) &&
|
|
17
|
+
status == 200 && !response_body(response).frozen? && html_request?(headers, response)
|
|
18
18
|
response_body = response_body(response)
|
|
19
19
|
append_to_html_body(response_body, footer_note) if Bullet.add_footer
|
|
20
20
|
append_to_html_body(response_body, Bullet.gather_inline_notifications)
|
|
21
21
|
headers['Content-Length'] = response_body.bytesize.to_s
|
|
22
22
|
end
|
|
23
|
-
end
|
|
24
|
-
if Bullet.enable? && Bullet.notification?
|
|
25
23
|
Bullet.perform_out_of_channel_notifications(env)
|
|
26
24
|
end
|
|
27
25
|
[status, headers, response_body ? [response_body] : response]
|
|
@@ -60,8 +58,8 @@ module Bullet
|
|
|
60
58
|
headers["Content-Transfer-Encoding"] == "binary"
|
|
61
59
|
end
|
|
62
60
|
|
|
63
|
-
def sse?(
|
|
64
|
-
|
|
61
|
+
def sse?(headers)
|
|
62
|
+
headers["Content-Type"] == "text/event-stream"
|
|
65
63
|
end
|
|
66
64
|
|
|
67
65
|
def html_request?(headers, response)
|
data/lib/bullet/version.rb
CHANGED
data/lib/bullet.rb
CHANGED
|
@@ -16,6 +16,9 @@ module Bullet
|
|
|
16
16
|
autoload :Registry, 'bullet/registry'
|
|
17
17
|
autoload :NotificationCollector, 'bullet/notification_collector'
|
|
18
18
|
|
|
19
|
+
BULLET_DEBUG = 'BULLET_DEBUG'.freeze
|
|
20
|
+
TRUE = 'true'.freeze
|
|
21
|
+
|
|
19
22
|
if defined? Rails::Railtie
|
|
20
23
|
class BulletRailtie < Rails::Railtie
|
|
21
24
|
initializer "bullet.configure_rails_initialization" do |app|
|
|
@@ -29,7 +32,9 @@ module Bullet
|
|
|
29
32
|
attr_reader :notification_collector, :whitelist
|
|
30
33
|
attr_accessor :add_footer, :orm_pathches_applied
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
available_notifiers = UniformNotifier::AVAILABLE_NOTIFIERS.map { |notifier| "#{notifier}=" }
|
|
36
|
+
available_notifiers << { :to => UniformNotifier }
|
|
37
|
+
delegate *available_notifiers
|
|
33
38
|
|
|
34
39
|
def raise=(should_raise)
|
|
35
40
|
UniformNotifier.raise=(should_raise ? Notification::UnoptimizedQueryError : false)
|
|
@@ -72,6 +77,7 @@ module Bullet
|
|
|
72
77
|
end
|
|
73
78
|
|
|
74
79
|
def add_whitelist(options)
|
|
80
|
+
reset_whitelist
|
|
75
81
|
@whitelist[options[:type]][options[:class_name].classify] ||= []
|
|
76
82
|
@whitelist[options[:type]][options[:class_name].classify] << options[:association].to_sym
|
|
77
83
|
end
|
|
@@ -81,7 +87,11 @@ module Bullet
|
|
|
81
87
|
end
|
|
82
88
|
|
|
83
89
|
def reset_whitelist
|
|
84
|
-
@whitelist
|
|
90
|
+
@whitelist ||= {:n_plus_one_query => {}, :unused_eager_loading => {}, :counter_cache => {}}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def clear_whitelist
|
|
94
|
+
@whitelist = nil
|
|
85
95
|
end
|
|
86
96
|
|
|
87
97
|
def bullet_logger=(active)
|
|
@@ -96,7 +106,7 @@ module Bullet
|
|
|
96
106
|
end
|
|
97
107
|
|
|
98
108
|
def debug(title, message)
|
|
99
|
-
puts "[Bullet][#{title}] #{message}" if ENV[
|
|
109
|
+
puts "[Bullet][#{title}] #{message}" if ENV[BULLET_DEBUG] == TRUE
|
|
100
110
|
end
|
|
101
111
|
|
|
102
112
|
def start_request
|
|
@@ -130,7 +140,7 @@ module Bullet
|
|
|
130
140
|
end
|
|
131
141
|
|
|
132
142
|
def start?
|
|
133
|
-
Thread.current[:bullet_start]
|
|
143
|
+
enable? && Thread.current[:bullet_start]
|
|
134
144
|
end
|
|
135
145
|
|
|
136
146
|
def notification_collector
|
|
@@ -160,7 +170,7 @@ module Bullet
|
|
|
160
170
|
|
|
161
171
|
def footer_info
|
|
162
172
|
info = []
|
|
163
|
-
|
|
173
|
+
notification_collector.collection.each do |notification|
|
|
164
174
|
info << notification.short_notice
|
|
165
175
|
end
|
|
166
176
|
info
|
|
@@ -176,14 +186,17 @@ module Bullet
|
|
|
176
186
|
end
|
|
177
187
|
|
|
178
188
|
def profile
|
|
179
|
-
|
|
189
|
+
if Bullet.enable?
|
|
190
|
+
begin
|
|
191
|
+
Bullet.start_request
|
|
180
192
|
|
|
181
|
-
|
|
193
|
+
yield
|
|
182
194
|
|
|
183
|
-
|
|
184
|
-
|
|
195
|
+
Bullet.perform_out_of_channel_notifications if Bullet.notification?
|
|
196
|
+
ensure
|
|
197
|
+
Bullet.end_request
|
|
198
|
+
end
|
|
185
199
|
end
|
|
186
|
-
Bullet.end_request if Bullet.enable?
|
|
187
200
|
end
|
|
188
201
|
|
|
189
202
|
private
|