bullet 5.5.0 → 6.1.5

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