bullet 5.9.0 → 7.0.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 (71) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/main.yml +82 -0
  3. data/CHANGELOG.md +72 -0
  4. data/Gemfile.rails-4.0 +1 -1
  5. data/Gemfile.rails-4.1 +1 -1
  6. data/Gemfile.rails-4.2 +1 -1
  7. data/Gemfile.rails-5.0 +1 -1
  8. data/Gemfile.rails-5.1 +1 -1
  9. data/Gemfile.rails-5.2 +1 -1
  10. data/Gemfile.rails-6.0 +15 -0
  11. data/Gemfile.rails-6.1 +15 -0
  12. data/Gemfile.rails-7.0 +10 -0
  13. data/MIT-LICENSE +1 -1
  14. data/README.md +59 -33
  15. data/lib/bullet/active_job.rb +13 -0
  16. data/lib/bullet/active_record4.rb +9 -32
  17. data/lib/bullet/active_record41.rb +8 -27
  18. data/lib/bullet/active_record42.rb +9 -24
  19. data/lib/bullet/active_record5.rb +190 -179
  20. data/lib/bullet/active_record52.rb +184 -169
  21. data/lib/bullet/active_record60.rb +274 -0
  22. data/lib/bullet/active_record61.rb +274 -0
  23. data/lib/bullet/active_record70.rb +277 -0
  24. data/lib/bullet/bullet_xhr.js +64 -0
  25. data/lib/bullet/dependency.rb +60 -36
  26. data/lib/bullet/detector/association.rb +26 -20
  27. data/lib/bullet/detector/counter_cache.rb +15 -11
  28. data/lib/bullet/detector/n_plus_one_query.rb +24 -14
  29. data/lib/bullet/detector/unused_eager_loading.rb +8 -5
  30. data/lib/bullet/ext/object.rb +4 -2
  31. data/lib/bullet/mongoid4x.rb +3 -7
  32. data/lib/bullet/mongoid5x.rb +3 -7
  33. data/lib/bullet/mongoid6x.rb +3 -7
  34. data/lib/bullet/mongoid7x.rb +34 -23
  35. data/lib/bullet/notification/base.rb +14 -18
  36. data/lib/bullet/notification/n_plus_one_query.rb +2 -4
  37. data/lib/bullet/notification/unused_eager_loading.rb +2 -4
  38. data/lib/bullet/notification.rb +2 -1
  39. data/lib/bullet/rack.rb +55 -27
  40. data/lib/bullet/stack_trace_filter.rb +11 -19
  41. data/lib/bullet/version.rb +1 -1
  42. data/lib/bullet.rb +68 -42
  43. data/lib/generators/bullet/install_generator.rb +22 -23
  44. data/perf/benchmark.rb +11 -14
  45. data/spec/bullet/detector/counter_cache_spec.rb +6 -6
  46. data/spec/bullet/detector/n_plus_one_query_spec.rb +8 -4
  47. data/spec/bullet/detector/unused_eager_loading_spec.rb +25 -8
  48. data/spec/bullet/ext/object_spec.rb +10 -5
  49. data/spec/bullet/notification/base_spec.rb +5 -7
  50. data/spec/bullet/notification/n_plus_one_query_spec.rb +16 -3
  51. data/spec/bullet/notification/unused_eager_loading_spec.rb +5 -1
  52. data/spec/bullet/rack_spec.rb +161 -11
  53. data/spec/bullet/registry/association_spec.rb +2 -2
  54. data/spec/bullet/registry/base_spec.rb +1 -1
  55. data/spec/bullet_spec.rb +25 -44
  56. data/spec/integration/active_record/association_spec.rb +115 -144
  57. data/spec/integration/counter_cache_spec.rb +14 -34
  58. data/spec/integration/mongoid/association_spec.rb +19 -33
  59. data/spec/models/attachment.rb +5 -0
  60. data/spec/models/deal.rb +5 -0
  61. data/spec/models/post.rb +2 -0
  62. data/spec/models/role.rb +7 -0
  63. data/spec/models/submission.rb +1 -0
  64. data/spec/models/user.rb +2 -0
  65. data/spec/spec_helper.rb +4 -10
  66. data/spec/support/bullet_ext.rb +8 -9
  67. data/spec/support/mongo_seed.rb +3 -16
  68. data/spec/support/sqlite_seed.rb +38 -0
  69. data/test.sh +3 -0
  70. metadata +21 -8
  71. data/.travis.yml +0 -12
@@ -6,20 +6,23 @@ module Bullet
6
6
  extend Dependency
7
7
  extend StackTraceFilter
8
8
 
9
- class <<self
10
- # executed when object.assocations is called.
9
+ class << self
10
+ # executed when object.associations is called.
11
11
  # first, it keeps this method call for object.association.
12
12
  # then, it checks if this associations call is unpreload.
13
13
  # if it is, keeps this unpreload associations and caller.
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
@@ -30,17 +33,21 @@ module Bullet
30
33
  return unless Bullet.start?
31
34
  return unless Bullet.n_plus_one_query_enable?
32
35
 
33
- objects = Array(object_or_objects)
34
- return if objects.map(&:primary_key_value).compact.empty?
36
+ objects = Array.wrap(object_or_objects)
37
+ return if objects.map(&:bullet_primary_key_value).compact.empty?
38
+ return if objects.all? { |obj| obj.class.name =~ /^HABTM_/ }
35
39
 
36
- Bullet.debug('Detector::NPlusOneQuery#add_possible_objects', "objects: #{objects.map(&:bullet_key).join(', ')}")
40
+ Bullet.debug(
41
+ 'Detector::NPlusOneQuery#add_possible_objects',
42
+ "objects: #{objects.map(&:bullet_key).join(', ')}"
43
+ )
37
44
  objects.each { |object| possible_objects.add object.bullet_key }
38
45
  end
39
46
 
40
47
  def add_impossible_object(object)
41
48
  return unless Bullet.start?
42
49
  return unless Bullet.n_plus_one_query_enable?
43
- return unless object.primary_key_value
50
+ return unless object.bullet_primary_key_value
44
51
 
45
52
  Bullet.debug('Detector::NPlusOneQuery#add_impossible_object', "object: #{object.bullet_key}")
46
53
  impossible_objects.add object.bullet_key
@@ -49,9 +56,12 @@ module Bullet
49
56
  def add_inversed_object(object, association)
50
57
  return unless Bullet.start?
51
58
  return unless Bullet.n_plus_one_query_enable?
52
- return unless object.primary_key_value
59
+ return unless object.bullet_primary_key_value
53
60
 
54
- Bullet.debug('Detector::NPlusOneQuery#add_inversed_object', "object: #{object.bullet_key}, association: #{association}")
61
+ Bullet.debug(
62
+ 'Detector::NPlusOneQuery#add_inversed_object',
63
+ "object: #{object.bullet_key}, association: #{association}"
64
+ )
55
65
  inversed_objects.add object.bullet_key, association
56
66
  end
57
67
 
@@ -72,9 +82,9 @@ module Bullet
72
82
  def association?(object, associations)
73
83
  value = object_associations[object.bullet_key]
74
84
  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
85
+ # associations == v comparison order is important here because
86
+ # v variable might be a squeel node where :== method is redefined,
87
+ # so it does not compare values at all and return unexpected results
78
88
  result = v.is_a?(Hash) ? v.key?(associations) : associations == v
79
89
  return true if result
80
90
  end
@@ -85,7 +95,7 @@ module Bullet
85
95
  private
86
96
 
87
97
  def create_notification(callers, klazz, associations)
88
- notify_associations = Array(associations) - Bullet.get_whitelist_associations(:n_plus_one_query, klazz)
98
+ notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:n_plus_one_query, klazz)
89
99
 
90
100
  if notify_associations.present?
91
101
  notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
@@ -6,11 +6,11 @@ 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
13
- # if association not in call_object_association, then the object => association - call_object_association is ununsed preload assocations
13
+ # if association not in call_object_association, then the object => association - call_object_association is ununsed preload associations
14
14
  def check_unused_preload_associations
15
15
  return unless Bullet.start?
16
16
  return unless Bullet.unused_eager_loading_enable?
@@ -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 = []
@@ -62,7 +65,7 @@ module Bullet
62
65
  private
63
66
 
64
67
  def create_notification(callers, klazz, associations)
65
- notify_associations = Array(associations) - Bullet.get_whitelist_associations(:unused_eager_loading, klazz)
68
+ notify_associations = Array.wrap(associations) - Bullet.get_safelist_associations(:unused_eager_loading, klazz)
66
69
 
67
70
  if notify_associations.present?
68
71
  notice = Bullet::Notification::UnusedEagerLoading.new(callers, klazz, notify_associations)
@@ -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
@@ -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 }
@@ -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
@@ -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 }
@@ -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
@@ -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 }
@@ -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
@@ -4,42 +4,55 @@ module Bullet
4
4
  module Mongoid
5
5
  def self.enable
6
6
  require 'mongoid'
7
+ require 'rubygems'
7
8
  ::Mongoid::Contextual::Mongo.class_eval do
8
9
  alias_method :origin_first, :first
9
10
  alias_method :origin_last, :last
10
11
  alias_method :origin_each, :each
11
12
  alias_method :origin_eager_load, :eager_load
12
13
 
13
- def first(opts = {})
14
- result = origin_first(opts)
15
- Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
16
- result
17
- end
18
-
19
- def last(opts = {})
20
- result = origin_last(opts)
21
- Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
22
- result
14
+ %i[first last].each do |context|
15
+ default = Gem::Version.new(::Mongoid::VERSION) >= Gem::Version.new('7.5') ? nil : {}
16
+ define_method(context) do |opts = default|
17
+ result = send(:"origin_#{context}", opts)
18
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
19
+ result
20
+ end
23
21
  end
24
22
 
25
23
  def each(&block)
26
24
  return to_enum unless block_given?
27
25
 
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)
26
+ first_document = nil
27
+ document_count = 0
28
+
29
+ origin_each do |document|
30
+ document_count += 1
31
+
32
+ if document_count == 1
33
+ first_document = document
34
+ elsif document_count == 2
35
+ Bullet::Detector::NPlusOneQuery.add_possible_objects([first_document, document])
36
+ yield(first_document)
37
+ first_document = nil
38
+ yield(document)
39
+ else
40
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(document)
41
+ yield(document)
42
+ end
43
+ end
44
+
45
+ if document_count == 1
46
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(first_document)
47
+ yield(first_document)
34
48
  end
35
- records.each(&block)
49
+
50
+ self
36
51
  end
37
52
 
38
53
  def eager_load(docs)
39
54
  associations = criteria.inclusions.map(&:name)
40
- docs.each do |doc|
41
- Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
42
- end
55
+ docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }
43
56
  Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
44
57
  origin_eager_load(docs)
45
58
  end
@@ -50,9 +63,7 @@ module Bullet
50
63
 
51
64
  def get_relation(name, association, object, reload = false)
52
65
  result = origin_get_relation(name, association, object, reload)
53
- unless association.embedded?
54
- Bullet::Detector::NPlusOneQuery.call_association(self, name)
55
- end
66
+ Bullet::Detector::NPlusOneQuery.call_association(self, name) unless association.embedded?
56
67
  result
57
68
  end
58
69
  end
@@ -8,7 +8,8 @@ module Bullet
8
8
 
9
9
  def initialize(base_class, association_or_associations, path = nil)
10
10
  @base_class = base_class
11
- @associations = association_or_associations.is_a?(Array) ? association_or_associations : [association_or_associations]
11
+ @associations =
12
+ association_or_associations.is_a?(Array) ? association_or_associations : [association_or_associations]
12
13
  @path = path
13
14
  end
14
15
 
@@ -25,16 +26,16 @@ module Bullet
25
26
  end
26
27
 
27
28
  def whoami
28
- @user ||= ENV['USER'].presence || (begin
29
- `whoami`.chomp
30
- rescue StandardError
31
- ''
32
- end)
33
- if @user.present?
34
- "user: #{@user}"
35
- else
36
- ''
37
- end
29
+ @user ||=
30
+ ENV['USER'].presence ||
31
+ (
32
+ begin
33
+ `whoami`.chomp
34
+ rescue StandardError
35
+ ''
36
+ end
37
+ )
38
+ @user.present? ? "user: #{@user}" : ''
38
39
  end
39
40
 
40
41
  def body_with_caller
@@ -54,12 +55,7 @@ module Bullet
54
55
  end
55
56
 
56
57
  def notification_data
57
- {
58
- user: whoami,
59
- url: url,
60
- title: title,
61
- body: body_with_caller
62
- }
58
+ { user: whoami, url: url, title: title, body: body_with_caller }
63
59
  end
64
60
 
65
61
  def eql?(other)
@@ -77,7 +73,7 @@ module Bullet
77
73
  end
78
74
 
79
75
  def associations_str
80
- ":includes => #{@associations.map { |a| a.to_s.to_sym unless a.is_a? Hash }.inspect}"
76
+ ".includes(#{@associations.map { |a| a.to_s.to_sym }.inspect})"
81
77
  end
82
78
  end
83
79
  end
@@ -10,7 +10,7 @@ module Bullet
10
10
  end
11
11
 
12
12
  def body
13
- "#{klazz_associations_str}\n Add to your finder: #{associations_str}"
13
+ "#{klazz_associations_str}\n Add to your query: #{associations_str}"
14
14
  end
15
15
 
16
16
  def title
@@ -18,9 +18,7 @@ module Bullet
18
18
  end
19
19
 
20
20
  def notification_data
21
- super.merge(
22
- backtrace: []
23
- )
21
+ super.merge(backtrace: [])
24
22
  end
25
23
 
26
24
  protected
@@ -10,7 +10,7 @@ module Bullet
10
10
  end
11
11
 
12
12
  def body
13
- "#{klazz_associations_str}\n Remove from your finder: #{associations_str}"
13
+ "#{klazz_associations_str}\n Remove from your query: #{associations_str}"
14
14
  end
15
15
 
16
16
  def title
@@ -18,9 +18,7 @@ module Bullet
18
18
  end
19
19
 
20
20
  def notification_data
21
- super.merge(
22
- backtrace: []
23
- )
21
+ super.merge(backtrace: [])
24
22
  end
25
23
 
26
24
  protected
@@ -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
@@ -15,13 +15,21 @@ module Bullet
15
15
  status, headers, response = @app.call(env)
16
16
 
17
17
  response_body = nil
18
+
18
19
  if Bullet.notification?
19
- if !file?(headers) && !sse?(headers) && !empty?(response) &&
20
- status == 200 && html_request?(headers, response)
21
- response_body = response_body(response)
22
- response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
23
- response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
24
- headers['Content-Length'] = response_body.bytesize.to_s
20
+ if Bullet.inject_into_page? && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
21
+ if html_request?(headers, response)
22
+ response_body = response_body(response)
23
+ response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
24
+ response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
25
+ if Bullet.add_footer && !Bullet.skip_http_headers
26
+ response_body = append_to_html_body(response_body, xhr_script)
27
+ end
28
+ headers['Content-Length'] = response_body.bytesize.to_s
29
+ elsif !Bullet.skip_http_headers
30
+ set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
31
+ set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
32
+ end
25
33
  end
26
34
  Bullet.perform_out_of_channel_notifications(env)
27
35
  end
@@ -32,20 +40,16 @@ module Bullet
32
40
 
33
41
  # fix issue if response's body is a Proc
34
42
  def empty?(response)
35
- # response may be ["Not Found"], ["Move Permanently"], etc.
36
- if rails?
37
- (response.is_a?(Array) && response.size <= 1) ||
38
- !response.respond_to?(:body) ||
39
- !response_body(response).respond_to?(:empty?) ||
40
- response_body(response).empty?
41
- else
42
- body = response_body(response)
43
- body.nil? || body.empty?
44
- end
43
+ # response may be ["Not Found"], ["Move Permanently"], etc, but
44
+ # those should not happen if the status is 200
45
+ return true if !response.respond_to?(:body) && !response.respond_to?(:first)
46
+ body = response_body(response)
47
+ body.nil? || body.empty?
45
48
  end
46
49
 
47
50
  def append_to_html_body(response_body, content)
48
51
  body = response_body.dup
52
+ content = content.html_safe if content.respond_to?(:html_safe)
49
53
  if body.include?('</body>')
50
54
  position = body.rindex('</body>')
51
55
  body.insert(position, content)
@@ -55,7 +59,15 @@ module Bullet
55
59
  end
56
60
 
57
61
  def footer_note
58
- "<div #{footer_div_attributes}>" + footer_close_button + Bullet.footer_info.uniq.join('<br>') + '</div>'
62
+ "<details #{details_attributes}><summary #{summary_attributes}>Bullet Warnings</summary><div #{footer_content_attributes}>#{Bullet.footer_info.uniq.join('<br>')}#{footer_console_message}</div></details>"
63
+ end
64
+
65
+ def set_header(headers, header_name, header_array)
66
+ # Many proxy applications such as Nginx and AWS ELB limit
67
+ # the size a header to 8KB, so truncate the list of reports to
68
+ # be under that limit
69
+ header_array.pop while header_array.to_json.length > 8 * 1024
70
+ headers[header_name] = header_array.to_json
59
71
  end
60
72
 
61
73
  def file?(headers)
@@ -67,31 +79,47 @@ module Bullet
67
79
  end
68
80
 
69
81
  def html_request?(headers, response)
70
- headers['Content-Type']&.include?('text/html') && response_body(response).include?('<html')
82
+ headers['Content-Type']&.include?('text/html')
71
83
  end
72
84
 
73
85
  def response_body(response)
74
86
  if response.respond_to?(:body)
75
87
  Array === response.body ? response.body.first : response.body
76
- else
88
+ elsif response.respond_to?(:first)
77
89
  response.first
78
90
  end
79
91
  end
80
92
 
81
93
  private
82
94
 
83
- def footer_div_attributes
95
+ def details_attributes
84
96
  <<~EOF
85
- 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);
86
- -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
87
- -moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
88
- padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
89
- color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
97
+ id="bullet-footer" data-is-bullet-footer
98
+ 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;"
90
99
  EOF
91
100
  end
92
101
 
93
- def footer_close_button
94
- "<span onclick='this.parentNode.remove()' style='position:absolute; right: 10px; top: 0px; font-weight: bold; color: #333;'>&times;</span>"
102
+ def summary_attributes
103
+ <<~EOF
104
+ style="font-weight: 600; padding: 2px 8px"
105
+ EOF
106
+ end
107
+
108
+ def footer_content_attributes
109
+ <<~EOF
110
+ style="padding: 8px; border-top: 1px solid #9b1c1c;"
111
+ EOF
112
+ end
113
+
114
+ def footer_console_message
115
+ if Bullet.console_enabled?
116
+ "<br/><span style='font-style: italic;'>See 'Uniform Notifier' in JS Console for Stacktrace</span>"
117
+ end
118
+ end
119
+
120
+ # Make footer work for XHR requests by appending data to the footer
121
+ def xhr_script
122
+ "<script type='text/javascript'>#{File.read("#{__dir__}/bullet_xhr.js")}</script>"
95
123
  end
96
124
  end
97
125
  end
@@ -1,17 +1,20 @@
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
- app_root = rails? ? Rails.root.to_s : Dir.pwd
9
- vendor_root = app_root + VENDOR_PATH
10
+ vendor_root = Bullet.app_root + VENDOR_PATH
10
11
  bundler_path = Bundler.bundle_path.to_s
11
12
  select_caller_locations do |location|
12
13
  caller_path = location_as_path(location)
13
- caller_path.include?(app_root) && !caller_path.include?(vendor_root) && !caller_path.include?(bundler_path) ||
14
- Bullet.stacktrace_includes.any? { |include_pattern| pattern_matches?(location, include_pattern) }
14
+ caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&
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,26 +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?
55
- caller.select do |caller_path|
56
- yield caller_path
57
- end
57
+ if IS_RUBY_19
58
+ caller.select { |caller_path| yield caller_path }
58
59
  else
59
- caller_locations.select do |location|
60
- yield location
61
- end
62
- end
63
- end
64
-
65
- def ruby_19?
66
- if @ruby_19.nil?
67
- @ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
60
+ caller_locations.select { |location| yield location }
68
61
  end
69
- @ruby_19
70
62
  end
71
63
  end
72
64
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '5.9.0'
4
+ VERSION = '7.0.4'
5
5
  end