bullet 5.7.5 → 6.1.4

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 (76) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +22 -1
  3. data/CHANGELOG.md +49 -12
  4. data/Gemfile.mongoid-7.0 +15 -0
  5. data/Gemfile.rails-4.0 +1 -1
  6. data/Gemfile.rails-4.1 +1 -1
  7. data/Gemfile.rails-4.2 +1 -1
  8. data/Gemfile.rails-5.0 +1 -1
  9. data/Gemfile.rails-5.1 +1 -1
  10. data/Gemfile.rails-5.2 +2 -2
  11. data/Gemfile.rails-6.0 +15 -0
  12. data/Gemfile.rails-6.1 +15 -0
  13. data/README.md +38 -13
  14. data/Rakefile +1 -1
  15. data/bullet.gemspec +8 -3
  16. data/lib/bullet.rb +50 -22
  17. data/lib/bullet/active_job.rb +13 -0
  18. data/lib/bullet/active_record4.rb +12 -35
  19. data/lib/bullet/active_record41.rb +10 -30
  20. data/lib/bullet/active_record42.rb +12 -27
  21. data/lib/bullet/active_record5.rb +197 -177
  22. data/lib/bullet/active_record52.rb +191 -166
  23. data/lib/bullet/active_record60.rb +278 -0
  24. data/lib/bullet/active_record61.rb +278 -0
  25. data/lib/bullet/bullet_xhr.js +63 -0
  26. data/lib/bullet/dependency.rb +54 -34
  27. data/lib/bullet/detector/association.rb +26 -20
  28. data/lib/bullet/detector/base.rb +1 -2
  29. data/lib/bullet/detector/counter_cache.rb +14 -9
  30. data/lib/bullet/detector/n_plus_one_query.rb +27 -17
  31. data/lib/bullet/detector/unused_eager_loading.rb +7 -3
  32. data/lib/bullet/ext/object.rb +5 -3
  33. data/lib/bullet/ext/string.rb +1 -1
  34. data/lib/bullet/mongoid4x.rb +4 -7
  35. data/lib/bullet/mongoid5x.rb +4 -7
  36. data/lib/bullet/mongoid6x.rb +8 -11
  37. data/lib/bullet/mongoid7x.rb +57 -0
  38. data/lib/bullet/notification/base.rb +15 -19
  39. data/lib/bullet/notification/n_plus_one_query.rb +2 -4
  40. data/lib/bullet/notification/unused_eager_loading.rb +2 -4
  41. data/lib/bullet/rack.rb +54 -28
  42. data/lib/bullet/stack_trace_filter.rb +39 -30
  43. data/lib/bullet/version.rb +1 -1
  44. data/lib/generators/bullet/install_generator.rb +26 -26
  45. data/perf/benchmark.rb +8 -14
  46. data/spec/bullet/detector/counter_cache_spec.rb +6 -6
  47. data/spec/bullet/detector/n_plus_one_query_spec.rb +30 -3
  48. data/spec/bullet/detector/unused_eager_loading_spec.rb +19 -6
  49. data/spec/bullet/ext/object_spec.rb +9 -4
  50. data/spec/bullet/notification/base_spec.rb +1 -3
  51. data/spec/bullet/notification/n_plus_one_query_spec.rb +16 -3
  52. data/spec/bullet/notification/unused_eager_loading_spec.rb +5 -1
  53. data/spec/bullet/rack_spec.rb +140 -5
  54. data/spec/bullet/registry/association_spec.rb +2 -2
  55. data/spec/bullet/registry/base_spec.rb +1 -1
  56. data/spec/bullet_spec.rb +10 -29
  57. data/spec/integration/active_record/association_spec.rb +122 -118
  58. data/spec/integration/counter_cache_spec.rb +11 -31
  59. data/spec/integration/mongoid/association_spec.rb +18 -32
  60. data/spec/models/attachment.rb +5 -0
  61. data/spec/models/client.rb +2 -0
  62. data/spec/models/firm.rb +1 -0
  63. data/spec/models/folder.rb +1 -2
  64. data/spec/models/group.rb +3 -0
  65. data/spec/models/page.rb +1 -2
  66. data/spec/models/post.rb +15 -0
  67. data/spec/models/submission.rb +1 -0
  68. data/spec/models/user.rb +1 -0
  69. data/spec/models/writer.rb +1 -2
  70. data/spec/spec_helper.rb +6 -10
  71. data/spec/support/bullet_ext.rb +8 -9
  72. data/spec/support/mongo_seed.rb +2 -16
  73. data/spec/support/sqlite_seed.rb +17 -2
  74. data/test.sh +2 -0
  75. data/update.sh +1 -0
  76. metadata +24 -11
@@ -0,0 +1,278 @@
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, 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)
71
+ end
72
+ end
73
+ super
74
+ end
75
+
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)
82
+ end
83
+ end
84
+ super
85
+ end
86
+ end
87
+ )
88
+
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
98
+ end
99
+ end
100
+ super
101
+ end
102
+ end
103
+ )
104
+
105
+ ::ActiveRecord::FinderMethods.prepend(
106
+ Module.new do
107
+ # add includes in scope
108
+ def find_with_associations
109
+ return super { |r| yield r } if block_given?
110
+
111
+ records = super
112
+ if Bullet.start?
113
+ associations = (eager_load_values + includes_values).uniq
114
+ records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
115
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
116
+ end
117
+ records
118
+ end
119
+ end
120
+ )
121
+
122
+ ::ActiveRecord::Associations::JoinDependency.prepend(
123
+ Module.new do
124
+ def instantiate(result_set, strict_loading_value, &block)
125
+ @bullet_eager_loadings = {}
126
+ records = super
127
+
128
+ if Bullet.start?
129
+ @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
130
+ objects = eager_loadings_hash.keys
131
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
132
+ objects,
133
+ eager_loadings_hash[objects.first].to_a
134
+ )
135
+ end
136
+ end
137
+ records
138
+ end
139
+
140
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
141
+ if Bullet.start?
142
+ unless ar_parent.nil?
143
+ parent.children.each do |node|
144
+ key = aliases.column_alias(node, node.primary_key)
145
+ id = row[key]
146
+ next unless id.nil?
147
+
148
+ associations = node.reflection.name
149
+ Bullet::Detector::Association.add_object_associations(ar_parent, associations)
150
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
151
+ @bullet_eager_loadings[ar_parent.class] ||= {}
152
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
153
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << associations
154
+ end
155
+ end
156
+ end
157
+
158
+ super
159
+ end
160
+
161
+ # call join associations
162
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
163
+ result = super
164
+
165
+ if Bullet.start?
166
+ associations = node.reflection.name
167
+ Bullet::Detector::Association.add_object_associations(record, associations)
168
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
169
+ @bullet_eager_loadings[record.class] ||= {}
170
+ @bullet_eager_loadings[record.class][record] ||= Set.new
171
+ @bullet_eager_loadings[record.class][record] << associations
172
+ end
173
+
174
+ result
175
+ end
176
+ end
177
+ )
178
+
179
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
180
+ Module.new do
181
+ def load_target
182
+ records = super
183
+
184
+ if Bullet.start?
185
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
186
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
187
+ association = owner.association(reflection.through_reflection.name)
188
+ Array(association.target).each do |through_record|
189
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
190
+ end
191
+
192
+ if reflection.through_reflection != through_reflection
193
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
194
+ end
195
+ end
196
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
197
+ if records.first.class.name !~ /^HABTM_/
198
+ if records.size > 1
199
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
200
+ Bullet::Detector::CounterCache.add_possible_objects(records)
201
+ elsif records.size == 1
202
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
203
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
204
+ end
205
+ end
206
+ end
207
+ records
208
+ end
209
+
210
+ def empty?
211
+ if Bullet.start? && !reflection.has_cached_counter?
212
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
213
+ end
214
+ super
215
+ end
216
+
217
+ def include?(object)
218
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
219
+ super
220
+ end
221
+ end
222
+ )
223
+
224
+ ::ActiveRecord::Associations::SingularAssociation.prepend(
225
+ Module.new do
226
+ # call has_one and belongs_to associations
227
+ def target
228
+ result = super()
229
+
230
+ if Bullet.start?
231
+ if owner.class.name !~ /^HABTM_/ && !@inversed
232
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
233
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
234
+ association = owner.association(reflection.through_reflection.name)
235
+ Array(association.target).each do |through_record|
236
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
237
+ end
238
+
239
+ if reflection.through_reflection != through_reflection
240
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
241
+ end
242
+ end
243
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
244
+
245
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
246
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
247
+ else
248
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
249
+ end
250
+ end
251
+ end
252
+ result
253
+ end
254
+ end
255
+ )
256
+
257
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(
258
+ Module.new do
259
+ def empty?
260
+ result = super
261
+ if Bullet.start? && !reflection.has_cached_counter?
262
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
263
+ end
264
+ result
265
+ end
266
+
267
+ def count_records
268
+ result = reflection.has_cached_counter?
269
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
270
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
271
+ end
272
+ super
273
+ end
274
+ end
275
+ )
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,63 @@
1
+ (function () {
2
+ var oldOpen = window.XMLHttpRequest.prototype.open;
3
+ var oldSend = window.XMLHttpRequest.prototype.send;
4
+
5
+ /**
6
+ * Return early if we've already extended prototype. This prevents
7
+ * "maximum call stack exceeded" errors when used with Turbolinks.
8
+ * See https://github.com/flyerhzm/bullet/issues/454
9
+ */
10
+ if (isBulletInitiated()) return;
11
+
12
+ function isBulletInitiated() {
13
+ return oldOpen.name == "bulletXHROpen" && oldSend.name == "bulletXHRSend";
14
+ }
15
+ function bulletXHROpen(_, url) {
16
+ this._storedUrl = url;
17
+ return Reflect.apply(oldOpen, this, arguments);
18
+ }
19
+ function bulletXHRSend() {
20
+ if (this.onload) {
21
+ this._storedOnload = this.onload;
22
+ }
23
+ this.addEventListener("load", bulletXHROnload);
24
+ return Reflect.apply(oldSend, this, arguments);
25
+ }
26
+ function bulletXHROnload() {
27
+ if (
28
+ this._storedUrl.startsWith(window.location.protocol + "//" + window.location.host) ||
29
+ !this._storedUrl.startsWith("http") // For relative paths
30
+ ) {
31
+ var bulletFooterText = this.getResponseHeader("X-bullet-footer-text");
32
+ if (bulletFooterText) {
33
+ setTimeout(function() {
34
+ var oldHtml = document.querySelector("#bullet-footer").innerHTML.split("<br>");
35
+ var header = oldHtml[0];
36
+ oldHtml = oldHtml.slice(1, oldHtml.length);
37
+ var newHtml = oldHtml.concat(JSON.parse(bulletFooterText));
38
+ newHtml = newHtml.slice(newHtml.length - 10, newHtml.length); // rotate through 10 most recent
39
+ document.querySelector("#bullet-footer").innerHTML = `${header}<br>${newHtml.join("<br>")}`;
40
+ }, 0);
41
+ }
42
+ var bulletConsoleText = this.getResponseHeader("X-bullet-console-text");
43
+ if (bulletConsoleText && typeof console !== "undefined" && console.log) {
44
+ setTimeout(function() {
45
+ JSON.parse(bulletConsoleText).forEach((message) => {
46
+ if (console.groupCollapsed && console.groupEnd) {
47
+ console.groupCollapsed("Uniform Notifier");
48
+ console.log(message);
49
+ console.groupEnd();
50
+ } else {
51
+ console.log(message);
52
+ }
53
+ });
54
+ }, 0);
55
+ }
56
+ }
57
+ if (this._storedOnload) {
58
+ return Reflect.apply(this._storedOnload, this, arguments);
59
+ }
60
+ }
61
+ window.XMLHttpRequest.prototype.open = bulletXHROpen;
62
+ window.XMLHttpRequest.prototype.send = bulletXHRSend;
63
+ })();
@@ -3,49 +3,53 @@
3
3
  module Bullet
4
4
  module Dependency
5
5
  def mongoid?
6
- @mongoid ||= defined? ::Mongoid
6
+ @mongoid ||= defined?(::Mongoid)
7
7
  end
8
8
 
9
9
  def active_record?
10
- @active_record ||= defined? ::ActiveRecord
11
- end
12
-
13
- def rails?
14
- @rails ||= defined? ::Rails
10
+ @active_record ||= defined?(::ActiveRecord)
15
11
  end
16
12
 
17
13
  def active_record_version
18
- @active_record_version ||= begin
19
- if active_record40?
20
- 'active_record4'
21
- elsif active_record41?
22
- 'active_record41'
23
- elsif active_record42?
24
- 'active_record42'
25
- elsif active_record50?
26
- 'active_record5'
27
- elsif active_record51?
28
- 'active_record5'
29
- elsif active_record52?
30
- 'active_record52'
31
- else
32
- raise "Bullet does not support active_record #{::ActiveRecord::VERSION} yet"
33
- end
34
- end
14
+ @active_record_version ||=
15
+ begin
16
+ if active_record40?
17
+ 'active_record4'
18
+ elsif active_record41?
19
+ 'active_record41'
20
+ elsif active_record42?
21
+ 'active_record42'
22
+ elsif active_record50?
23
+ 'active_record5'
24
+ elsif active_record51?
25
+ 'active_record5'
26
+ elsif active_record52?
27
+ 'active_record52'
28
+ elsif active_record60?
29
+ 'active_record60'
30
+ elsif active_record61?
31
+ 'active_record61'
32
+ else
33
+ raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
34
+ end
35
+ end
35
36
  end
36
37
 
37
38
  def mongoid_version
38
- @mongoid_version ||= begin
39
- if mongoid4x?
40
- 'mongoid4x'
41
- elsif mongoid5x?
42
- 'mongoid5x'
43
- elsif mongoid6x?
44
- 'mongoid6x'
45
- else
46
- raise "Bullet does not support mongoid #{::Mongoid::VERSION} yet"
47
- end
48
- end
39
+ @mongoid_version ||=
40
+ begin
41
+ if mongoid4x?
42
+ 'mongoid4x'
43
+ elsif mongoid5x?
44
+ 'mongoid5x'
45
+ elsif mongoid6x?
46
+ 'mongoid6x'
47
+ elsif mongoid7x?
48
+ 'mongoid7x'
49
+ else
50
+ raise "Bullet does not support mongoid #{::Mongoid::VERSION} yet"
51
+ end
52
+ end
49
53
  end
50
54
 
51
55
  def active_record4?
@@ -56,6 +60,10 @@ module Bullet
56
60
  active_record? && ::ActiveRecord::VERSION::MAJOR == 5
57
61
  end
58
62
 
63
+ def active_record6?
64
+ active_record? && ::ActiveRecord::VERSION::MAJOR == 6
65
+ end
66
+
59
67
  def active_record40?
60
68
  active_record4? && ::ActiveRecord::VERSION::MINOR == 0
61
69
  end
@@ -80,6 +88,14 @@ module Bullet
80
88
  active_record5? && ::ActiveRecord::VERSION::MINOR == 2
81
89
  end
82
90
 
91
+ def active_record60?
92
+ active_record6? && ::ActiveRecord::VERSION::MINOR == 0
93
+ end
94
+
95
+ def active_record61?
96
+ active_record6? && ::ActiveRecord::VERSION::MINOR == 1
97
+ end
98
+
83
99
  def mongoid4x?
84
100
  mongoid? && ::Mongoid::VERSION =~ /\A4/
85
101
  end
@@ -91,5 +107,9 @@ module Bullet
91
107
  def mongoid6x?
92
108
  mongoid? && ::Mongoid::VERSION =~ /\A6/
93
109
  end
110
+
111
+ def mongoid7x?
112
+ mongoid? && ::Mongoid::VERSION =~ /\A7/
113
+ end
94
114
  end
95
115
  end