bullet 5.5.0 → 6.1.5

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 (123) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/main.yml +66 -0
  3. data/CHANGELOG.md +83 -1
  4. data/Gemfile +6 -1
  5. data/Gemfile.mongoid-7.0 +15 -0
  6. data/Gemfile.rails-4.0 +1 -1
  7. data/Gemfile.rails-4.1 +1 -1
  8. data/Gemfile.rails-4.2 +1 -1
  9. data/Gemfile.rails-5.0 +2 -2
  10. data/Gemfile.rails-5.1 +15 -0
  11. data/Gemfile.rails-5.2 +15 -0
  12. data/Gemfile.rails-6.0 +15 -0
  13. data/Gemfile.rails-6.1 +15 -0
  14. data/Guardfile +2 -2
  15. data/Hacking.md +1 -1
  16. data/README.md +58 -37
  17. data/Rakefile +20 -21
  18. data/bullet.gemspec +21 -15
  19. data/lib/bullet/active_job.rb +13 -0
  20. data/lib/bullet/active_record4.rb +15 -34
  21. data/lib/bullet/active_record41.rb +14 -31
  22. data/lib/bullet/active_record42.rb +23 -36
  23. data/lib/bullet/active_record5.rb +178 -158
  24. data/lib/bullet/active_record52.rb +251 -0
  25. data/lib/bullet/active_record60.rb +278 -0
  26. data/lib/bullet/active_record61.rb +278 -0
  27. data/lib/bullet/bullet_xhr.js +64 -0
  28. data/lib/bullet/dependency.rb +60 -32
  29. data/lib/bullet/detector/association.rb +41 -32
  30. data/lib/bullet/detector/base.rb +2 -0
  31. data/lib/bullet/detector/counter_cache.rb +25 -17
  32. data/lib/bullet/detector/n_plus_one_query.rb +38 -24
  33. data/lib/bullet/detector/unused_eager_loading.rb +35 -26
  34. data/lib/bullet/detector.rb +2 -0
  35. data/lib/bullet/ext/object.rb +9 -5
  36. data/lib/bullet/ext/string.rb +3 -1
  37. data/lib/bullet/mongoid4x.rb +9 -8
  38. data/lib/bullet/mongoid5x.rb +9 -8
  39. data/lib/bullet/mongoid6x.rb +13 -12
  40. data/lib/bullet/mongoid7x.rb +57 -0
  41. data/lib/bullet/notification/base.rb +28 -25
  42. data/lib/bullet/notification/counter_cache.rb +3 -1
  43. data/lib/bullet/notification/n_plus_one_query.rb +8 -7
  44. data/lib/bullet/notification/unused_eager_loading.rb +8 -7
  45. data/lib/bullet/notification.rb +4 -1
  46. data/lib/bullet/notification_collector.rb +2 -1
  47. data/lib/bullet/rack.rb +66 -35
  48. data/lib/bullet/registry/association.rb +2 -0
  49. data/lib/bullet/registry/base.rb +2 -0
  50. data/lib/bullet/registry/object.rb +2 -0
  51. data/lib/bullet/registry.rb +2 -0
  52. data/lib/bullet/stack_trace_filter.rb +50 -20
  53. data/lib/bullet/version.rb +3 -2
  54. data/lib/bullet.rb +136 -55
  55. data/lib/generators/bullet/install_generator.rb +48 -0
  56. data/perf/benchmark.rb +19 -22
  57. data/rails/init.rb +2 -0
  58. data/spec/bullet/detector/association_spec.rb +6 -4
  59. data/spec/bullet/detector/base_spec.rb +2 -0
  60. data/spec/bullet/detector/counter_cache_spec.rb +17 -15
  61. data/spec/bullet/detector/n_plus_one_query_spec.rb +75 -46
  62. data/spec/bullet/detector/unused_eager_loading_spec.rb +45 -26
  63. data/spec/bullet/ext/object_spec.rb +18 -11
  64. data/spec/bullet/ext/string_spec.rb +7 -5
  65. data/spec/bullet/notification/base_spec.rb +38 -39
  66. data/spec/bullet/notification/counter_cache_spec.rb +5 -3
  67. data/spec/bullet/notification/n_plus_one_query_spec.rb +20 -5
  68. data/spec/bullet/notification/unused_eager_loading_spec.rb +9 -3
  69. data/spec/bullet/notification_collector_spec.rb +12 -10
  70. data/spec/bullet/rack_spec.rb +200 -48
  71. data/spec/bullet/registry/association_spec.rb +12 -10
  72. data/spec/bullet/registry/base_spec.rb +22 -20
  73. data/spec/bullet/registry/object_spec.rb +6 -4
  74. data/spec/bullet_spec.rb +64 -33
  75. data/spec/integration/{active_record4 → active_record}/association_spec.rb +238 -223
  76. data/spec/integration/counter_cache_spec.rb +26 -44
  77. data/spec/integration/mongoid/association_spec.rb +53 -65
  78. data/spec/models/address.rb +2 -0
  79. data/spec/models/attachment.rb +5 -0
  80. data/spec/models/author.rb +2 -0
  81. data/spec/models/base_user.rb +2 -0
  82. data/spec/models/category.rb +2 -0
  83. data/spec/models/city.rb +2 -0
  84. data/spec/models/client.rb +4 -0
  85. data/spec/models/comment.rb +3 -1
  86. data/spec/models/company.rb +2 -0
  87. data/spec/models/country.rb +2 -0
  88. data/spec/models/deal.rb +5 -0
  89. data/spec/models/document.rb +4 -2
  90. data/spec/models/entry.rb +2 -0
  91. data/spec/models/firm.rb +3 -0
  92. data/spec/models/folder.rb +2 -0
  93. data/spec/models/group.rb +4 -0
  94. data/spec/models/mongoid/address.rb +3 -1
  95. data/spec/models/mongoid/category.rb +4 -2
  96. data/spec/models/mongoid/comment.rb +3 -1
  97. data/spec/models/mongoid/company.rb +3 -1
  98. data/spec/models/mongoid/entry.rb +3 -1
  99. data/spec/models/mongoid/post.rb +6 -4
  100. data/spec/models/mongoid/user.rb +2 -0
  101. data/spec/models/newspaper.rb +3 -1
  102. data/spec/models/page.rb +2 -0
  103. data/spec/models/person.rb +2 -0
  104. data/spec/models/pet.rb +2 -0
  105. data/spec/models/post.rb +19 -0
  106. data/spec/models/relationship.rb +2 -0
  107. data/spec/models/reply.rb +2 -0
  108. data/spec/models/student.rb +2 -0
  109. data/spec/models/submission.rb +3 -0
  110. data/spec/models/teacher.rb +2 -0
  111. data/spec/models/user.rb +3 -0
  112. data/spec/models/writer.rb +2 -0
  113. data/spec/spec_helper.rb +19 -23
  114. data/spec/support/bullet_ext.rb +10 -9
  115. data/spec/support/mongo_seed.rb +38 -57
  116. data/spec/support/rack_double.rb +13 -19
  117. data/spec/support/sqlite_seed.rb +104 -76
  118. data/tasks/bullet_tasks.rake +4 -2
  119. data/test.sh +5 -0
  120. data/update.sh +3 -0
  121. metadata +34 -17
  122. data/.travis.yml +0 -16
  123. data/spec/integration/active_record5/association_spec.rb +0 -768
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Mongoid
3
5
  def self.enable
@@ -21,20 +23,21 @@ module Bullet
21
23
  end
22
24
 
23
25
  def each(&block)
24
- records = view.map{ |doc| ::Mongoid::Factory.from_db(klass, doc) }
26
+ return to_enum unless block
27
+
28
+ records = []
29
+ origin_each { |record| records << record }
25
30
  if records.length > 1
26
31
  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
27
32
  elsif records.size == 1
28
33
  Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
29
34
  end
30
- origin_each(&block)
35
+ records.each(&block)
31
36
  end
32
37
 
33
38
  def eager_load(docs)
34
39
  associations = criteria.inclusions.map(&:name)
35
- docs.each do |doc|
36
- Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
37
- end
40
+ docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }
38
41
  Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
39
42
  origin_eager_load(docs)
40
43
  end
@@ -45,9 +48,7 @@ module Bullet
45
48
 
46
49
  def get_relation(name, metadata, object, reload = false)
47
50
  result = origin_get_relation(name, metadata, object, reload)
48
- if metadata.macro !~ /embed/
49
- Bullet::Detector::NPlusOneQuery.call_association(self, name)
50
- end
51
+ Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/
51
52
  result
52
53
  end
53
54
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Mongoid
3
5
  def self.enable
@@ -8,33 +10,34 @@ module Bullet
8
10
  alias_method :origin_each, :each
9
11
  alias_method :origin_eager_load, :eager_load
10
12
 
11
- def first
12
- result = origin_first
13
+ def first(opt = {})
14
+ result = origin_first(opt)
13
15
  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
14
16
  result
15
17
  end
16
18
 
17
- def last
18
- result = origin_last
19
+ def last(opt = {})
20
+ result = origin_last(opt)
19
21
  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
20
22
  result
21
23
  end
22
24
 
23
25
  def each(&block)
24
- records = view.map{ |doc| ::Mongoid::Factory.from_db(klass, doc) }
26
+ return to_enum unless block
27
+
28
+ records = []
29
+ origin_each { |record| records << record }
25
30
  if records.length > 1
26
31
  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
27
32
  elsif records.size == 1
28
33
  Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
29
34
  end
30
- origin_each(&block)
35
+ records.each(&block)
31
36
  end
32
37
 
33
38
  def eager_load(docs)
34
39
  associations = criteria.inclusions.map(&:name)
35
- docs.each do |doc|
36
- Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
37
- end
40
+ docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }
38
41
  Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
39
42
  origin_eager_load(docs)
40
43
  end
@@ -45,9 +48,7 @@ module Bullet
45
48
 
46
49
  def get_relation(name, metadata, object, reload = false)
47
50
  result = origin_get_relation(name, metadata, object, reload)
48
- if metadata.macro !~ /embed/
49
- Bullet::Detector::NPlusOneQuery.call_association(self, name)
50
- end
51
+ Bullet::Detector::NPlusOneQuery.call_association(self, name) if metadata.macro !~ /embed/
51
52
  result
52
53
  end
53
54
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bullet
4
+ module Mongoid
5
+ def self.enable
6
+ require 'mongoid'
7
+ ::Mongoid::Contextual::Mongo.class_eval do
8
+ alias_method :origin_first, :first
9
+ alias_method :origin_last, :last
10
+ alias_method :origin_each, :each
11
+ alias_method :origin_eager_load, :eager_load
12
+
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
23
+ end
24
+
25
+ def each(&block)
26
+ return to_enum unless block
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)
34
+ end
35
+ records.each(&block)
36
+ end
37
+
38
+ def eager_load(docs)
39
+ associations = criteria.inclusions.map(&:name)
40
+ docs.each { |doc| Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations) }
41
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
42
+ origin_eager_load(docs)
43
+ end
44
+ end
45
+
46
+ ::Mongoid::Association::Accessors.class_eval do
47
+ alias_method :origin_get_relation, :get_relation
48
+
49
+ def get_relation(name, association, object, reload = false)
50
+ result = origin_get_relation(name, association, object, reload)
51
+ Bullet::Detector::NPlusOneQuery.call_association(self, name) unless association.embedded?
52
+ result
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Notification
3
5
  class Base
@@ -6,29 +8,34 @@ module Bullet
6
8
 
7
9
  def initialize(base_class, association_or_associations, path = nil)
8
10
  @base_class = base_class
9
- @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]
10
13
  @path = path
11
14
  end
12
15
 
13
16
  def title
14
- raise NoMethodError.new("no method title defined")
17
+ raise NoMethodError, 'no method title defined'
15
18
  end
16
19
 
17
20
  def body
18
- raise NoMethodError.new("no method body defined")
21
+ raise NoMethodError, 'no method body defined'
19
22
  end
20
23
 
21
24
  def call_stack_messages
22
- ""
25
+ ''
23
26
  end
24
27
 
25
28
  def whoami
26
- @user ||= ENV['USER'].presence || (`whoami`.chomp rescue "")
27
- if @user.present?
28
- "user: #{@user}"
29
- else
30
- ""
31
- 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}" : ''
32
39
  end
33
40
 
34
41
  def body_with_caller
@@ -36,24 +43,19 @@ module Bullet
36
43
  end
37
44
 
38
45
  def notify_inline
39
- self.notifier.inline_notify(notification_data)
46
+ notifier.inline_notify(notification_data)
40
47
  end
41
48
 
42
49
  def notify_out_of_channel
43
- self.notifier.out_of_channel_notify(notification_data)
50
+ notifier.out_of_channel_notify(notification_data)
44
51
  end
45
52
 
46
53
  def short_notice
47
- [whoami.presence, url, title, body].compact.join(" ")
54
+ [whoami.presence, url, title, body].compact.join(' ')
48
55
  end
49
56
 
50
57
  def notification_data
51
- {
52
- :user => whoami,
53
- :url => url,
54
- :title => title,
55
- :body => body_with_caller,
56
- }
58
+ { user: whoami, url: url, title: title, body: body_with_caller }
57
59
  end
58
60
 
59
61
  def eql?(other)
@@ -65,13 +67,14 @@ module Bullet
65
67
  end
66
68
 
67
69
  protected
68
- def klazz_associations_str
69
- " #{@base_class} => [#{@associations.map(&:inspect).join(', ')}]"
70
- end
71
70
 
72
- def associations_str
73
- ":includes => #{@associations.map{ |a| a.to_s.to_sym unless a.is_a? Hash }.inspect}"
74
- end
71
+ def klazz_associations_str
72
+ " #{@base_class} => [#{@associations.map(&:inspect).join(', ')}]"
73
+ end
74
+
75
+ def associations_str
76
+ ".includes(#{@associations.map { |a| a.to_s.to_sym }.inspect})"
77
+ end
75
78
  end
76
79
  end
77
80
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Notification
3
5
  class CounterCache < Base
@@ -6,7 +8,7 @@ module Bullet
6
8
  end
7
9
 
8
10
  def title
9
- "Need Counter Cache"
11
+ 'Need Counter Cache'
10
12
  end
11
13
  end
12
14
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Notification
3
5
  class NPlusOneQuery < Base
@@ -8,7 +10,7 @@ module Bullet
8
10
  end
9
11
 
10
12
  def body
11
- "#{klazz_associations_str}\n Add to your finder: #{associations_str}"
13
+ "#{klazz_associations_str}\n Add to your query: #{associations_str}"
12
14
  end
13
15
 
14
16
  def title
@@ -16,15 +18,14 @@ module Bullet
16
18
  end
17
19
 
18
20
  def notification_data
19
- super.merge(
20
- :backtrace => []
21
- )
21
+ super.merge(backtrace: [])
22
22
  end
23
23
 
24
24
  protected
25
- def call_stack_messages
26
- (['Call stack'] + @callers).join( "\n " )
27
- end
25
+
26
+ def call_stack_messages
27
+ (['Call stack'] + @callers).join("\n ")
28
+ end
28
29
  end
29
30
  end
30
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Notification
3
5
  class UnusedEagerLoading < Base
@@ -8,7 +10,7 @@ module Bullet
8
10
  end
9
11
 
10
12
  def body
11
- "#{klazz_associations_str}\n Remove from your finder: #{associations_str}"
13
+ "#{klazz_associations_str}\n Remove from your query: #{associations_str}"
12
14
  end
13
15
 
14
16
  def title
@@ -16,15 +18,14 @@ module Bullet
16
18
  end
17
19
 
18
20
  def notification_data
19
- super.merge(
20
- :backtrace => []
21
- )
21
+ super.merge(backtrace: [])
22
22
  end
23
23
 
24
24
  protected
25
- def call_stack_messages
26
- (['Call stack'] + @callers).join( "\n " )
27
- end
25
+
26
+ def call_stack_messages
27
+ (['Call stack'] + @callers).join("\n ")
28
+ end
28
29
  end
29
30
  end
30
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Notification
3
5
  autoload :Base, 'bullet/notification/base'
@@ -5,6 +7,7 @@ module Bullet
5
7
  autoload :NPlusOneQuery, 'bullet/notification/n_plus_one_query'
6
8
  autoload :CounterCache, 'bullet/notification/counter_cache'
7
9
 
8
- class UnoptimizedQueryError < StandardError; end
10
+ class UnoptimizedQueryError < StandardError
11
+ end
9
12
  end
10
13
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
5
  module Bullet
@@ -21,4 +23,3 @@ module Bullet
21
23
  end
22
24
  end
23
25
  end
24
-
data/lib/bullet/rack.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  class Rack
3
5
  include Dependency
@@ -8,17 +10,26 @@ module Bullet
8
10
 
9
11
  def call(env)
10
12
  return @app.call(env) unless Bullet.enable?
13
+
11
14
  Bullet.start_request
12
15
  status, headers, response = @app.call(env)
13
16
 
14
17
  response_body = nil
18
+
15
19
  if Bullet.notification?
16
- if !file?(headers) && !sse?(headers) && !empty?(response) &&
17
- status == 200 && !response_body(response).frozen? && html_request?(headers, response)
18
- response_body = response_body(response)
19
- append_to_html_body(response_body, footer_note) if Bullet.add_footer
20
- append_to_html_body(response_body, Bullet.gather_inline_notifications)
21
- 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
22
33
  end
23
34
  Bullet.perform_out_of_channel_notifications(env)
24
35
  end
@@ -29,65 +40,85 @@ module Bullet
29
40
 
30
41
  # fix issue if response's body is a Proc
31
42
  def empty?(response)
32
- # response may be ["Not Found"], ["Move Permanently"], etc.
33
- if rails?
34
- (response.is_a?(Array) && response.size <= 1) ||
35
- !response.respond_to?(:body) ||
36
- !response_body(response).respond_to?(:empty?) ||
37
- response_body(response).empty?
38
- else
39
- body = response_body(response)
40
- body.nil? || body.empty?
41
- end
43
+ # response may be ["Not Found"], ["Move Permanently"], etc, but
44
+ # those should not happen if the status is 200
45
+ body = response_body(response)
46
+ body.nil? || body.empty?
42
47
  end
43
48
 
44
49
  def append_to_html_body(response_body, content)
45
- if response_body.include?('</body>')
46
- position = response_body.rindex('</body>')
47
- response_body.insert(position, content)
50
+ body = response_body.dup
51
+ content = content.html_safe if content.respond_to?(:html_safe)
52
+ if body.include?('</body>')
53
+ position = body.rindex('</body>')
54
+ body.insert(position, content)
48
55
  else
49
- response_body << content
56
+ body << content
50
57
  end
51
58
  end
52
59
 
53
60
  def footer_note
54
- "<div #{footer_div_attributes}>" + footer_close_button + 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>"
62
+ end
63
+
64
+ def set_header(headers, header_name, header_array)
65
+ # Many proxy applications such as Nginx and AWS ELB limit
66
+ # the size a header to 8KB, so truncate the list of reports to
67
+ # be under that limit
68
+ header_array.pop while header_array.to_json.length > 8 * 1024
69
+ headers[header_name] = header_array.to_json
55
70
  end
56
71
 
57
72
  def file?(headers)
58
- headers["Content-Transfer-Encoding"] == "binary"
73
+ headers['Content-Transfer-Encoding'] == 'binary' || headers['Content-Disposition']
59
74
  end
60
75
 
61
76
  def sse?(headers)
62
- headers["Content-Type"] == "text/event-stream"
77
+ headers['Content-Type'] == 'text/event-stream'
63
78
  end
64
79
 
65
80
  def html_request?(headers, response)
66
- headers['Content-Type'] && headers['Content-Type'].include?('text/html') && response_body(response).include?("<html")
81
+ headers['Content-Type']&.include?('text/html') && response_body(response).include?('<html')
67
82
  end
68
83
 
69
84
  def response_body(response)
70
85
  if response.respond_to?(:body)
71
86
  Array === response.body ? response.body.first : response.body
72
- else
87
+ elsif response.respond_to?(:first)
73
88
  response.first
74
89
  end
75
90
  end
76
91
 
77
92
  private
78
93
 
79
- def footer_div_attributes
80
- <<EOF
81
- 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);
82
- -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
83
- -moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
84
- padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
85
- color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
86
- EOF
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
102
+ <<~EOF
103
+ style="font-weight: 600; padding: 2px 8px"
104
+ EOF
105
+ end
106
+
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
114
+ if Bullet.console_enabled?
115
+ "<br/><span style='font-style: italic;'>See 'Uniform Notifier' in JS Console for Stacktrace</span>"
116
+ end
87
117
  end
88
118
 
89
- def footer_close_button
90
- "<span onclick='this.parentNode.remove()' style='position:absolute; right: 10px; top: 0px; font-weight: bold; color: #333;'>&times;</span>"
119
+ # Make footer work for XHR requests by appending data to the footer
120
+ def xhr_script
121
+ "<script type='text/javascript'>#{File.read("#{__dir__}/bullet_xhr.js")}</script>"
91
122
  end
92
123
  end
93
124
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Registry
3
5
  class Association < Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Registry
3
5
  class Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Registry
3
5
  class Object < Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Registry
3
5
  autoload :Base, 'bullet/registry/base'
@@ -1,33 +1,63 @@
1
+ # frozen_string_literal: true
2
+ require "bundler"
3
+
1
4
  module Bullet
2
5
  module StackTraceFilter
3
- VENDOR_PATH = "/vendor"
6
+ VENDOR_PATH = '/vendor'
7
+ IS_RUBY_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
4
8
 
5
9
  def caller_in_project
6
- app_root = rails? ? Rails.root.to_s : Dir.pwd
7
- vendor_root = app_root + VENDOR_PATH
8
- caller.select do |caller_path|
9
- caller_path.include?(app_root) && !caller_path.include?(vendor_root) ||
10
- Bullet.stacktrace_includes.any? do |include_pattern|
11
- case include_pattern
12
- when String
13
- caller_path.include?(include_pattern)
14
- when Regexp
15
- caller_path =~ include_pattern
16
- end
17
- end
10
+ vendor_root = Bullet.app_root + VENDOR_PATH
11
+ bundler_path = Bundler.bundle_path.to_s
12
+ select_caller_locations do |location|
13
+ caller_path = location_as_path(location)
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
+ }
18
18
  end
19
19
  end
20
20
 
21
21
  def excluded_stacktrace_path?
22
22
  Bullet.stacktrace_excludes.any? do |exclude_pattern|
23
- caller_in_project.any? do |caller_path|
24
- case exclude_pattern
25
- when String
26
- caller_path.include?(exclude_pattern)
27
- when Regexp
28
- caller_path =~ exclude_pattern
29
- end
23
+ caller_in_project.any? { |location| pattern_matches?(location, exclude_pattern) }
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def pattern_matches?(location, pattern)
30
+ path = location_as_path(location)
31
+ case pattern
32
+ when Array
33
+ pattern_path = pattern.first
34
+ filter = pattern.last
35
+ return false unless pattern_matches?(location, pattern_path)
36
+
37
+ case filter
38
+ when Range
39
+ filter.include?(location.lineno)
40
+ when Integer
41
+ filter == location.lineno
42
+ when String
43
+ filter == location.base_label
30
44
  end
45
+ when String
46
+ path.include?(pattern)
47
+ when Regexp
48
+ path =~ pattern
49
+ end
50
+ end
51
+
52
+ def location_as_path(location)
53
+ IS_RUBY_19 ? location : location.absolute_path.to_s
54
+ end
55
+
56
+ def select_caller_locations
57
+ if IS_RUBY_19
58
+ caller.select { |caller_path| yield caller_path }
59
+ else
60
+ caller_locations.select { |location| yield location }
31
61
  end
32
62
  end
33
63
  end
@@ -1,4 +1,5 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  module Bullet
3
- VERSION = "5.5.0"
4
+ VERSION = '6.1.5'
4
5
  end