bullet 5.8.1 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
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