bullet 6.0.2 → 7.0.3

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.
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