bullet 6.1.0 → 7.0.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +82 -0
  3. data/CHANGELOG.md +31 -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 +17 -7
  8. data/lib/bullet/active_job.rb +5 -1
  9. data/lib/bullet/active_record41.rb +1 -0
  10. data/lib/bullet/active_record42.rb +1 -0
  11. data/lib/bullet/active_record52.rb +22 -17
  12. data/lib/bullet/active_record60.rb +22 -17
  13. data/lib/bullet/active_record61.rb +272 -0
  14. data/lib/bullet/active_record70.rb +261 -0
  15. data/lib/bullet/bullet_xhr.js +18 -17
  16. data/lib/bullet/dependency.rb +16 -0
  17. data/lib/bullet/detector/base.rb +2 -1
  18. data/lib/bullet/detector/counter_cache.rb +1 -1
  19. data/lib/bullet/detector/n_plus_one_query.rb +3 -3
  20. data/lib/bullet/detector/unused_eager_loading.rb +1 -1
  21. data/lib/bullet/mongoid4x.rb +1 -1
  22. data/lib/bullet/mongoid5x.rb +1 -1
  23. data/lib/bullet/mongoid6x.rb +1 -1
  24. data/lib/bullet/mongoid7x.rb +24 -7
  25. data/lib/bullet/notification.rb +2 -1
  26. data/lib/bullet/rack.rb +26 -18
  27. data/lib/bullet/stack_trace_filter.rb +7 -9
  28. data/lib/bullet/version.rb +1 -1
  29. data/lib/bullet.rb +69 -23
  30. data/lib/generators/bullet/install_generator.rb +23 -25
  31. data/perf/benchmark.rb +4 -1
  32. data/spec/bullet/detector/counter_cache_spec.rb +1 -1
  33. data/spec/bullet/detector/unused_eager_loading_spec.rb +10 -10
  34. data/spec/bullet/ext/object_spec.rb +1 -1
  35. data/spec/bullet/notification/n_plus_one_query_spec.rb +1 -3
  36. data/spec/bullet/rack_spec.rb +130 -3
  37. data/spec/bullet_spec.rb +39 -10
  38. data/spec/integration/active_record/association_spec.rb +54 -10
  39. data/spec/integration/counter_cache_spec.rb +4 -4
  40. data/spec/integration/mongoid/association_spec.rb +4 -4
  41. data/spec/models/attachment.rb +5 -0
  42. data/spec/models/deal.rb +5 -0
  43. data/spec/models/folder.rb +2 -1
  44. data/spec/models/group.rb +2 -1
  45. data/spec/models/page.rb +2 -1
  46. data/spec/models/post.rb +2 -0
  47. data/spec/models/submission.rb +1 -0
  48. data/spec/models/user.rb +1 -0
  49. data/spec/models/writer.rb +2 -1
  50. data/spec/spec_helper.rb +0 -2
  51. data/spec/support/mongo_seed.rb +1 -0
  52. data/spec/support/sqlite_seed.rb +20 -0
  53. data/test.sh +2 -0
  54. metadata +15 -7
  55. data/.travis.yml +0 -33
@@ -0,0 +1,261 @@
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::Branch.prepend(
64
+ Module.new do
65
+ def preloaders_for_reflection(reflection, reflection_records)
66
+ if Bullet.start?
67
+ reflection_records.compact!
68
+ if reflection_records.first.class.name !~ /^HABTM_/
69
+ reflection_records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }
70
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(reflection_records, reflection.name)
71
+ end
72
+ end
73
+ super
74
+ end
75
+ end
76
+ )
77
+
78
+ ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
79
+ Module.new do
80
+ def preloaded_records
81
+ if Bullet.start? && !defined?(@preloaded_records)
82
+ source_preloaders.each do |source_preloader|
83
+ reflection_name = source_preloader.send(:reflection).name
84
+ source_preloader.send(:owners).each do |owner|
85
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
86
+ end
87
+ end
88
+ end
89
+ super
90
+ end
91
+ end
92
+ )
93
+
94
+ ::ActiveRecord::Associations::JoinDependency.prepend(
95
+ Module.new do
96
+ def instantiate(result_set, strict_loading_value, &block)
97
+ @bullet_eager_loadings = {}
98
+ records = super
99
+
100
+ if Bullet.start?
101
+ @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
102
+ objects = eager_loadings_hash.keys
103
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
104
+ objects,
105
+ eager_loadings_hash[objects.first].to_a
106
+ )
107
+ end
108
+ end
109
+ records
110
+ end
111
+
112
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
113
+ if Bullet.start?
114
+ unless ar_parent.nil?
115
+ parent.children.each do |node|
116
+ key = aliases.column_alias(node, node.primary_key)
117
+ id = row[key]
118
+ next unless id.nil?
119
+
120
+ associations = node.reflection.name
121
+ Bullet::Detector::Association.add_object_associations(ar_parent, associations)
122
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
123
+ @bullet_eager_loadings[ar_parent.class] ||= {}
124
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
125
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << associations
126
+ end
127
+ end
128
+ end
129
+
130
+ super
131
+ end
132
+
133
+ # call join associations
134
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
135
+ result = super
136
+
137
+ if Bullet.start?
138
+ associations = node.reflection.name
139
+ Bullet::Detector::Association.add_object_associations(record, associations)
140
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
141
+ @bullet_eager_loadings[record.class] ||= {}
142
+ @bullet_eager_loadings[record.class][record] ||= Set.new
143
+ @bullet_eager_loadings[record.class][record] << associations
144
+ end
145
+
146
+ result
147
+ end
148
+ end
149
+ )
150
+
151
+ ::ActiveRecord::Associations::Association.prepend(
152
+ Module.new do
153
+ def inversed_from(record)
154
+ if Bullet.start?
155
+ Bullet::Detector::NPlusOneQuery.add_inversed_object(owner, reflection.name)
156
+ end
157
+ super
158
+ end
159
+ end
160
+ )
161
+
162
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
163
+ Module.new do
164
+ def load_target
165
+ records = super
166
+
167
+ if Bullet.start?
168
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
169
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
170
+ association = owner.association(reflection.through_reflection.name)
171
+ Array(association.target).each do |through_record|
172
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
173
+ end
174
+
175
+ if reflection.through_reflection != through_reflection
176
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
177
+ end
178
+ end
179
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
180
+ if records.first.class.name !~ /^HABTM_/
181
+ if records.size > 1
182
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
183
+ Bullet::Detector::CounterCache.add_possible_objects(records)
184
+ elsif records.size == 1
185
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
186
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
187
+ end
188
+ end
189
+ end
190
+ records
191
+ end
192
+
193
+ def empty?
194
+ if Bullet.start? && !reflection.has_cached_counter?
195
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
196
+ end
197
+ super
198
+ end
199
+
200
+ def include?(object)
201
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
202
+ super
203
+ end
204
+ end
205
+ )
206
+
207
+ ::ActiveRecord::Associations::SingularAssociation.prepend(
208
+ Module.new do
209
+ # call has_one and belongs_to associations
210
+ def reader
211
+ result = super
212
+
213
+ if Bullet.start?
214
+ if owner.class.name !~ /^HABTM_/
215
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
216
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
217
+ association = owner.association(reflection.through_reflection.name)
218
+ Array(association.target).each do |through_record|
219
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
220
+ end
221
+
222
+ if reflection.through_reflection != through_reflection
223
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
224
+ end
225
+ end
226
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
227
+
228
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
229
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
230
+ else
231
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
232
+ end
233
+ end
234
+ end
235
+ result
236
+ end
237
+ end
238
+ )
239
+
240
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(
241
+ Module.new do
242
+ def empty?
243
+ result = super
244
+ if Bullet.start? && !reflection.has_cached_counter?
245
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
246
+ end
247
+ result
248
+ end
249
+
250
+ def count_records
251
+ result = reflection.has_cached_counter?
252
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
253
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
254
+ end
255
+ super
256
+ end
257
+ end
258
+ )
259
+ end
260
+ end
261
+ end
@@ -1,4 +1,4 @@
1
- (function() {
1
+ (function () {
2
2
  var oldOpen = window.XMLHttpRequest.prototype.open;
3
3
  var oldSend = window.XMLHttpRequest.prototype.send;
4
4
 
@@ -10,41 +10,42 @@
10
10
  if (isBulletInitiated()) return;
11
11
 
12
12
  function isBulletInitiated() {
13
- return oldOpen.name == 'bulletXHROpen' && oldSend.name == 'bulletXHRSend';
13
+ return oldOpen.name == "bulletXHROpen" && oldSend.name == "bulletXHRSend";
14
14
  }
15
15
  function bulletXHROpen(_, url) {
16
16
  this._storedUrl = url;
17
- return oldOpen.apply(this, arguments);
17
+ return Reflect.apply(oldOpen, this, arguments);
18
18
  }
19
19
  function bulletXHRSend() {
20
20
  if (this.onload) {
21
21
  this._storedOnload = this.onload;
22
22
  }
23
- this.addEventListener('load', bulletXHROnload);
24
- return oldSend.apply(this, arguments);
23
+ this.onload = null
24
+ this.addEventListener("load", bulletXHROnload);
25
+ return Reflect.apply(oldSend, this, arguments);
25
26
  }
26
27
  function bulletXHROnload() {
27
28
  if (
28
- this._storedUrl.startsWith(window.location.protocol + '//' + window.location.host) ||
29
- !this._storedUrl.startsWith('http') // For relative paths
29
+ this._storedUrl.startsWith(window.location.protocol + "//" + window.location.host) ||
30
+ !this._storedUrl.startsWith("http") // For relative paths
30
31
  ) {
31
- var bulletFooterText = this.getResponseHeader('X-bullet-footer-text');
32
+ var bulletFooterText = this.getResponseHeader("X-bullet-footer-text");
32
33
  if (bulletFooterText) {
33
- setTimeout(() => {
34
- var oldHtml = document.getElementById('bullet-footer').innerHTML.split('<br>');
34
+ setTimeout(function() {
35
+ var oldHtml = document.querySelector("#bullet-footer").innerHTML.split("<br>");
35
36
  var header = oldHtml[0];
36
37
  oldHtml = oldHtml.slice(1, oldHtml.length);
37
38
  var newHtml = oldHtml.concat(JSON.parse(bulletFooterText));
38
39
  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
+ document.querySelector("#bullet-footer").innerHTML = `${header}<br>${newHtml.join("<br>")}`;
40
41
  }, 0);
41
42
  }
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 => {
43
+ var bulletConsoleText = this.getResponseHeader("X-bullet-console-text");
44
+ if (bulletConsoleText && typeof console !== "undefined" && console.log) {
45
+ setTimeout(function() {
46
+ JSON.parse(bulletConsoleText).forEach((message) => {
46
47
  if (console.groupCollapsed && console.groupEnd) {
47
- console.groupCollapsed('Uniform Notifier');
48
+ console.groupCollapsed("Uniform Notifier");
48
49
  console.log(message);
49
50
  console.groupEnd();
50
51
  } else {
@@ -55,7 +56,7 @@
55
56
  }
56
57
  }
57
58
  if (this._storedOnload) {
58
- return this._storedOnload.apply(this, arguments);
59
+ return Reflect.apply(this._storedOnload, this, arguments);
59
60
  }
60
61
  }
61
62
  window.XMLHttpRequest.prototype.open = bulletXHROpen;
@@ -27,6 +27,10 @@ module Bullet
27
27
  'active_record52'
28
28
  elsif active_record60?
29
29
  'active_record60'
30
+ elsif active_record61?
31
+ 'active_record61'
32
+ elsif active_record70?
33
+ 'active_record70'
30
34
  else
31
35
  raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
32
36
  end
@@ -62,6 +66,10 @@ module Bullet
62
66
  active_record? && ::ActiveRecord::VERSION::MAJOR == 6
63
67
  end
64
68
 
69
+ def active_record7?
70
+ active_record? && ::ActiveRecord::VERSION::MAJOR == 7
71
+ end
72
+
65
73
  def active_record40?
66
74
  active_record4? && ::ActiveRecord::VERSION::MINOR == 0
67
75
  end
@@ -90,6 +98,14 @@ module Bullet
90
98
  active_record6? && ::ActiveRecord::VERSION::MINOR == 0
91
99
  end
92
100
 
101
+ def active_record61?
102
+ active_record6? && ::ActiveRecord::VERSION::MINOR == 1
103
+ end
104
+
105
+ def active_record70?
106
+ active_record7? && ::ActiveRecord::VERSION::MINOR == 0
107
+ end
108
+
93
109
  def mongoid4x?
94
110
  mongoid? && ::Mongoid::VERSION =~ /\A4/
95
111
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Bullet
4
4
  module Detector
5
- class Base; end
5
+ class Base
6
+ end
6
7
  end
7
8
  end
@@ -54,7 +54,7 @@ module Bullet
54
54
  private
55
55
 
56
56
  def create_notification(klazz, associations)
57
- notify_associations = Array(associations) - Bullet.get_whitelist_associations(:counter_cache, klazz)
57
+ notify_associations = Array(associations) - Bullet.get_safelist_associations(:counter_cache, klazz)
58
58
 
59
59
  if notify_associations.present?
60
60
  notice = Bullet::Notification::CounterCache.new klazz, notify_associations
@@ -35,6 +35,7 @@ module Bullet
35
35
 
36
36
  objects = Array(object_or_objects)
37
37
  return if objects.map(&:bullet_primary_key_value).compact.empty?
38
+ return if objects.all? { |obj| obj.class.name =~ /^HABTM_/ }
38
39
 
39
40
  Bullet.debug(
40
41
  'Detector::NPlusOneQuery#add_possible_objects',
@@ -84,8 +85,7 @@ module Bullet
84
85
  # associations == v comparison order is important here because
85
86
  # v variable might be a squeel node where :== method is redefined,
86
87
  # so it does not compare values at all and return unexpected results
87
- result =
88
- v.is_a?(Hash) ? v.key?(associations) : associations == v
88
+ result = v.is_a?(Hash) ? v.key?(associations) : associations == v
89
89
  return true if result
90
90
  end
91
91
 
@@ -95,7 +95,7 @@ module Bullet
95
95
  private
96
96
 
97
97
  def create_notification(callers, klazz, associations)
98
- notify_associations = Array(associations) - Bullet.get_whitelist_associations(:n_plus_one_query, klazz)
98
+ notify_associations = Array(associations) - Bullet.get_safelist_associations(:n_plus_one_query, klazz)
99
99
 
100
100
  if notify_associations.present?
101
101
  notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
@@ -65,7 +65,7 @@ module Bullet
65
65
  private
66
66
 
67
67
  def create_notification(callers, klazz, associations)
68
- notify_associations = Array(associations) - Bullet.get_whitelist_associations(:unused_eager_loading, klazz)
68
+ notify_associations = Array(associations) - Bullet.get_safelist_associations(:unused_eager_loading, klazz)
69
69
 
70
70
  if notify_associations.present?
71
71
  notice = Bullet::Notification::UnusedEagerLoading.new(callers, klazz, notify_associations)
@@ -23,7 +23,7 @@ module Bullet
23
23
  end
24
24
 
25
25
  def each(&block)
26
- return to_enum unless block_given?
26
+ return to_enum unless block
27
27
 
28
28
  records = []
29
29
  origin_each { |record| records << record }
@@ -23,7 +23,7 @@ module Bullet
23
23
  end
24
24
 
25
25
  def each(&block)
26
- return to_enum unless block_given?
26
+ return to_enum unless block
27
27
 
28
28
  records = []
29
29
  origin_each { |record| records << record }
@@ -23,7 +23,7 @@ module Bullet
23
23
  end
24
24
 
25
25
  def each(&block)
26
- return to_enum unless block_given?
26
+ return to_enum unless block
27
27
 
28
28
  records = []
29
29
  origin_each { |record| records << record }
@@ -25,14 +25,31 @@ module Bullet
25
25
  def each(&block)
26
26
  return to_enum unless block_given?
27
27
 
28
- records = []
29
- origin_each { |record| records << record }
30
- if records.length > 1
31
- Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
32
- elsif records.size == 1
33
- Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
28
+ first_document = nil
29
+ document_count = 0
30
+
31
+ origin_each do |document|
32
+ document_count += 1
33
+
34
+ if document_count == 1
35
+ first_document = document
36
+ elsif document_count == 2
37
+ Bullet::Detector::NPlusOneQuery.add_possible_objects([first_document, document])
38
+ yield(first_document)
39
+ first_document = nil
40
+ yield(document)
41
+ else
42
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(document)
43
+ yield(document)
44
+ end
34
45
  end
35
- records.each(&block)
46
+
47
+ if document_count == 1
48
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(first_document)
49
+ yield(first_document)
50
+ end
51
+
52
+ self
36
53
  end
37
54
 
38
55
  def eager_load(docs)
@@ -7,6 +7,7 @@ module Bullet
7
7
  autoload :NPlusOneQuery, 'bullet/notification/n_plus_one_query'
8
8
  autoload :CounterCache, 'bullet/notification/counter_cache'
9
9
 
10
- class UnoptimizedQueryError < StandardError; end
10
+ class UnoptimizedQueryError < StandardError
11
+ end
11
12
  end
12
13
  end
data/lib/bullet/rack.rb CHANGED
@@ -17,14 +17,16 @@ module Bullet
17
17
  response_body = nil
18
18
 
19
19
  if Bullet.notification?
20
- if !Bullet.skip_html_injection? && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
20
+ if Bullet.inject_into_page? && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
21
21
  if html_request?(headers, response)
22
22
  response_body = response_body(response)
23
23
  response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
24
24
  response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
25
- response_body = append_to_html_body(response_body, xhr_script)
25
+ if Bullet.add_footer && !Bullet.skip_http_headers
26
+ response_body = append_to_html_body(response_body, xhr_script)
27
+ end
26
28
  headers['Content-Length'] = response_body.bytesize.to_s
27
- else
29
+ elsif !Bullet.skip_http_headers
28
30
  set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
29
31
  set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
30
32
  end
@@ -46,6 +48,7 @@ module Bullet
46
48
 
47
49
  def append_to_html_body(response_body, content)
48
50
  body = response_body.dup
51
+ content = content.html_safe if content.respond_to?(:html_safe)
49
52
  if body.include?('</body>')
50
53
  position = body.rindex('</body>')
51
54
  body.insert(position, content)
@@ -55,14 +58,14 @@ module Bullet
55
58
  end
56
59
 
57
60
  def footer_note
58
- "<div #{footer_div_attributes}>" + footer_header + '<br>' + Bullet.footer_info.uniq.join('<br>') + '</div>'
61
+ "<details #{details_attributes}><summary #{summary_attributes}>Bullet Warnings</summary><div #{footer_content_attributes}>#{Bullet.footer_info.uniq.join('<br>')}#{footer_console_message}</div></details>"
59
62
  end
60
63
 
61
64
  def set_header(headers, header_name, header_array)
62
65
  # Many proxy applications such as Nginx and AWS ELB limit
63
66
  # the size a header to 8KB, so truncate the list of reports to
64
67
  # be under that limit
65
- header_array.pop while header_array.to_json.length > 8 * 1_024
68
+ header_array.pop while header_array.to_json.length > 8 * 1024
66
69
  headers[header_name] = header_array.to_json
67
70
  end
68
71
 
@@ -81,30 +84,35 @@ module Bullet
81
84
  def response_body(response)
82
85
  if response.respond_to?(:body)
83
86
  Array === response.body ? response.body.first : response.body
84
- else
87
+ elsif response.respond_to?(:first)
85
88
  response.first
86
89
  end
87
90
  end
88
91
 
89
92
  private
90
93
 
91
- def footer_div_attributes
94
+ def details_attributes
95
+ <<~EOF
96
+ id="bullet-footer" data-is-bullet-footer
97
+ style="cursor: pointer; position: fixed; left: 0px; bottom: 0px; z-index: 9999; background: #fdf2f2; color: #9b1c1c; font-size: 12px; border-radius: 0px 8px 0px 0px; border: 1px solid #9b1c1c;"
98
+ EOF
99
+ end
100
+
101
+ def summary_attributes
92
102
  <<~EOF
93
- id="bullet-footer" data-is-bullet-footer ondblclick="this.parentNode.removeChild(this);" style="position: fixed; bottom: 0pt; left: 0pt; cursor: pointer; border-style: solid; border-color: rgb(153, 153, 153);
94
- -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
95
- -moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
96
- padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
97
- color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
103
+ style="font-weight: 600; padding: 2px 8px"
98
104
  EOF
99
105
  end
100
106
 
101
- def footer_header
102
- cancel_button =
103
- "<span onclick='this.parentNode.remove()' style='position:absolute; right: 10px; top: 0px; font-weight: bold; color: #333;'>&times;</span>"
107
+ def footer_content_attributes
108
+ <<~EOF
109
+ style="padding: 8px; border-top: 1px solid #9b1c1c;"
110
+ EOF
111
+ end
112
+
113
+ def footer_console_message
104
114
  if Bullet.console_enabled?
105
- "<span>See 'Uniform Notifier' in JS Console for Stacktrace</span>#{cancel_button}"
106
- else
107
- cancel_button
115
+ "<br/><span style='font-style: italic;'>See 'Uniform Notifier' in JS Console for Stacktrace</span>"
108
116
  end
109
117
  end
110
118
 
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
+ require "bundler"
2
3
 
3
4
  module Bullet
4
5
  module StackTraceFilter
5
6
  VENDOR_PATH = '/vendor'
7
+ IS_RUBY_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
6
8
 
7
9
  def caller_in_project
8
10
  vendor_root = Bullet.app_root + VENDOR_PATH
@@ -10,8 +12,9 @@ module Bullet
10
12
  select_caller_locations do |location|
11
13
  caller_path = location_as_path(location)
12
14
  caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&
13
- !caller_path.include?(bundler_path) ||
14
- Bullet.stacktrace_includes.any? { |include_pattern| pattern_matches?(location, include_pattern) }
15
+ !caller_path.include?(bundler_path) || Bullet.stacktrace_includes.any? { |include_pattern|
16
+ pattern_matches?(location, include_pattern)
17
+ }
15
18
  end
16
19
  end
17
20
 
@@ -47,20 +50,15 @@ module Bullet
47
50
  end
48
51
 
49
52
  def location_as_path(location)
50
- ruby_19? ? location : location.absolute_path.to_s
53
+ IS_RUBY_19 ? location : location.absolute_path.to_s
51
54
  end
52
55
 
53
56
  def select_caller_locations
54
- if ruby_19?
57
+ if IS_RUBY_19
55
58
  caller.select { |caller_path| yield caller_path }
56
59
  else
57
60
  caller_locations.select { |location| yield location }
58
61
  end
59
62
  end
60
-
61
- def ruby_19?
62
- @ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0') if @ruby_19.nil?
63
- @ruby_19
64
- end
65
63
  end
66
64
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '6.1.0'
4
+ VERSION = '7.0.0'
5
5
  end