bullet 6.0.2 → 7.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +82 -0
  3. data/CHANGELOG.md +52 -0
  4. data/Gemfile.rails-6.0 +1 -1
  5. data/Gemfile.rails-6.1 +15 -0
  6. data/Gemfile.rails-7.0 +10 -0
  7. data/README.md +39 -25
  8. data/lib/bullet/active_job.rb +1 -3
  9. data/lib/bullet/active_record4.rb +9 -23
  10. data/lib/bullet/active_record41.rb +8 -19
  11. data/lib/bullet/active_record42.rb +9 -16
  12. data/lib/bullet/active_record5.rb +188 -170
  13. data/lib/bullet/active_record52.rb +182 -162
  14. data/lib/bullet/active_record60.rb +191 -178
  15. data/lib/bullet/active_record61.rb +272 -0
  16. data/lib/bullet/active_record70.rb +275 -0
  17. data/lib/bullet/bullet_xhr.js +18 -23
  18. data/lib/bullet/dependency.rb +52 -34
  19. data/lib/bullet/detector/association.rb +24 -18
  20. data/lib/bullet/detector/counter_cache.rb +12 -8
  21. data/lib/bullet/detector/n_plus_one_query.rb +20 -10
  22. data/lib/bullet/detector/unused_eager_loading.rb +7 -4
  23. data/lib/bullet/mongoid4x.rb +3 -7
  24. data/lib/bullet/mongoid5x.rb +3 -7
  25. data/lib/bullet/mongoid6x.rb +3 -7
  26. data/lib/bullet/mongoid7x.rb +26 -13
  27. data/lib/bullet/notification/base.rb +14 -18
  28. data/lib/bullet/notification/n_plus_one_query.rb +2 -4
  29. data/lib/bullet/notification/unused_eager_loading.rb +2 -4
  30. data/lib/bullet/notification.rb +2 -1
  31. data/lib/bullet/rack.rb +28 -17
  32. data/lib/bullet/stack_trace_filter.rb +10 -17
  33. data/lib/bullet/version.rb +1 -1
  34. data/lib/bullet.rb +52 -42
  35. data/lib/generators/bullet/install_generator.rb +22 -23
  36. data/perf/benchmark.rb +11 -14
  37. data/spec/bullet/detector/counter_cache_spec.rb +6 -6
  38. data/spec/bullet/detector/n_plus_one_query_spec.rb +8 -4
  39. data/spec/bullet/detector/unused_eager_loading_spec.rb +25 -8
  40. data/spec/bullet/ext/object_spec.rb +1 -1
  41. data/spec/bullet/notification/base_spec.rb +5 -7
  42. data/spec/bullet/notification/n_plus_one_query_spec.rb +16 -3
  43. data/spec/bullet/notification/unused_eager_loading_spec.rb +5 -1
  44. data/spec/bullet/rack_spec.rb +154 -13
  45. data/spec/bullet/registry/association_spec.rb +2 -2
  46. data/spec/bullet/registry/base_spec.rb +1 -1
  47. data/spec/bullet_spec.rb +25 -44
  48. data/spec/integration/active_record/association_spec.rb +104 -130
  49. data/spec/integration/counter_cache_spec.rb +14 -34
  50. data/spec/integration/mongoid/association_spec.rb +19 -33
  51. data/spec/models/attachment.rb +5 -0
  52. data/spec/models/deal.rb +5 -0
  53. data/spec/models/post.rb +2 -0
  54. data/spec/models/role.rb +7 -0
  55. data/spec/models/submission.rb +1 -0
  56. data/spec/models/user.rb +2 -0
  57. data/spec/spec_helper.rb +4 -10
  58. data/spec/support/bullet_ext.rb +8 -9
  59. data/spec/support/mongo_seed.rb +3 -16
  60. data/spec/support/sqlite_seed.rb +38 -0
  61. data/test.sh +2 -0
  62. metadata +17 -7
  63. data/.travis.yml +0 -31
@@ -13,247 +13,260 @@ module Bullet
13
13
  module ActiveRecord
14
14
  def self.enable
15
15
  require 'active_record'
16
- ::ActiveRecord::Base.extend(Module.new do
17
- def find_by_sql(sql, binds = [], preparable: nil, &block)
18
- result = super
19
- if Bullet.start?
20
- if result.is_a? Array
21
- if result.size > 1
22
- Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
23
- Bullet::Detector::CounterCache.add_possible_objects(result)
24
- elsif result.size == 1
25
- Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
26
- Bullet::Detector::CounterCache.add_impossible_object(result.first)
16
+ ::ActiveRecord::Base.extend(
17
+ Module.new do
18
+ def find_by_sql(sql, binds = [], preparable: nil, &block)
19
+ result = super
20
+ if Bullet.start?
21
+ if result.is_a? Array
22
+ if result.size > 1
23
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
24
+ Bullet::Detector::CounterCache.add_possible_objects(result)
25
+ elsif result.size == 1
26
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
27
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
28
+ end
29
+ elsif result.is_a? ::ActiveRecord::Base
30
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
31
+ Bullet::Detector::CounterCache.add_impossible_object(result)
27
32
  end
28
- elsif result.is_a? ::ActiveRecord::Base
29
- Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
30
- Bullet::Detector::CounterCache.add_impossible_object(result)
31
33
  end
34
+ result
32
35
  end
33
- result
34
36
  end
35
- end)
37
+ )
36
38
 
37
39
  ::ActiveRecord::Base.prepend(SaveWithBulletSupport)
38
40
 
39
- ::ActiveRecord::Relation.prepend(Module.new do
40
- # if select a collection of objects, then these objects have possible to cause N+1 query.
41
- # if select only one object, then the only one object has impossible to cause N+1 query.
42
- def records
43
- result = super
44
- if Bullet.start?
45
- if result.first.class.name !~ /^HABTM_/
46
- if result.size > 1
47
- Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
48
- Bullet::Detector::CounterCache.add_possible_objects(result)
49
- elsif result.size == 1
50
- Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
51
- Bullet::Detector::CounterCache.add_impossible_object(result.first)
41
+ ::ActiveRecord::Relation.prepend(
42
+ Module.new do
43
+ # if select a collection of objects, then these objects have possible to cause N+1 query.
44
+ # if select only one object, then the only one object has impossible to cause N+1 query.
45
+ def records
46
+ result = super
47
+ if Bullet.start?
48
+ if result.first.class.name !~ /^HABTM_/
49
+ if result.size > 1
50
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
51
+ Bullet::Detector::CounterCache.add_possible_objects(result)
52
+ elsif result.size == 1
53
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
54
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
55
+ end
52
56
  end
53
57
  end
58
+ result
54
59
  end
55
- result
56
60
  end
57
- end)
61
+ )
58
62
 
59
- ::ActiveRecord::Associations::Preloader.prepend(Module.new do
60
- def preloaders_for_one(association, records, scope, polymorphic_parent)
61
- if Bullet.start?
62
- records.compact!
63
- if records.first.class.name !~ /^HABTM_/
64
- records.each do |record|
65
- Bullet::Detector::Association.add_object_associations(record, association)
63
+ ::ActiveRecord::Associations::Preloader.prepend(
64
+ Module.new do
65
+ def preloaders_for_one(association, records, scope, polymorphic_parent)
66
+ if Bullet.start?
67
+ records.compact!
68
+ if records.first.class.name !~ /^HABTM_/
69
+ records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }
70
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
66
71
  end
67
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
68
72
  end
73
+ super
69
74
  end
70
- super
71
- end
72
75
 
73
- def preloaders_for_reflection(reflection, records, scope)
74
- if Bullet.start?
75
- records.compact!
76
- if records.first.class.name !~ /^HABTM_/
77
- records.each do |record|
78
- Bullet::Detector::Association.add_object_associations(record, reflection.name)
76
+ def preloaders_for_reflection(reflection, records, scope)
77
+ if Bullet.start?
78
+ records.compact!
79
+ if records.first.class.name !~ /^HABTM_/
80
+ records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }
81
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, reflection.name)
79
82
  end
80
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, reflection.name)
81
83
  end
84
+ super
82
85
  end
83
- super
84
86
  end
85
- end)
87
+ )
86
88
 
87
- ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(Module.new do
88
- def preloaded_records
89
- if Bullet.start? && !defined?(@preloaded_records)
90
- source_preloaders.each do |source_preloader|
91
- reflection_name = source_preloader.send(:reflection).name
92
- source_preloader.send(:owners).each do |owner|
93
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
89
+ ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
90
+ Module.new do
91
+ def preloaded_records
92
+ if Bullet.start? && !defined?(@preloaded_records)
93
+ source_preloaders.each do |source_preloader|
94
+ reflection_name = source_preloader.send(:reflection).name
95
+ source_preloader.send(:owners).each do |owner|
96
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
97
+ end
94
98
  end
95
99
  end
100
+ super
96
101
  end
97
- super
98
102
  end
99
- end)
103
+ )
100
104
 
101
- ::ActiveRecord::FinderMethods.prepend(Module.new do
102
- # add includes in scope
103
- def find_with_associations
104
- return super { |r| yield r } if block_given?
105
+ ::ActiveRecord::Associations::JoinDependency.prepend(
106
+ Module.new do
107
+ def instantiate(result_set, &block)
108
+ @bullet_eager_loadings = {}
109
+ records = super
105
110
 
106
- records = super
107
- if Bullet.start?
108
- associations = (eager_load_values + includes_values).uniq
109
- records.each do |record|
110
- Bullet::Detector::Association.add_object_associations(record, associations)
111
+ if Bullet.start?
112
+ @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
113
+ objects = eager_loadings_hash.keys
114
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
115
+ objects,
116
+ eager_loadings_hash[objects.first].to_a
117
+ )
118
+ end
111
119
  end
112
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
120
+ records
113
121
  end
114
- records
115
- end
116
- end)
117
122
 
118
- ::ActiveRecord::Associations::JoinDependency.prepend(Module.new do
119
- def instantiate(result_set, &block)
120
- @bullet_eager_loadings = {}
121
- records = super
123
+ def construct(ar_parent, parent, row, seen, model_cache)
124
+ if Bullet.start?
125
+ unless ar_parent.nil?
126
+ parent.children.each do |node|
127
+ key = aliases.column_alias(node, node.primary_key)
128
+ id = row[key]
129
+ next unless id.nil?
122
130
 
123
- if Bullet.start?
124
- @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
125
- objects = eager_loadings_hash.keys
126
- Bullet::Detector::UnusedEagerLoading.add_eager_loadings(objects, eager_loadings_hash[objects.first].to_a)
131
+ associations = node.reflection.name
132
+ Bullet::Detector::Association.add_object_associations(ar_parent, associations)
133
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
134
+ @bullet_eager_loadings[ar_parent.class] ||= {}
135
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
136
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << associations
137
+ end
138
+ end
127
139
  end
140
+
141
+ super
128
142
  end
129
- records
130
- end
131
143
 
132
- def construct(ar_parent, parent, row, seen, model_cache)
133
- if Bullet.start?
134
- unless ar_parent.nil?
135
- parent.children.each do |node|
136
- key = aliases.column_alias(node, node.primary_key)
137
- id = row[key]
138
- next unless id.nil?
144
+ # call join associations
145
+ def construct_model(record, node, row, model_cache, id)
146
+ result = super
139
147
 
140
- associations = node.reflection.name
141
- Bullet::Detector::Association.add_object_associations(ar_parent, associations)
142
- Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
143
- @bullet_eager_loadings[ar_parent.class] ||= {}
144
- @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
145
- @bullet_eager_loadings[ar_parent.class][ar_parent] << associations
146
- end
148
+ if Bullet.start?
149
+ associations = node.reflection.name
150
+ Bullet::Detector::Association.add_object_associations(record, associations)
151
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
152
+ @bullet_eager_loadings[record.class] ||= {}
153
+ @bullet_eager_loadings[record.class][record] ||= Set.new
154
+ @bullet_eager_loadings[record.class][record] << associations
147
155
  end
148
- end
149
156
 
150
- super
157
+ result
158
+ end
151
159
  end
160
+ )
152
161
 
153
- # call join associations
154
- def construct_model(record, node, row, model_cache, id)
155
- result = super
156
-
157
- if Bullet.start?
158
- associations = node.reflection.name
159
- Bullet::Detector::Association.add_object_associations(record, associations)
160
- Bullet::Detector::NPlusOneQuery.call_association(record, associations)
161
- @bullet_eager_loadings[record.class] ||= {}
162
- @bullet_eager_loadings[record.class][record] ||= Set.new
163
- @bullet_eager_loadings[record.class][record] << associations
162
+ ::ActiveRecord::Associations::Association.prepend(
163
+ Module.new do
164
+ def inversed_from(record)
165
+ if Bullet.start?
166
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
167
+ end
168
+ super
164
169
  end
165
-
166
- result
167
170
  end
168
- end)
171
+ )
169
172
 
170
- ::ActiveRecord::Associations::CollectionAssociation.prepend(Module.new do
171
- def load_target
172
- records = super
173
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
174
+ Module.new do
175
+ def load_target
176
+ records = super
173
177
 
174
- if Bullet.start?
175
- if is_a? ::ActiveRecord::Associations::ThroughAssociation
176
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
177
- association = owner.association(reflection.through_reflection.name)
178
- Array(association.target).each do |through_record|
179
- Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
180
- end
178
+ if Bullet.start?
179
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
180
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
181
+ association = owner.association(reflection.through_reflection.name)
182
+ Array.wrap(association.target).each do |through_record|
183
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
184
+ end
181
185
 
182
- if reflection.through_reflection != through_reflection
183
- Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
186
+ if reflection.through_reflection != through_reflection
187
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
188
+ end
184
189
  end
185
- end
186
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
187
- if records.first.class.name !~ /^HABTM_/
188
- if records.size > 1
189
- Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
190
- Bullet::Detector::CounterCache.add_possible_objects(records)
191
- elsif records.size == 1
192
- Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
193
- Bullet::Detector::CounterCache.add_impossible_object(records.first)
190
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
191
+ if records.first.class.name !~ /^HABTM_/
192
+ if records.size > 1
193
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
194
+ Bullet::Detector::CounterCache.add_possible_objects(records)
195
+ elsif records.size == 1
196
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
197
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
198
+ end
194
199
  end
195
200
  end
201
+ records
196
202
  end
197
- records
198
- end
199
203
 
200
- def empty?
201
- if Bullet.start? && !reflection.has_cached_counter?
202
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
204
+ def empty?
205
+ if Bullet.start? && !reflection.has_cached_counter?
206
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
207
+ end
208
+ super
203
209
  end
204
- super
205
- end
206
210
 
207
- def include?(object)
208
- if Bullet.start?
209
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
211
+ def include?(object)
212
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
213
+ super
210
214
  end
211
- super
212
215
  end
213
- end)
216
+ )
214
217
 
215
- ::ActiveRecord::Associations::SingularAssociation.prepend(Module.new do
216
- # call has_one and belongs_to associations
217
- def target
218
- result = super()
219
- if Bullet.start?
220
- if owner.class.name !~ /^HABTM_/ && !@inversed
221
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
222
- if Bullet::Detector::NPlusOneQuery.impossible?(owner)
223
- Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
224
- else
225
- Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
218
+ ::ActiveRecord::Associations::SingularAssociation.prepend(
219
+ Module.new do
220
+ # call has_one and belongs_to associations
221
+ def target
222
+ result = super()
223
+
224
+ if Bullet.start?
225
+ if owner.class.name !~ /^HABTM_/ && !@inversed
226
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
227
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
228
+ association = owner.association(reflection.through_reflection.name)
229
+ Array.wrap(association.target).each do |through_record|
230
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
231
+ end
232
+
233
+ if reflection.through_reflection != through_reflection
234
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
235
+ end
236
+ end
237
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
238
+
239
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
240
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
241
+ else
242
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
243
+ end
226
244
  end
227
245
  end
246
+ result
228
247
  end
229
- result
230
248
  end
231
- end)
249
+ )
232
250
 
233
- ::ActiveRecord::Associations::HasManyAssociation.prepend(Module.new do
234
- def empty?
235
- result = super
236
- if Bullet.start? && !reflection.has_cached_counter?
237
- Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
251
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(
252
+ Module.new do
253
+ def empty?
254
+ result = super
255
+ if Bullet.start? && !reflection.has_cached_counter?
256
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
257
+ end
258
+ result
238
259
  end
239
- result
240
- end
241
260
 
242
- def count_records
243
- result = reflection.has_cached_counter?
244
- if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
245
- Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
261
+ def count_records
262
+ result = reflection.has_cached_counter?
263
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
264
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
265
+ end
266
+ super
246
267
  end
247
- super
248
- end
249
- end)
250
-
251
- ::ActiveRecord::Associations::BelongsToAssociation.prepend(Module.new do
252
- def writer(record)
253
- Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
254
- super
255
268
  end
256
- end)
269
+ )
257
270
  end
258
271
  end
259
272
  end