bullet_instructure 4.0.5 → 4.14.7
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 +27 -1
- data/CHANGELOG.md +44 -2
- data/Gemfile.mongoid +0 -2
- data/Gemfile.mongoid-2.4 +2 -4
- data/Gemfile.mongoid-2.5 +2 -4
- data/Gemfile.mongoid-2.6 +1 -3
- data/Gemfile.mongoid-2.7 +2 -4
- data/Gemfile.mongoid-2.8 +2 -4
- data/Gemfile.mongoid-3.0 +2 -4
- data/Gemfile.mongoid-3.1 +2 -4
- data/Gemfile.mongoid-4.0 +2 -4
- data/Gemfile.rails-3.0 +1 -3
- data/Gemfile.rails-3.1 +1 -3
- data/Gemfile.rails-3.2 +1 -3
- data/Gemfile.rails-4.0 +1 -3
- data/Gemfile.rails-4.1 +1 -3
- data/Gemfile.rails-4.2 +17 -0
- data/README.md +55 -43
- data/bullet_instructure.gemspec +2 -1
- data/lib/bullet/active_record3.rb +68 -34
- data/lib/bullet/active_record3x.rb +60 -32
- data/lib/bullet/active_record4.rb +57 -39
- data/lib/bullet/active_record41.rb +83 -42
- data/lib/bullet/active_record42.rb +195 -0
- data/lib/bullet/dependency.rb +6 -0
- data/lib/bullet/detector/association.rb +23 -17
- data/lib/bullet/detector/counter_cache.rb +16 -16
- data/lib/bullet/detector/n_plus_one_query.rb +43 -30
- data/lib/bullet/detector/unused_eager_loading.rb +2 -2
- data/lib/bullet/ext/object.rb +6 -2
- data/lib/bullet/notification/base.rb +17 -18
- data/lib/bullet/notification/n_plus_one_query.rb +6 -4
- data/lib/bullet/notification/unused_eager_loading.rb +1 -1
- data/lib/bullet/rack.rb +21 -14
- data/lib/bullet/version.rb +2 -2
- data/lib/bullet.rb +18 -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 +14 -0
- data/spec/bullet/notification/base_spec.rb +30 -18
- data/spec/bullet/notification/n_plus_one_query_spec.rb +3 -3
- data/spec/bullet/notification/unused_eager_loading_spec.rb +1 -1
- data/spec/bullet/rack_spec.rb +3 -3
- data/spec/bullet_spec.rb +2 -2
- data/spec/integration/active_record3/association_spec.rb +22 -2
- data/spec/integration/active_record4/association_spec.rb +47 -2
- data/spec/models/category.rb +5 -2
- data/spec/models/comment.rb +2 -0
- data/spec/models/post.rb +8 -3
- data/spec/models/reply.rb +3 -0
- data/spec/models/submission.rb +1 -1
- data/spec/spec_helper.rb +4 -4
- data/spec/support/sqlite_seed.rb +14 -6
- data/test.sh +1 -0
- data/update.sh +14 -0
- metadata +26 -9
- data/.ruby-version +0 -1
@@ -8,29 +8,35 @@ module Bullet
|
|
8
8
|
# if select only one object, then the only one object has impossible to cause N+1 query.
|
9
9
|
def to_a
|
10
10
|
records = origin_to_a
|
11
|
-
if
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
if Bullet.start?
|
12
|
+
if records.first.class.name !~ /^HABTM_/
|
13
|
+
if records.size > 1
|
14
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
15
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
16
|
+
elsif records.size == 1
|
17
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
18
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
19
|
+
end
|
20
|
+
end
|
17
21
|
end
|
18
22
|
records
|
19
23
|
end
|
20
24
|
end
|
21
25
|
|
22
26
|
::ActiveRecord::Associations::Preloader.class_eval do
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
alias_method :origin_preloaders_on, :preloaders_on
|
28
|
+
|
29
|
+
def preloaders_on(association, records, scope)
|
30
|
+
if Bullet.start?
|
31
|
+
records.compact!
|
32
|
+
if records.first.class.name !~ /^HABTM_/
|
33
|
+
records.each do |record|
|
34
|
+
Bullet::Detector::Association.add_object_associations(record, association)
|
35
|
+
end
|
36
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
|
37
|
+
end
|
32
38
|
end
|
33
|
-
|
39
|
+
origin_preloaders_on(association, records, scope)
|
34
40
|
end
|
35
41
|
end
|
36
42
|
|
@@ -38,29 +44,50 @@ module Bullet
|
|
38
44
|
# add includes in scope
|
39
45
|
alias_method :origin_find_with_associations, :find_with_associations
|
40
46
|
def find_with_associations
|
41
|
-
if block_given?
|
42
|
-
|
43
|
-
|
44
|
-
records = origin_find_with_associations
|
47
|
+
return origin_find_with_associations { |r| yield r } if block_given?
|
48
|
+
records = origin_find_with_associations
|
49
|
+
if Bullet.start?
|
45
50
|
associations = (eager_load_values + includes_values).uniq
|
46
51
|
records.each do |record|
|
47
52
|
Bullet::Detector::Association.add_object_associations(record, associations)
|
48
|
-
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
49
53
|
end
|
50
54
|
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
51
|
-
records
|
52
55
|
end
|
56
|
+
records
|
53
57
|
end
|
54
58
|
end
|
55
59
|
|
56
60
|
::ActiveRecord::Associations::JoinDependency.class_eval do
|
61
|
+
alias_method :origin_instantiate, :instantiate
|
57
62
|
alias_method :origin_construct_model, :construct_model
|
63
|
+
|
64
|
+
def instantiate(result_set, aliases)
|
65
|
+
@bullet_eager_loadings = {}
|
66
|
+
records = origin_instantiate(result_set, aliases)
|
67
|
+
|
68
|
+
if Bullet.start?
|
69
|
+
@bullet_eager_loadings.each do |klazz, eager_loadings_hash|
|
70
|
+
objects = eager_loadings_hash.keys
|
71
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
records
|
75
|
+
end
|
76
|
+
|
58
77
|
# call join associations
|
59
|
-
def construct_model(record,
|
60
|
-
|
61
|
-
|
62
|
-
Bullet
|
63
|
-
|
78
|
+
def construct_model(record, node, row, model_cache, id, aliases)
|
79
|
+
result = origin_construct_model(record, node, row, model_cache, id, aliases)
|
80
|
+
|
81
|
+
if Bullet.start?
|
82
|
+
associations = node.reflection.name
|
83
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
84
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
85
|
+
@bullet_eager_loadings[record.class] ||= {}
|
86
|
+
@bullet_eager_loadings[record.class][record] ||= Set.new
|
87
|
+
@bullet_eager_loadings[record.class][record] << associations
|
88
|
+
end
|
89
|
+
|
90
|
+
result
|
64
91
|
end
|
65
92
|
end
|
66
93
|
|
@@ -68,9 +95,27 @@ module Bullet
|
|
68
95
|
# call one to many associations
|
69
96
|
alias_method :origin_load_target, :load_target
|
70
97
|
def load_target
|
71
|
-
Bullet
|
98
|
+
if Bullet.start?
|
99
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
|
100
|
+
end
|
72
101
|
origin_load_target
|
73
102
|
end
|
103
|
+
|
104
|
+
alias_method :origin_empty?, :empty?
|
105
|
+
def empty?
|
106
|
+
if Bullet.start?
|
107
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
108
|
+
end
|
109
|
+
origin_empty?
|
110
|
+
end
|
111
|
+
|
112
|
+
alias_method :origin_include?, :include?
|
113
|
+
def include?(object)
|
114
|
+
if Bullet.start?
|
115
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
116
|
+
end
|
117
|
+
origin_include?(object)
|
118
|
+
end
|
74
119
|
end
|
75
120
|
|
76
121
|
::ActiveRecord::Associations::SingularAssociation.class_eval do
|
@@ -78,28 +123,24 @@ module Bullet
|
|
78
123
|
alias_method :origin_reader, :reader
|
79
124
|
def reader(force_reload = false)
|
80
125
|
result = origin_reader(force_reload)
|
81
|
-
Bullet
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
::ActiveRecord::Associations::Association.class_eval do
|
88
|
-
alias_method :origin_set_inverse_instance, :set_inverse_instance
|
89
|
-
def set_inverse_instance(record)
|
90
|
-
if record && invertible_for?(record)
|
91
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(record)
|
126
|
+
if Bullet.start?
|
127
|
+
if @owner.class.name !~ /^HABTM_/ && !@inversed
|
128
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
129
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
|
130
|
+
end
|
92
131
|
end
|
93
|
-
|
132
|
+
result
|
94
133
|
end
|
95
134
|
end
|
96
135
|
|
97
136
|
::ActiveRecord::Associations::HasManyAssociation.class_eval do
|
98
137
|
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
99
138
|
|
100
|
-
def has_cached_counter?(reflection = reflection)
|
139
|
+
def has_cached_counter?(reflection = reflection())
|
101
140
|
result = origin_has_cached_counter?(reflection)
|
102
|
-
Bullet
|
141
|
+
if Bullet.start?
|
142
|
+
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name) unless result
|
143
|
+
end
|
103
144
|
result
|
104
145
|
end
|
105
146
|
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
module Bullet
|
2
|
+
module ActiveRecord
|
3
|
+
def self.enable
|
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::Relation.class_eval do
|
25
|
+
alias_method :origin_to_a, :to_a
|
26
|
+
# if select a collection of objects, then these objects have possible to cause N+1 query.
|
27
|
+
# if select only one object, then the only one object has impossible to cause N+1 query.
|
28
|
+
def to_a
|
29
|
+
records = origin_to_a
|
30
|
+
if Bullet.start?
|
31
|
+
if records.first.class.name !~ /^HABTM_/
|
32
|
+
if records.size > 1
|
33
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
34
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
35
|
+
elsif records.size == 1
|
36
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
37
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
records
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
::ActiveRecord::Associations::Preloader.class_eval do
|
46
|
+
alias_method :origin_preloaders_on, :preloaders_on
|
47
|
+
|
48
|
+
def preloaders_on(association, records, scope)
|
49
|
+
if Bullet.start?
|
50
|
+
records.compact!
|
51
|
+
if records.first.class.name !~ /^HABTM_/
|
52
|
+
records.each do |record|
|
53
|
+
Bullet::Detector::Association.add_object_associations(record, association)
|
54
|
+
end
|
55
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
origin_preloaders_on(association, records, scope)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
::ActiveRecord::FinderMethods.class_eval do
|
63
|
+
# add includes in scope
|
64
|
+
alias_method :origin_find_with_associations, :find_with_associations
|
65
|
+
def find_with_associations
|
66
|
+
return origin_find_with_associations { |r| yield r } if block_given?
|
67
|
+
records = origin_find_with_associations
|
68
|
+
if Bullet.start?
|
69
|
+
associations = (eager_load_values + includes_values).uniq
|
70
|
+
records.each do |record|
|
71
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
72
|
+
end
|
73
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
|
74
|
+
end
|
75
|
+
records
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
::ActiveRecord::Associations::JoinDependency.class_eval do
|
80
|
+
alias_method :origin_instantiate, :instantiate
|
81
|
+
alias_method :origin_construct_model, :construct_model
|
82
|
+
|
83
|
+
def instantiate(result_set, aliases)
|
84
|
+
@bullet_eager_loadings = {}
|
85
|
+
records = origin_instantiate(result_set, aliases)
|
86
|
+
|
87
|
+
if Bullet.start?
|
88
|
+
@bullet_eager_loadings.each do |klazz, eager_loadings_hash|
|
89
|
+
objects = eager_loadings_hash.keys
|
90
|
+
Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
records
|
94
|
+
end
|
95
|
+
|
96
|
+
# call join associations
|
97
|
+
def construct_model(record, node, row, model_cache, id, aliases)
|
98
|
+
result = origin_construct_model(record, node, row, model_cache, id, aliases)
|
99
|
+
|
100
|
+
if Bullet.start?
|
101
|
+
associations = node.reflection.name
|
102
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
103
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
104
|
+
@bullet_eager_loadings[record.class] ||= {}
|
105
|
+
@bullet_eager_loadings[record.class][record] ||= Set.new
|
106
|
+
@bullet_eager_loadings[record.class][record] << associations
|
107
|
+
end
|
108
|
+
|
109
|
+
result
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
::ActiveRecord::Associations::CollectionAssociation.class_eval do
|
114
|
+
# call one to many associations
|
115
|
+
alias_method :origin_load_target, :load_target
|
116
|
+
def load_target
|
117
|
+
records = origin_load_target
|
118
|
+
|
119
|
+
if Bullet.start?
|
120
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
|
121
|
+
if records.first.class.name !~ /^HABTM_/
|
122
|
+
if records.size > 1
|
123
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
124
|
+
Bullet::Detector::CounterCache.add_possible_objects(records)
|
125
|
+
elsif records.size == 1
|
126
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
127
|
+
Bullet::Detector::CounterCache.add_impossible_object(records.first)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
records
|
132
|
+
end
|
133
|
+
|
134
|
+
alias_method :origin_empty?, :empty?
|
135
|
+
def empty?
|
136
|
+
if Bullet.start?
|
137
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
138
|
+
end
|
139
|
+
origin_empty?
|
140
|
+
end
|
141
|
+
|
142
|
+
alias_method :origin_include?, :include?
|
143
|
+
def include?(object)
|
144
|
+
if Bullet.start?
|
145
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
146
|
+
end
|
147
|
+
origin_include?(object)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
::ActiveRecord::Associations::SingularAssociation.class_eval do
|
152
|
+
# call has_one and belongs_to associations
|
153
|
+
alias_method :origin_reader, :reader
|
154
|
+
def reader(force_reload = false)
|
155
|
+
result = origin_reader(force_reload)
|
156
|
+
if Bullet.start?
|
157
|
+
if @owner.class.name !~ /^HABTM_/ && !@inversed
|
158
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
159
|
+
if Bullet::Detector::NPlusOneQuery.impossible?(@owner)
|
160
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
161
|
+
else
|
162
|
+
Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
result
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
::ActiveRecord::Associations::HasManyAssociation.class_eval do
|
171
|
+
alias_method :origin_many_empty?, :empty?
|
172
|
+
def empty?
|
173
|
+
Thread.current[:bullet_collection_empty] = true
|
174
|
+
result = origin_many_empty?
|
175
|
+
Thread.current[:bullet_collection_empty] = nil
|
176
|
+
if Bullet.start?
|
177
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
178
|
+
end
|
179
|
+
result
|
180
|
+
end
|
181
|
+
|
182
|
+
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
183
|
+
def has_cached_counter?(reflection = reflection())
|
184
|
+
result = origin_has_cached_counter?(reflection)
|
185
|
+
if Bullet.start?
|
186
|
+
if !result && !Thread.current[:bullet_collection_empty]
|
187
|
+
Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
result
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
data/lib/bullet/dependency.rb
CHANGED
@@ -22,6 +22,8 @@ module Bullet
|
|
22
22
|
'active_record4'
|
23
23
|
elsif active_record41?
|
24
24
|
'active_record41'
|
25
|
+
elsif active_record42?
|
26
|
+
'active_record42'
|
25
27
|
end
|
26
28
|
end
|
27
29
|
end
|
@@ -66,6 +68,10 @@ module Bullet
|
|
66
68
|
active_record4? && ::ActiveRecord::VERSION::MINOR == 1
|
67
69
|
end
|
68
70
|
|
71
|
+
def active_record42?
|
72
|
+
active_record4? && ::ActiveRecord::VERSION::MINOR == 2
|
73
|
+
end
|
74
|
+
|
69
75
|
def mongoid2x?
|
70
76
|
mongoid? && ::Mongoid::VERSION =~ /\A2\.[4-8]/
|
71
77
|
end
|
@@ -5,7 +5,7 @@ module Bullet
|
|
5
5
|
def add_object_associations(object, associations)
|
6
6
|
return unless Bullet.start?
|
7
7
|
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
|
8
|
-
return unless object.
|
8
|
+
return unless object.primary_key_value
|
9
9
|
|
10
10
|
Bullet.debug("Detector::Association#add_object_associations", "object: #{object.bullet_key}, associations: #{associations}")
|
11
11
|
object_associations.add(object.bullet_key, associations)
|
@@ -14,12 +14,28 @@ module Bullet
|
|
14
14
|
def add_call_object_associations(object, associations)
|
15
15
|
return unless Bullet.start?
|
16
16
|
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
|
17
|
-
return unless object.
|
17
|
+
return unless object.primary_key_value
|
18
18
|
|
19
19
|
Bullet.debug("Detector::Association#add_call_object_associations", "object: #{object.bullet_key}, associations: #{associations}")
|
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,21 +54,11 @@ module Bullet
|
|
38
54
|
Thread.current[:bullet_call_object_associations]
|
39
55
|
end
|
40
56
|
|
41
|
-
#
|
42
|
-
# that
|
43
|
-
# e.g. {
|
44
|
-
def
|
45
|
-
Thread.current[:
|
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
|
-
# Notice: impossible_objects are not accurate,
|
52
|
-
# if find collection returns only one object, then the object is impossible object,
|
53
|
-
# impossible_objects are used to avoid treating 1+1 query to N+1 query.
|
54
|
-
def impossible_objects
|
55
|
-
Thread.current[:bullet_impossible_objects]
|
57
|
+
# inversed_objects keeps object relationships
|
58
|
+
# that association is inversed.
|
59
|
+
# e.g. { "Comment:1" => ["post"] }
|
60
|
+
def inversed_objects
|
61
|
+
Thread.current[:bullet_inversed_objects]
|
56
62
|
end
|
57
63
|
|
58
64
|
# eager_loadings keep the object relationships
|
@@ -5,10 +5,10 @@ module Bullet
|
|
5
5
|
def add_counter_cache(object, associations)
|
6
6
|
return unless Bullet.start?
|
7
7
|
return unless Bullet.counter_cache_enable?
|
8
|
-
return unless object.
|
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
|
@@ -17,7 +17,7 @@ module Bullet
|
|
17
17
|
return unless Bullet.start?
|
18
18
|
return unless Bullet.counter_cache_enable?
|
19
19
|
objects = Array(object_or_objects)
|
20
|
-
return if objects.map(&:
|
20
|
+
return if objects.map(&:primary_key_value).compact.empty?
|
21
21
|
|
22
22
|
Bullet.debug("Detector::CounterCache#add_possible_objects", "objects: #{objects.map(&:bullet_key).join(', ')}")
|
23
23
|
objects.each { |object| possible_objects.add object.bullet_key }
|
@@ -26,12 +26,24 @@ module Bullet
|
|
26
26
|
def add_impossible_object(object)
|
27
27
|
return unless Bullet.start?
|
28
28
|
return unless Bullet.counter_cache_enable?
|
29
|
-
return unless object.
|
29
|
+
return unless object.primary_key_value
|
30
30
|
|
31
31
|
Bullet.debug("Detector::CounterCache#add_impossible_object", "object: #{object.bullet_key}")
|
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
|
@@ -10,11 +10,12 @@ module Bullet
|
|
10
10
|
# if it is, keeps this unpreload associations and caller.
|
11
11
|
def call_association(object, associations)
|
12
12
|
return unless Bullet.start?
|
13
|
-
return unless object.
|
13
|
+
return unless object.primary_key_value
|
14
|
+
return if inversed_objects.include?(object.bullet_key, associations)
|
14
15
|
add_call_object_associations(object, associations)
|
15
16
|
|
16
17
|
Bullet.debug("Detector::NPlusOneQuery#call_association", "object: #{object.bullet_key}, associations: #{associations}")
|
17
|
-
if conditions_met?(object
|
18
|
+
if conditions_met?(object, associations)
|
18
19
|
Bullet.debug("detect n + 1 query", "object: #{object.bullet_key}, associations: #{associations}")
|
19
20
|
create_notification caller_in_project, object.class.to_s, associations
|
20
21
|
end
|
@@ -24,7 +25,7 @@ module Bullet
|
|
24
25
|
return unless Bullet.start?
|
25
26
|
return unless Bullet.n_plus_one_query_enable?
|
26
27
|
objects = Array(object_or_objects)
|
27
|
-
return if objects.map(&:
|
28
|
+
return if objects.map(&:primary_key_value).compact.empty?
|
28
29
|
|
29
30
|
Bullet.debug("Detector::NPlusOneQuery#add_possible_objects", "objects: #{objects.map(&:bullet_key).join(', ')}")
|
30
31
|
objects.each { |object| possible_objects.add object.bullet_key }
|
@@ -33,12 +34,50 @@ module Bullet
|
|
33
34
|
def add_impossible_object(object)
|
34
35
|
return unless Bullet.start?
|
35
36
|
return unless Bullet.n_plus_one_query_enable?
|
36
|
-
return unless object.
|
37
|
+
return unless object.primary_key_value
|
37
38
|
|
38
39
|
Bullet.debug("Detector::NPlusOneQuery#add_impossible_object", "object: #{object.bullet_key}")
|
39
40
|
impossible_objects.add object.bullet_key
|
40
41
|
end
|
41
42
|
|
43
|
+
def add_inversed_object(object, association)
|
44
|
+
return unless Bullet.start?
|
45
|
+
return unless Bullet.n_plus_one_query_enable?
|
46
|
+
return unless object.primary_key_value
|
47
|
+
|
48
|
+
Bullet.debug("Detector::NPlusOneQuery#add_inversed_object", "object: #{object.bullet_key}, association: #{association}")
|
49
|
+
inversed_objects.add object.bullet_key, association
|
50
|
+
end
|
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.has_key?(associations) : associations == v
|
74
|
+
return true if result
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
42
81
|
private
|
43
82
|
def create_notification(callers, klazz, associations)
|
44
83
|
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:n_plus_one_query, klazz)
|
@@ -49,11 +88,6 @@ module Bullet
|
|
49
88
|
end
|
50
89
|
end
|
51
90
|
|
52
|
-
# decide whether the object.associations is unpreloaded or not.
|
53
|
-
def conditions_met?(bullet_key, associations)
|
54
|
-
possible?(bullet_key) && !impossible?(bullet_key) && !association?(bullet_key, associations)
|
55
|
-
end
|
56
|
-
|
57
91
|
def caller_in_project
|
58
92
|
app_root = rails? ? Rails.root.to_s : Dir.pwd
|
59
93
|
vendor_root = app_root + "/vendor"
|
@@ -62,27 +96,6 @@ module Bullet
|
|
62
96
|
Bullet.stacktrace_includes.any? { |include| c.include?(include) }
|
63
97
|
end
|
64
98
|
end
|
65
|
-
|
66
|
-
def possible?(bullet_key)
|
67
|
-
possible_objects.include? bullet_key
|
68
|
-
end
|
69
|
-
|
70
|
-
def impossible?(bullet_key)
|
71
|
-
impossible_objects.include? bullet_key
|
72
|
-
end
|
73
|
-
|
74
|
-
# check if object => associations already exists in object_associations.
|
75
|
-
def association?(bullet_key, associations)
|
76
|
-
value = object_associations[bullet_key]
|
77
|
-
if value
|
78
|
-
value.each do |v|
|
79
|
-
result = v.is_a?(Hash) ? v.has_key?(associations) : v == associations
|
80
|
-
return true if result
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
return false
|
85
|
-
end
|
86
99
|
end
|
87
100
|
end
|
88
101
|
end
|
@@ -22,7 +22,7 @@ module Bullet
|
|
22
22
|
def add_eager_loadings(objects, associations)
|
23
23
|
return unless Bullet.start?
|
24
24
|
return unless Bullet.unused_eager_loading_enable?
|
25
|
-
return if objects.map(&:
|
25
|
+
return if objects.map(&:primary_key_value).compact.empty?
|
26
26
|
|
27
27
|
Bullet.debug("Detector::UnusedEagerLoading#add_eager_loadings", "objects: #{objects.map(&:bullet_key).join(', ')}, associations: #{associations}")
|
28
28
|
bullet_keys = objects.map(&:bullet_key)
|
@@ -47,7 +47,7 @@ module Bullet
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
eager_loadings.add
|
50
|
+
eager_loadings.add(*to_add) if to_add
|
51
51
|
to_merge.each { |k,val| eager_loadings.merge k, val }
|
52
52
|
to_delete.each { |k| eager_loadings.delete k }
|
53
53
|
|
data/lib/bullet/ext/object.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
class Object
|
2
2
|
def bullet_key
|
3
|
+
"#{self.class}:#{self.primary_key_value}"
|
4
|
+
end
|
5
|
+
|
6
|
+
def primary_key_value
|
3
7
|
if self.class.respond_to?(:primary_key) && self.class.primary_key
|
4
|
-
|
8
|
+
self.send self.class.primary_key
|
5
9
|
else
|
6
|
-
|
10
|
+
self.id
|
7
11
|
end
|
8
12
|
end
|
9
13
|
end
|