bullet 5.8.1 → 6.1.0

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 (64) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +22 -1
  3. data/CHANGELOG.md +24 -1
  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 +1 -1
  11. data/Gemfile.rails-6.0 +15 -0
  12. data/README.md +24 -10
  13. data/lib/bullet.rb +42 -17
  14. data/lib/bullet/active_job.rb +9 -0
  15. data/lib/bullet/active_record4.rb +9 -32
  16. data/lib/bullet/active_record41.rb +7 -27
  17. data/lib/bullet/active_record42.rb +8 -24
  18. data/lib/bullet/active_record5.rb +188 -179
  19. data/lib/bullet/active_record52.rb +176 -168
  20. data/lib/bullet/active_record60.rb +267 -0
  21. data/lib/bullet/bullet_xhr.js +63 -0
  22. data/lib/bullet/dependency.rb +48 -34
  23. data/lib/bullet/detector/association.rb +26 -20
  24. data/lib/bullet/detector/base.rb +1 -2
  25. data/lib/bullet/detector/counter_cache.rb +13 -9
  26. data/lib/bullet/detector/n_plus_one_query.rb +22 -12
  27. data/lib/bullet/detector/unused_eager_loading.rb +6 -3
  28. data/lib/bullet/ext/object.rb +4 -2
  29. data/lib/bullet/mongoid4x.rb +2 -6
  30. data/lib/bullet/mongoid5x.rb +2 -6
  31. data/lib/bullet/mongoid6x.rb +2 -6
  32. data/lib/bullet/mongoid7x.rb +57 -0
  33. data/lib/bullet/notification/base.rb +14 -18
  34. data/lib/bullet/notification/n_plus_one_query.rb +2 -4
  35. data/lib/bullet/notification/unused_eager_loading.rb +2 -4
  36. data/lib/bullet/rack.rb +39 -20
  37. data/lib/bullet/stack_trace_filter.rb +6 -12
  38. data/lib/bullet/version.rb +1 -1
  39. data/lib/generators/bullet/install_generator.rb +4 -2
  40. data/perf/benchmark.rb +8 -14
  41. data/spec/bullet/detector/counter_cache_spec.rb +5 -5
  42. data/spec/bullet/detector/n_plus_one_query_spec.rb +7 -3
  43. data/spec/bullet/detector/unused_eager_loading_spec.rb +29 -12
  44. data/spec/bullet/ext/object_spec.rb +9 -4
  45. data/spec/bullet/notification/base_spec.rb +1 -3
  46. data/spec/bullet/notification/n_plus_one_query_spec.rb +18 -3
  47. data/spec/bullet/notification/unused_eager_loading_spec.rb +5 -1
  48. data/spec/bullet/rack_spec.rb +30 -6
  49. data/spec/bullet/registry/association_spec.rb +2 -2
  50. data/spec/bullet/registry/base_spec.rb +1 -1
  51. data/spec/bullet_spec.rb +10 -29
  52. data/spec/integration/active_record/association_spec.rb +45 -136
  53. data/spec/integration/counter_cache_spec.rb +11 -31
  54. data/spec/integration/mongoid/association_spec.rb +18 -32
  55. data/spec/models/folder.rb +1 -2
  56. data/spec/models/group.rb +1 -2
  57. data/spec/models/page.rb +1 -2
  58. data/spec/models/writer.rb +1 -2
  59. data/spec/spec_helper.rb +6 -10
  60. data/spec/support/bullet_ext.rb +8 -9
  61. data/spec/support/mongo_seed.rb +2 -16
  62. data/test.sh +2 -0
  63. data/update.sh +1 -0
  64. metadata +9 -4
@@ -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 oldOpen.apply(this, arguments);
18
+ }
19
+ function bulletXHRSend() {
20
+ if (this.onload) {
21
+ this._storedOnload = this.onload;
22
+ }
23
+ this.addEventListener('load', bulletXHROnload);
24
+ return oldSend.apply(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(() => {
34
+ var oldHtml = document.getElementById('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.getElementById('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(() => {
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 this._storedOnload.apply(this, arguments);
59
+ }
60
+ }
61
+ window.XMLHttpRequest.prototype.open = bulletXHROpen;
62
+ window.XMLHttpRequest.prototype.send = bulletXHRSend;
63
+ })();
@@ -3,49 +3,51 @@
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::STRING} 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
+ else
31
+ raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
32
+ end
33
+ end
35
34
  end
36
35
 
37
36
  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
37
+ @mongoid_version ||=
38
+ begin
39
+ if mongoid4x?
40
+ 'mongoid4x'
41
+ elsif mongoid5x?
42
+ 'mongoid5x'
43
+ elsif mongoid6x?
44
+ 'mongoid6x'
45
+ elsif mongoid7x?
46
+ 'mongoid7x'
47
+ else
48
+ raise "Bullet does not support mongoid #{::Mongoid::VERSION} yet"
49
+ end
50
+ end
49
51
  end
50
52
 
51
53
  def active_record4?
@@ -56,6 +58,10 @@ module Bullet
56
58
  active_record? && ::ActiveRecord::VERSION::MAJOR == 5
57
59
  end
58
60
 
61
+ def active_record6?
62
+ active_record? && ::ActiveRecord::VERSION::MAJOR == 6
63
+ end
64
+
59
65
  def active_record40?
60
66
  active_record4? && ::ActiveRecord::VERSION::MINOR == 0
61
67
  end
@@ -80,6 +86,10 @@ module Bullet
80
86
  active_record5? && ::ActiveRecord::VERSION::MINOR == 2
81
87
  end
82
88
 
89
+ def active_record60?
90
+ active_record6? && ::ActiveRecord::VERSION::MINOR == 0
91
+ end
92
+
83
93
  def mongoid4x?
84
94
  mongoid? && ::Mongoid::VERSION =~ /\A4/
85
95
  end
@@ -91,5 +101,9 @@ module Bullet
91
101
  def mongoid6x?
92
102
  mongoid? && ::Mongoid::VERSION =~ /\A6/
93
103
  end
104
+
105
+ def mongoid7x?
106
+ mongoid? && ::Mongoid::VERSION =~ /\A7/
107
+ end
94
108
  end
95
109
  end
@@ -3,22 +3,28 @@
3
3
  module Bullet
4
4
  module Detector
5
5
  class Association < Base
6
- class <<self
6
+ class << self
7
7
  def add_object_associations(object, associations)
8
8
  return unless Bullet.start?
9
9
  return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
10
- return unless object.primary_key_value
10
+ return unless object.bullet_primary_key_value
11
11
 
12
- Bullet.debug('Detector::Association#add_object_associations', "object: #{object.bullet_key}, associations: #{associations}")
12
+ Bullet.debug(
13
+ 'Detector::Association#add_object_associations',
14
+ "object: #{object.bullet_key}, associations: #{associations}"
15
+ )
13
16
  object_associations.add(object.bullet_key, associations)
14
17
  end
15
18
 
16
19
  def add_call_object_associations(object, associations)
17
20
  return unless Bullet.start?
18
21
  return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
19
- return unless object.primary_key_value
22
+ return unless object.bullet_primary_key_value
20
23
 
21
- Bullet.debug('Detector::Association#add_call_object_associations', "object: #{object.bullet_key}, associations: #{associations}")
24
+ Bullet.debug(
25
+ 'Detector::Association#add_call_object_associations',
26
+ "object: #{object.bullet_key}, associations: #{associations}"
27
+ )
22
28
  call_object_associations.add(object.bullet_key, associations)
23
29
  end
24
30
 
@@ -40,33 +46,33 @@ module Bullet
40
46
 
41
47
  private
42
48
 
43
- # object_associations keep the object relationships
44
- # that the object has many associations.
45
- # e.g. { "Post:1" => [:comments] }
46
- # the object_associations keep all associations that may be or may no be
47
- # unpreload associations or unused preload associations.
49
+ # object_associations keep the object relationships
50
+ # that the object has many associations.
51
+ # e.g. { "Post:1" => [:comments] }
52
+ # the object_associations keep all associations that may be or may no be
53
+ # unpreload associations or unused preload associations.
48
54
  def object_associations
49
55
  Thread.current[:bullet_object_associations]
50
56
  end
51
57
 
52
- # call_object_associations keep the object relationships
53
- # that object.associations is called.
54
- # e.g. { "Post:1" => [:comments] }
55
- # they are used to detect unused preload associations.
58
+ # call_object_associations keep the object relationships
59
+ # that object.associations is called.
60
+ # e.g. { "Post:1" => [:comments] }
61
+ # they are used to detect unused preload associations.
56
62
  def call_object_associations
57
63
  Thread.current[:bullet_call_object_associations]
58
64
  end
59
65
 
60
- # inversed_objects keeps object relationships
61
- # that association is inversed.
62
- # e.g. { "Comment:1" => ["post"] }
66
+ # inversed_objects keeps object relationships
67
+ # that association is inversed.
68
+ # e.g. { "Comment:1" => ["post"] }
63
69
  def inversed_objects
64
70
  Thread.current[:bullet_inversed_objects]
65
71
  end
66
72
 
67
- # eager_loadings keep the object relationships
68
- # that the associations are preloaded by find :include.
69
- # e.g. { ["Post:1", "Post:2"] => [:comments, :user] }
73
+ # eager_loadings keep the object relationships
74
+ # that the associations are preloaded by find :include.
75
+ # e.g. { ["Post:1", "Post:2"] => [:comments, :user] }
70
76
  def eager_loadings
71
77
  Thread.current[:bullet_eager_loadings]
72
78
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  module Bullet
4
4
  module Detector
5
- class Base
6
- end
5
+ class Base; end
7
6
  end
8
7
  end
@@ -3,16 +3,17 @@
3
3
  module Bullet
4
4
  module Detector
5
5
  class CounterCache < Base
6
- class <<self
6
+ class << self
7
7
  def add_counter_cache(object, associations)
8
8
  return unless Bullet.start?
9
9
  return unless Bullet.counter_cache_enable?
10
- return unless object.primary_key_value
10
+ return unless object.bullet_primary_key_value
11
11
 
12
- Bullet.debug('Detector::CounterCache#add_counter_cache', "object: #{object.bullet_key}, associations: #{associations}")
13
- if conditions_met?(object, associations)
14
- create_notification object.class.to_s, associations
15
- end
12
+ Bullet.debug(
13
+ 'Detector::CounterCache#add_counter_cache',
14
+ "object: #{object.bullet_key}, associations: #{associations}"
15
+ )
16
+ create_notification object.class.to_s, associations if conditions_met?(object, associations)
16
17
  end
17
18
 
18
19
  def add_possible_objects(object_or_objects)
@@ -20,16 +21,19 @@ module Bullet
20
21
  return unless Bullet.counter_cache_enable?
21
22
 
22
23
  objects = Array(object_or_objects)
23
- return if objects.map(&:primary_key_value).compact.empty?
24
+ return if objects.map(&:bullet_primary_key_value).compact.empty?
24
25
 
25
- Bullet.debug('Detector::CounterCache#add_possible_objects', "objects: #{objects.map(&:bullet_key).join(', ')}")
26
+ Bullet.debug(
27
+ 'Detector::CounterCache#add_possible_objects',
28
+ "objects: #{objects.map(&:bullet_key).join(', ')}"
29
+ )
26
30
  objects.each { |object| possible_objects.add object.bullet_key }
27
31
  end
28
32
 
29
33
  def add_impossible_object(object)
30
34
  return unless Bullet.start?
31
35
  return unless Bullet.counter_cache_enable?
32
- return unless object.primary_key_value
36
+ return unless object.bullet_primary_key_value
33
37
 
34
38
  Bullet.debug('Detector::CounterCache#add_impossible_object', "object: #{object.bullet_key}")
35
39
  impossible_objects.add object.bullet_key
@@ -6,7 +6,7 @@ module Bullet
6
6
  extend Dependency
7
7
  extend StackTraceFilter
8
8
 
9
- class <<self
9
+ class << self
10
10
  # executed when object.assocations is called.
11
11
  # first, it keeps this method call for object.association.
12
12
  # then, it checks if this associations call is unpreload.
@@ -14,12 +14,15 @@ module Bullet
14
14
  def call_association(object, associations)
15
15
  return unless Bullet.start?
16
16
  return unless Bullet.n_plus_one_query_enable?
17
- return unless object.primary_key_value
17
+ return unless object.bullet_primary_key_value
18
18
  return if inversed_objects.include?(object.bullet_key, associations)
19
19
 
20
20
  add_call_object_associations(object, associations)
21
21
 
22
- Bullet.debug('Detector::NPlusOneQuery#call_association', "object: #{object.bullet_key}, associations: #{associations}")
22
+ Bullet.debug(
23
+ 'Detector::NPlusOneQuery#call_association',
24
+ "object: #{object.bullet_key}, associations: #{associations}"
25
+ )
23
26
  if !excluded_stacktrace_path? && conditions_met?(object, associations)
24
27
  Bullet.debug('detect n + 1 query', "object: #{object.bullet_key}, associations: #{associations}")
25
28
  create_notification caller_in_project, object.class.to_s, associations
@@ -31,16 +34,19 @@ module Bullet
31
34
  return unless Bullet.n_plus_one_query_enable?
32
35
 
33
36
  objects = Array(object_or_objects)
34
- return if objects.map(&:primary_key_value).compact.empty?
37
+ return if objects.map(&:bullet_primary_key_value).compact.empty?
35
38
 
36
- Bullet.debug('Detector::NPlusOneQuery#add_possible_objects', "objects: #{objects.map(&:bullet_key).join(', ')}")
39
+ Bullet.debug(
40
+ 'Detector::NPlusOneQuery#add_possible_objects',
41
+ "objects: #{objects.map(&:bullet_key).join(', ')}"
42
+ )
37
43
  objects.each { |object| possible_objects.add object.bullet_key }
38
44
  end
39
45
 
40
46
  def add_impossible_object(object)
41
47
  return unless Bullet.start?
42
48
  return unless Bullet.n_plus_one_query_enable?
43
- return unless object.primary_key_value
49
+ return unless object.bullet_primary_key_value
44
50
 
45
51
  Bullet.debug('Detector::NPlusOneQuery#add_impossible_object', "object: #{object.bullet_key}")
46
52
  impossible_objects.add object.bullet_key
@@ -49,9 +55,12 @@ module Bullet
49
55
  def add_inversed_object(object, association)
50
56
  return unless Bullet.start?
51
57
  return unless Bullet.n_plus_one_query_enable?
52
- return unless object.primary_key_value
58
+ return unless object.bullet_primary_key_value
53
59
 
54
- Bullet.debug('Detector::NPlusOneQuery#add_inversed_object', "object: #{object.bullet_key}, association: #{association}")
60
+ Bullet.debug(
61
+ 'Detector::NPlusOneQuery#add_inversed_object',
62
+ "object: #{object.bullet_key}, association: #{association}"
63
+ )
55
64
  inversed_objects.add object.bullet_key, association
56
65
  end
57
66
 
@@ -72,10 +81,11 @@ module Bullet
72
81
  def association?(object, associations)
73
82
  value = object_associations[object.bullet_key]
74
83
  value&.each do |v|
75
- # associations == v comparison order is important here because
76
- # v variable might be a squeel node where :== method is redefined,
77
- # so it does not compare values at all and return unexpected results
78
- result = v.is_a?(Hash) ? v.key?(associations) : associations == v
84
+ # associations == v comparison order is important here because
85
+ # v variable might be a squeel node where :== method is redefined,
86
+ # so it does not compare values at all and return unexpected results
87
+ result =
88
+ v.is_a?(Hash) ? v.key?(associations) : associations == v
79
89
  return true if result
80
90
  end
81
91
 
@@ -6,7 +6,7 @@ module Bullet
6
6
  extend Dependency
7
7
  extend StackTraceFilter
8
8
 
9
- class <<self
9
+ class << self
10
10
  # check if there are unused preload associations.
11
11
  # get related_objects from eager_loadings associated with object and associations
12
12
  # get call_object_association from associations of call_object_associations whose object is in related_objects
@@ -27,9 +27,12 @@ module Bullet
27
27
  def add_eager_loadings(objects, associations)
28
28
  return unless Bullet.start?
29
29
  return unless Bullet.unused_eager_loading_enable?
30
- return if objects.map(&:primary_key_value).compact.empty?
30
+ return if objects.map(&:bullet_primary_key_value).compact.empty?
31
31
 
32
- Bullet.debug('Detector::UnusedEagerLoading#add_eager_loadings', "objects: #{objects.map(&:bullet_key).join(', ')}, associations: #{associations}")
32
+ Bullet.debug(
33
+ 'Detector::UnusedEagerLoading#add_eager_loadings',
34
+ "objects: #{objects.map(&:bullet_key).join(', ')}, associations: #{associations}"
35
+ )
33
36
  bullet_keys = objects.map(&:bullet_key)
34
37
 
35
38
  to_add = []
@@ -2,10 +2,12 @@
2
2
 
3
3
  class Object
4
4
  def bullet_key
5
- "#{self.class}:#{primary_key_value}"
5
+ "#{self.class}:#{bullet_primary_key_value}"
6
6
  end
7
7
 
8
- def primary_key_value
8
+ def bullet_primary_key_value
9
+ return if respond_to?(:persisted?) && !persisted?
10
+
9
11
  if self.class.respond_to?(:primary_keys) && self.class.primary_keys
10
12
  self.class.primary_keys.map { |primary_key| send primary_key }.join(',')
11
13
  elsif self.class.respond_to?(:primary_key) && self.class.primary_key
@@ -37,9 +37,7 @@ module Bullet
37
37
 
38
38
  def eager_load(docs)
39
39
  associations = criteria.inclusions.map(&:name)
40
- docs.each do |doc|
41
- Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
42
- end
40
+ docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }
43
41
  Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
44
42
  origin_eager_load(docs)
45
43
  end
@@ -50,9 +48,7 @@ module Bullet
50
48
 
51
49
  def get_relation(name, metadata, object, reload = false)
52
50
  result = origin_get_relation(name, metadata, object, reload)
53
- if metadata.macro !~ /embed/
54
- Bullet::Detector::NPlusOneQuery.call_association(self, name)
55
- end
51
+ Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/
56
52
  result
57
53
  end
58
54
  end