bullet 6.1.4 → 6.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74ea4e863bfe254dde17af73253ffe7041cd2b2dabee2dad05d1eccb6904f229
4
- data.tar.gz: 284dcd1a516922384bdaf1b3dd9e6a5e6776c213c3ecbc9ca41b223c5b92d36b
3
+ metadata.gz: b21c2c4ca3caf6c41961a7182a5c8d37f6b89aa78b8dfb7badfb940f11a23191
4
+ data.tar.gz: 0f432034f9b4cb2fe6c6572481c797aefcfa69373839fbd0985a7efd2a0bbea9
5
5
  SHA512:
6
- metadata.gz: 26c43bfdac9582f059d067d4f56d4d85ed28416654ff5fd4686bf9977cbfbc8d92f887079954662e904fbe3d1b861d02482ac2fefac30a859fbdad8c3833e225
7
- data.tar.gz: 48decfa9d28b9936f5c1f39c669b6e8061fae649f9036c29d4b28138083a43db8739f3b73bb9c390e9f6baea62aa1f66f79e8ced69cf13e789b8f116c101f25f
6
+ metadata.gz: d2457987f11f034fa457030cf7161d7508fb26230cf9e88a2571c54a934ad9a1330b48dd3d38eede3033bd7ef6c920e564dcbfa43a15354bf72d14ed799aefe2
7
+ data.tar.gz: ed6690fcb3bd778d6d5a27538a0ee78d298c749be1e445c305b9884448ca13f97e5a62c76ac48229a250041bb09ba609d6b154b12cf42a0f3dca66fbb9c83021
@@ -0,0 +1,66 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: CI
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ jobs:
17
+ test_rails_4:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ matrix:
21
+ gemfile: ['Gemfile.rails-4.0', 'Gemfile.rails-4.1', 'Gemfile.rails-4.2']
22
+ env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
23
+ BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
24
+ steps:
25
+ - uses: actions/checkout@v2
26
+ - name: Set up Ruby
27
+ uses: ruby/setup-ruby@v1
28
+ with:
29
+ ruby-version: 2.3
30
+ bundler: 1
31
+ bundler-cache: true
32
+ - name: Run tests
33
+ run: bundle exec rake
34
+ test_rails_5:
35
+ runs-on: ubuntu-latest
36
+ strategy:
37
+ matrix:
38
+ gemfile: ['Gemfile.rails-5.0', 'Gemfile.rails-5.1', 'Gemfile.rails-5.2']
39
+ env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
40
+ BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
41
+ steps:
42
+ - uses: actions/checkout@v2
43
+ - name: Set up Ruby
44
+ uses: ruby/setup-ruby@v1
45
+ with:
46
+ ruby-version: 2.5
47
+ bundler: 1
48
+ bundler-cache: true
49
+ - name: Run tests
50
+ run: bundle exec rake
51
+ test_rails_6:
52
+ runs-on: ubuntu-latest
53
+ strategy:
54
+ matrix:
55
+ gemfile: ['Gemfile.rails-6.0', 'Gemfile.rails-6.1']
56
+ env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
57
+ BUNDLE_GEMFILE: ${{ github.workspace }}/${{ matrix.gemfile }}
58
+ steps:
59
+ - uses: actions/checkout@v2
60
+ - name: Set up Ruby
61
+ uses: ruby/setup-ruby@v1
62
+ with:
63
+ ruby-version: 2.7
64
+ bundler-cache: true
65
+ - name: Run tests
66
+ run: bundle exec rake
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## Next Release
2
2
 
3
+ ## 6.1.5 (08/16/2021)
4
+
5
+ * Rename whitelist to safelist
6
+ * Fix onload called twice
7
+ * Support Rack::Files::Iterator responses
8
+ * Ensure HABTM associations are not incorrectly labeled n+1
9
+
3
10
  ## 6.1.4 (02/26/2021)
4
11
 
5
12
  * Added an option to stop adding HTTP headers to API requests
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Bullet
2
2
 
3
+ ![Main workflow](https://github.com/flyerhzm/bullet/actions/workflows/main.yml/badge.svg)
3
4
  [![Gem Version](https://badge.fury.io/rb/bullet.svg)](http://badge.fury.io/rb/bullet)
4
- [![Build Status](https://secure.travis-ci.org/flyerhzm/bullet.svg)](http://travis-ci.org/flyerhzm/bullet)
5
5
  [![AwesomeCode Status for flyerhzm/bullet](https://awesomecode.io/projects/6755235b-e2c1-459e-bf92-b8b13d0c0472/status)](https://awesomecode.io/repos/flyerhzm/bullet)
6
6
  [![Coderwall Endorse](http://api.coderwall.com/flyerhzm/endorsecount.png)](http://coderwall.com/flyerhzm)
7
7
 
@@ -67,6 +67,7 @@ config.after_initialize do
67
67
  Bullet.rails_logger = true
68
68
  Bullet.honeybadger = true
69
69
  Bullet.bugsnag = true
70
+ Bullet.appsignal = true
70
71
  Bullet.airbrake = true
71
72
  Bullet.rollbar = true
72
73
  Bullet.add_footer = true
@@ -90,6 +91,7 @@ The code above will enable all of the Bullet notification systems:
90
91
  * `Bullet.honeybadger`: add notifications to Honeybadger
91
92
  * `Bullet.bugsnag`: add notifications to bugsnag
92
93
  * `Bullet.airbrake`: add notifications to airbrake
94
+ * `Bullet.appsignal`: add notifications to AppSignal
93
95
  * `Bullet.rollbar`: add notifications to rollbar
94
96
  * `Bullet.sentry`: add notifications to sentry
95
97
  * `Bullet.add_footer`: adds the details in the bottom left corner of the page. Double click the footer or use close button to hide footer.
@@ -119,15 +121,15 @@ Bullet.unused_eager_loading_enable = false
119
121
  Bullet.counter_cache_enable = false
120
122
  ```
121
123
 
122
- ## Whitelist
124
+ ## Safe list
123
125
 
124
126
  Sometimes Bullet may notify you of query problems you don't care to fix, or
125
- which come from outside your code. You can whitelist these to ignore them:
127
+ which come from outside your code. You can add them to a safe list to ignore them:
126
128
 
127
129
  ```ruby
128
- Bullet.add_whitelist :type => :n_plus_one_query, :class_name => "Post", :association => :comments
129
- Bullet.add_whitelist :type => :unused_eager_loading, :class_name => "Post", :association => :comments
130
- Bullet.add_whitelist :type => :counter_cache, :class_name => "Country", :association => :cities
130
+ Bullet.add_safelist :type => :n_plus_one_query, :class_name => "Post", :association => :comments
131
+ Bullet.add_safelist :type => :unused_eager_loading, :class_name => "Post", :association => :comments
132
+ Bullet.add_safelist :type => :counter_cache, :class_name => "Country", :association => :cities
131
133
  ```
132
134
 
133
135
  If you want to skip bullet in some specific controller actions, you can
data/lib/bullet.rb CHANGED
@@ -20,9 +20,6 @@ module Bullet
20
20
  autoload :Registry, 'bullet/registry'
21
21
  autoload :NotificationCollector, 'bullet/notification_collector'
22
22
 
23
- BULLET_DEBUG = 'BULLET_DEBUG'
24
- TRUE = 'true'
25
-
26
23
  if defined?(Rails::Railtie)
27
24
  class BulletRailtie < Rails::Railtie
28
25
  initializer 'bullet.configure_rails_initialization' do |app|
@@ -38,10 +35,11 @@ module Bullet
38
35
  :stacktrace_includes,
39
36
  :stacktrace_excludes,
40
37
  :skip_html_injection
41
- attr_reader :whitelist
38
+ attr_reader :safelist
42
39
  attr_accessor :add_footer, :orm_patches_applied, :skip_http_headers
43
40
 
44
- available_notifiers = UniformNotifier::AVAILABLE_NOTIFIERS.select { |notifier| notifier != :raise }.map { |notifier| "#{notifier}=" }
41
+ available_notifiers =
42
+ UniformNotifier::AVAILABLE_NOTIFIERS.select { |notifier| notifier != :raise }.map { |notifier| "#{notifier}=" }
45
43
  available_notifiers_options = { to: UniformNotifier }
46
44
  delegate(*available_notifiers, **available_notifiers_options)
47
45
 
@@ -59,7 +57,7 @@ module Bullet
59
57
  @enable = @n_plus_one_query_enable = @unused_eager_loading_enable = @counter_cache_enable = enable
60
58
 
61
59
  if enable?
62
- reset_whitelist
60
+ reset_safelist
63
61
  unless orm_patches_applied
64
62
  self.orm_patches_applied = true
65
63
  Bullet::Mongoid.enable if mongoid?
@@ -72,8 +70,9 @@ module Bullet
72
70
  !!@enable
73
71
  end
74
72
 
73
+ # Rails.root might be nil if `railties` is a dependency on a project that does not use Rails
75
74
  def app_root
76
- @app_root ||= (defined?(::Rails.root) ? Rails.root.to_s : Dir.pwd).to_s
75
+ @app_root ||= (defined?(::Rails.root) && !::Rails.root.nil? ? Rails.root.to_s : Dir.pwd).to_s
77
76
  end
78
77
 
79
78
  def n_plus_one_query_enable?
@@ -96,29 +95,74 @@ module Bullet
96
95
  @stacktrace_excludes ||= []
97
96
  end
98
97
 
98
+ def add_safelist(options)
99
+ reset_safelist
100
+ @safelist[options[:type]][options[:class_name]] ||= []
101
+ @safelist[options[:type]][options[:class_name]] << options[:association].to_sym
102
+ end
103
+
104
+ def delete_safelist(options)
105
+ reset_safelist
106
+ @safelist[options[:type]][options[:class_name]] ||= []
107
+ @safelist[options[:type]][options[:class_name]].delete(options[:association].to_sym)
108
+ @safelist[options[:type]].delete_if { |_key, val| val.empty? }
109
+ end
110
+
111
+ def get_safelist_associations(type, class_name)
112
+ Array(@safelist[type][class_name])
113
+ end
114
+
115
+ def reset_safelist
116
+ @safelist ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }
117
+ end
118
+
119
+ def clear_safelist
120
+ @safelist = nil
121
+ end
122
+
99
123
  def add_whitelist(options)
100
- reset_whitelist
101
- @whitelist[options[:type]][options[:class_name]] ||= []
102
- @whitelist[options[:type]][options[:class_name]] << options[:association].to_sym
124
+ ActiveSupport::Deprecation.warn(<<~WARN.strip
125
+ add_whitelist is deprecated in favor of add_safelist. It will be removed from the next major release.
126
+ WARN
127
+ )
128
+
129
+ add_safelist(options)
103
130
  end
104
131
 
105
132
  def delete_whitelist(options)
106
- reset_whitelist
107
- @whitelist[options[:type]][options[:class_name]] ||= []
108
- @whitelist[options[:type]][options[:class_name]].delete(options[:association].to_sym)
109
- @whitelist[options[:type]].delete_if { |_key, val| val.empty? }
133
+ ActiveSupport::Deprecation.warn(<<~WARN.strip
134
+ delete_whitelist is deprecated in favor of delete_safelist. It will be removed from the next major release.
135
+ WARN
136
+ )
137
+
138
+ delete_safelist(options)
110
139
  end
111
140
 
112
141
  def get_whitelist_associations(type, class_name)
113
- Array(@whitelist[type][class_name])
142
+ ActiveSupport::Deprecation.warn(<<~WARN.strip
143
+ get_whitelist_associations is deprecated in favor of get_safelist_associations. It will be removed from the next major release.
144
+ WARN
145
+ )
146
+
147
+ get_safelist_associations(type, class_name)
114
148
  end
115
149
 
116
150
  def reset_whitelist
117
- @whitelist ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }
151
+ ActiveSupport::Deprecation.warn(<<~WARN.strip
152
+ reset_whitelist is deprecated in favor of reset_safelist. It will be removed from the next major release.
153
+ WARN
154
+ )
155
+
156
+ reset_safelist
118
157
  end
119
158
 
120
159
  def clear_whitelist
121
- @whitelist = nil
160
+ ActiveSupport::Deprecation.warn(<<~WARN.strip
161
+ clear_whitelist is deprecated in favor of clear_safelist. It will be removed from the next major release.
162
+ WARN
163
+ )
164
+
165
+ clear_safelist
122
166
  end
123
167
 
124
168
  def bullet_logger=(active)
@@ -132,7 +176,7 @@ module Bullet
132
176
  end
133
177
 
134
178
  def debug(title, message)
135
- puts "[Bullet][#{title}] #{message}" if ENV[BULLET_DEBUG] == TRUE
179
+ puts "[Bullet][#{title}] #{message}" if ENV['BULLET_DEBUG'] == 'true'
136
180
  end
137
181
 
138
182
  def start_request
@@ -30,6 +30,7 @@ module Bullet
30
30
 
31
31
  ::ActiveRecord::Relation.class_eval do
32
32
  alias_method :origin_to_a, :to_a
33
+
33
34
  # if select a collection of objects, then these objects have possible to cause N+1 query.
34
35
  # if select only one object, then the only one object has impossible to cause N+1 query.
35
36
  def to_a
@@ -52,6 +52,7 @@ module Bullet
52
52
 
53
53
  ::ActiveRecord::Relation.class_eval do
54
54
  alias_method :origin_to_a, :to_a
55
+
55
56
  # if select a collection of objects, then these objects have possible to cause N+1 query.
56
57
  # if select only one object, then the only one object has impossible to cause N+1 query.
57
58
  def to_a
@@ -20,6 +20,7 @@
20
20
  if (this.onload) {
21
21
  this._storedOnload = this.onload;
22
22
  }
23
+ this.onload = null
23
24
  this.addEventListener("load", bulletXHROnload);
24
25
  return Reflect.apply(oldSend, this, arguments);
25
26
  }
@@ -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)
@@ -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
@@ -22,7 +22,9 @@ module Bullet
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) if Bullet.add_footer && !Bullet.skip_http_headers
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
29
  elsif !Bullet.skip_http_headers
28
30
  set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
@@ -82,7 +84,7 @@ module Bullet
82
84
  def response_body(response)
83
85
  if response.respond_to?(:body)
84
86
  Array === response.body ? response.body.first : response.body
85
- else
87
+ elsif response.respond_to?(:first)
86
88
  response.first
87
89
  end
88
90
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require "bundler"
2
3
 
3
4
  module Bullet
4
5
  module StackTraceFilter
@@ -11,8 +12,9 @@ module Bullet
11
12
  select_caller_locations do |location|
12
13
  caller_path = location_as_path(location)
13
14
  caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&
14
- !caller_path.include?(bundler_path) ||
15
- 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
+ }
16
18
  end
17
19
  end
18
20
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '6.1.4'
4
+ VERSION = '6.1.5'
5
5
  end
data/perf/benchmark.rb CHANGED
@@ -30,7 +30,10 @@ end
30
30
 
31
31
  # create database bullet_benchmark;
32
32
  ActiveRecord::Base.establish_connection(
33
- adapter: 'mysql2', database: 'bullet_benchmark', server: '/tmp/mysql.socket', username: 'root'
33
+ adapter: 'mysql2',
34
+ database: 'bullet_benchmark',
35
+ server: '/tmp/mysql.socket',
36
+ username: 'root'
34
37
  )
35
38
 
36
39
  ActiveRecord::Base.connection.tables.each { |table| ActiveRecord::Base.connection.drop_table(table) }
@@ -19,7 +19,9 @@ module Bullet
19
19
  it 'should get call associations if object and association are both in eager_loadings and call_object_associations' do
20
20
  UnusedEagerLoading.add_eager_loadings([@post], :association)
21
21
  UnusedEagerLoading.add_call_object_associations(@post, :association)
22
- expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq([:association])
22
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq(
23
+ [:association]
24
+ )
23
25
  end
24
26
 
25
27
  it 'should not get call associations if not exist in call_object_associations' do
@@ -30,7 +32,9 @@ module Bullet
30
32
 
31
33
  context '.diff_object_associations' do
32
34
  it 'should return associations not exist in call_association' do
33
- expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq([:association])
35
+ expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq(
36
+ [:association]
37
+ )
34
38
  end
35
39
 
36
40
  it 'should return empty if associations exist in call_association' do
@@ -10,7 +10,7 @@ describe Object do
10
10
  end
11
11
 
12
12
  if mongoid?
13
- it 'should return class with namesapce and id composition' do
13
+ it 'should return class with namespace and id composition' do
14
14
  post = Mongoid::Post.first
15
15
  expect(post.bullet_key).to eq("Mongoid::Post:#{post.id}")
16
16
  end
@@ -105,9 +105,10 @@ module Bullet
105
105
 
106
106
  it 'should change response body for html safe string if add_footer is true' do
107
107
  expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
108
- app.response = Support::ResponseDouble.new.tap do |response|
109
- response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
110
- end
108
+ app.response =
109
+ Support::ResponseDouble.new.tap do |response|
110
+ response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
111
+ end
111
112
  _, headers, response = middleware.call('Content-Type' => 'text/html')
112
113
 
113
114
  expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
@@ -117,7 +118,7 @@ module Bullet
117
118
  it 'should add the footer-text header for non-html requests when add_footer is true' do
118
119
  allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
119
120
  allow(Bullet).to receive(:footer_info).and_return(['footer text'])
120
- app.headers = {'Content-Type' => 'application/json'}
121
+ app.headers = { 'Content-Type' => 'application/json' }
121
122
  _, headers, _response = middleware.call({})
122
123
  expect(headers).to include('X-bullet-footer-text' => '["footer text"]')
123
124
  end
@@ -131,9 +132,10 @@ module Bullet
131
132
 
132
133
  it 'should change response body for html safe string if console_enabled is true' do
133
134
  expect(Bullet).to receive(:console_enabled?).and_return(true)
134
- app.response = Support::ResponseDouble.new.tap do |response|
135
- response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
136
- end
135
+ app.response =
136
+ Support::ResponseDouble.new.tap do |response|
137
+ response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
138
+ end
137
139
  _, headers, response = middleware.call('Content-Type' => 'text/html')
138
140
  expect(headers['Content-Length']).to eq('56')
139
141
  expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
@@ -142,7 +144,7 @@ module Bullet
142
144
  it 'should add headers for non-html requests when console_enabled is true' do
143
145
  allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
144
146
  allow(Bullet).to receive(:text_notifications).and_return(['text notifications'])
145
- app.headers = {'Content-Type' => 'application/json'}
147
+ app.headers = { 'Content-Type' => 'application/json' }
146
148
  _, headers, _response = middleware.call({})
147
149
  expect(headers).to include('X-bullet-console-text' => '["text notifications"]')
148
150
  end
@@ -155,13 +157,13 @@ module Bullet
155
157
  end
156
158
 
157
159
  it "shouldn't add headers unnecessarily" do
158
- app.headers = {'Content-Type' => 'application/json'}
160
+ app.headers = { 'Content-Type' => 'application/json' }
159
161
  _, headers, _response = middleware.call({})
160
162
  expect(headers).not_to include('X-bullet-footer-text')
161
163
  expect(headers).not_to include('X-bullet-console-text')
162
164
  end
163
165
 
164
- context "when skip_http_headers is enabled" do
166
+ context 'when skip_http_headers is enabled' do
165
167
  before do
166
168
  allow(Bullet).to receive(:skip_http_headers).and_return(true)
167
169
  end
@@ -183,14 +185,14 @@ module Bullet
183
185
 
184
186
  it 'should not add the footer-text header for non-html requests when add_footer is true' do
185
187
  allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
186
- app.headers = {'Content-Type' => 'application/json'}
188
+ app.headers = { 'Content-Type' => 'application/json' }
187
189
  _, headers, _response = middleware.call({})
188
190
  expect(headers).not_to include('X-bullet-footer-text')
189
191
  end
190
192
 
191
193
  it 'should not add headers for non-html requests when console_enabled is true' do
192
194
  allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
193
- app.headers = {'Content-Type' => 'application/json'}
195
+ app.headers = { 'Content-Type' => 'application/json' }
194
196
  _, headers, _response = middleware.call({})
195
197
  expect(headers).not_to include('X-bullet-console-text')
196
198
  end
@@ -259,6 +261,20 @@ module Bullet
259
261
  expect(middleware.response_body(response)).to eq body_string
260
262
  end
261
263
  end
264
+
265
+ begin
266
+ require 'rack/files'
267
+
268
+ context 'when `response` is a Rack::Files::Iterator' do
269
+ let(:response) { instance_double(::Rack::Files::Iterator) }
270
+ before { allow(response).to receive(:is_a?).with(::Rack::Files::Iterator) { true } }
271
+
272
+ it 'should return nil' do
273
+ expect(middleware.response_body(response)).to be_nil
274
+ end
275
+ end
276
+ rescue LoadError
277
+ end
262
278
  end
263
279
  end
264
280
  end
data/spec/bullet_spec.rb CHANGED
@@ -74,31 +74,60 @@ describe Bullet, focused: true do
74
74
  end
75
75
  end
76
76
 
77
+ describe '#add_safelist' do
78
+ context "for 'special' class names" do
79
+ it 'is added to the safelist successfully' do
80
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
81
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
82
+ end
83
+ end
84
+ end
85
+
77
86
  describe '#add_whitelist' do
78
87
  context "for 'special' class names" do
79
- it 'is added to the whitelist successfully' do
88
+ it 'is added to the safelist successfully' do
80
89
  Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
81
- expect(Bullet.get_whitelist_associations(:n_plus_one_query, 'Klass')).to include :department
90
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
91
+ end
92
+ end
93
+ end
94
+
95
+ describe '#delete_safelist' do
96
+ context "for 'special' class names" do
97
+ it 'is deleted from the safelist successfully' do
98
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
99
+ Bullet.delete_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
100
+ expect(Bullet.safelist[:n_plus_one_query]).to eq({})
101
+ end
102
+ end
103
+
104
+ context 'when exists multiple definitions' do
105
+ it 'is deleted from the safelist successfully' do
106
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
107
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
108
+ Bullet.delete_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
109
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
110
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to_not include :team
82
111
  end
83
112
  end
84
113
  end
85
114
 
86
115
  describe '#delete_whitelist' do
87
116
  context "for 'special' class names" do
88
- it 'is deleted from the whitelist successfully' do
89
- Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
117
+ it 'is deleted from the safelist successfully' do
118
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
90
119
  Bullet.delete_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
91
- expect(Bullet.whitelist[:n_plus_one_query]).to eq({})
120
+ expect(Bullet.safelist[:n_plus_one_query]).to eq({})
92
121
  end
93
122
  end
94
123
 
95
124
  context 'when exists multiple definitions' do
96
- it 'is deleted from the whitelist successfully' do
97
- Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
98
- Bullet.add_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
125
+ it 'is deleted from the safelist successfully' do
126
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
127
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
99
128
  Bullet.delete_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
100
- expect(Bullet.get_whitelist_associations(:n_plus_one_query, 'Klass')).to include :department
101
- expect(Bullet.get_whitelist_associations(:n_plus_one_query, 'Klass')).to_not include :team
129
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
130
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to_not include :team
102
131
  end
103
132
  end
104
133
  end
@@ -129,7 +129,7 @@ if active_record?
129
129
  expect(Bullet::Detector::Association).to be_completely_preloading_associations
130
130
  end
131
131
 
132
- it 'should detect unused preload with post => commnets, no category => posts' do
132
+ it 'should detect unused preload with post => comments, no category => posts' do
133
133
  Category.includes(posts: :comments).each { |category| category.posts.map(&:name) }
134
134
  Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
135
135
  expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Post, :comments)
@@ -202,7 +202,7 @@ if active_record?
202
202
  expect(Bullet::Detector::Association).to be_completely_preloading_associations
203
203
  end
204
204
 
205
- it 'should detect preload with post => commnets' do
205
+ it 'should detect preload with post => comments' do
206
206
  Post.first.comments.map(&:name)
207
207
  Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
208
208
  expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
@@ -401,6 +401,15 @@ if active_record?
401
401
  end
402
402
 
403
403
  describe Bullet::Detector::Association, 'has_and_belongs_to_many' do
404
+ context 'posts <=> deals' do
405
+ it 'should detect preload associations with join tables that have identifier' do
406
+ Post.includes(:deals).each { |post| post.deals.map(&:name) }
407
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
408
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
409
+
410
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
411
+ end
412
+ end
404
413
  context 'students <=> teachers' do
405
414
  it 'should detect non preload associations' do
406
415
  Student.all.each { |student| student.teachers.map(&:name) }
@@ -729,9 +738,9 @@ if active_record?
729
738
  end
730
739
  end
731
740
 
732
- context 'whitelist n plus one query' do
733
- before { Bullet.add_whitelist type: :n_plus_one_query, class_name: 'Post', association: :comments }
734
- after { Bullet.clear_whitelist }
741
+ context 'add n plus one query to safelist' do
742
+ before { Bullet.add_safelist type: :n_plus_one_query, class_name: 'Post', association: :comments }
743
+ after { Bullet.clear_safelist }
735
744
 
736
745
  it 'should not detect n plus one query' do
737
746
  Post.all.each { |post| post.comments.map(&:name) }
@@ -750,9 +759,9 @@ if active_record?
750
759
  end
751
760
  end
752
761
 
753
- context 'whitelist unused eager loading' do
754
- before { Bullet.add_whitelist type: :unused_eager_loading, class_name: 'Post', association: :comments }
755
- after { Bullet.clear_whitelist }
762
+ context 'add unused eager loading to safelist' do
763
+ before { Bullet.add_safelist type: :unused_eager_loading, class_name: 'Post', association: :comments }
764
+ after { Bullet.clear_safelist }
756
765
 
757
766
  it 'should not detect unused eager loading' do
758
767
  Post.includes(:comments).map(&:name)
@@ -55,9 +55,9 @@ if !mongoid? && active_record?
55
55
  end
56
56
  end
57
57
 
58
- context 'whitelist' do
59
- before { Bullet.add_whitelist type: :counter_cache, class_name: 'Country', association: :cities }
60
- after { Bullet.clear_whitelist }
58
+ context 'safelist' do
59
+ before { Bullet.add_safelist type: :counter_cache, class_name: 'Country', association: :cities }
60
+ after { Bullet.clear_safelist }
61
61
 
62
62
  it 'should not detect counter cache' do
63
63
  Country.all.each { |country| country.cities.size }
@@ -118,7 +118,7 @@ if mongoid?
118
118
  expect(Bullet::Detector::Association).to be_completely_preloading_associations
119
119
  end
120
120
 
121
- it 'should detect preload with post => commnets' do
121
+ it 'should detect preload with post => comments' do
122
122
  Mongoid::Post.first.comments.map(&:name)
123
123
  Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
124
124
  expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Deal < ActiveRecord::Base
4
+ has_and_belongs_to_many :posts
5
+ end
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Folder < Document; end
3
+ class Folder < Document
4
+ end
data/spec/models/group.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Group < ActiveRecord::Base; end
3
+ class Group < ActiveRecord::Base
4
+ end
data/spec/models/page.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Page < Document; end
3
+ class Page < Document
4
+ end
data/spec/models/post.rb CHANGED
@@ -4,6 +4,7 @@ class Post < ActiveRecord::Base
4
4
  belongs_to :category, inverse_of: :posts
5
5
  belongs_to :writer
6
6
  has_many :comments, inverse_of: :post
7
+ has_and_belongs_to_many :deals
7
8
 
8
9
  validates :category, presence: true
9
10
 
@@ -21,6 +22,7 @@ class Post < ActiveRecord::Base
21
22
  next unless trigger_after_save
22
23
 
23
24
  temp_comment = Comment.new(post: self)
25
+
24
26
  # this triggers self to be "possible", even though it's
25
27
  # not saved yet
26
28
  temp_comment.post
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Writer < BaseUser; end
3
+ class Writer < BaseUser
4
+ end
data/spec/spec_helper.rb CHANGED
@@ -4,12 +4,10 @@ require 'rspec'
4
4
  begin
5
5
  require 'active_record'
6
6
  rescue LoadError
7
-
8
7
  end
9
8
  begin
10
9
  require 'mongoid'
11
10
  rescue LoadError
12
-
13
11
  end
14
12
 
15
13
  module Rails
@@ -45,6 +45,7 @@ module Support
45
45
  Mongoid.configure do |config|
46
46
  config.load_configuration(clients: { default: { database: 'bullet', hosts: %w[localhost:27017] } })
47
47
  end
48
+
48
49
  # Increase the level from DEBUG in order to avoid excessive logging to the screen
49
50
  Mongo::Logger.logger.level = Logger::WARN
50
51
  end
@@ -21,6 +21,13 @@ module Support
21
21
  post2 = category2.posts.create(name: 'second', writer: writer2)
22
22
  post3 = category2.posts.create(name: 'third', writer: writer2)
23
23
 
24
+ deal1 = Deal.new(name: 'Deal 1')
25
+ deal1.posts << post1
26
+ deal1.posts << post2
27
+ deal2 = Deal.new(name: 'Deal 2')
28
+ post1.deals << deal1
29
+ post1.deals << deal2
30
+
24
31
  comment1 = post1.comments.create(name: 'first', author: writer1)
25
32
  comment2 = post1.comments.create(name: 'first2', author: writer1)
26
33
  comment3 = post1.comments.create(name: 'first3', author: writer1)
@@ -156,6 +163,11 @@ module Support
156
163
  t.column :hotel_id, :integer
157
164
  end
158
165
 
166
+ create_table :deals_posts do |t|
167
+ t.column :deal_id, :integer
168
+ t.column :post_id, :integer
169
+ end
170
+
159
171
  create_table :documents do |t|
160
172
  t.string :name
161
173
  t.string :type
data/test.sh CHANGED
@@ -1,5 +1,6 @@
1
1
  #bundle update rails && bundle exec rspec spec
2
2
  #BUNDLE_GEMFILE=Gemfile.mongoid bundle update mongoid && BUNDLE_GEMFILE=Gemfile.mongoid bundle exec rspec spec
3
+ BUNDLE_GEMFILE=Gemfile.rails-6.1 bundle && BUNDLE_GEMFILE=Gemfile.rails-6.1 bundle exec rspec spec
3
4
  BUNDLE_GEMFILE=Gemfile.rails-6.0 bundle && BUNDLE_GEMFILE=Gemfile.rails-6.0 bundle exec rspec spec
4
5
  BUNDLE_GEMFILE=Gemfile.rails-5.2 bundle && BUNDLE_GEMFILE=Gemfile.rails-5.2 bundle exec rspec spec
5
6
  BUNDLE_GEMFILE=Gemfile.rails-5.1 bundle && BUNDLE_GEMFILE=Gemfile.rails-5.1 bundle exec rspec spec
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.4
4
+ version: 6.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-26 00:00:00.000000000 Z
11
+ date: 2021-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -45,9 +45,9 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - ".github/workflows/main.yml"
48
49
  - ".gitignore"
49
50
  - ".rspec"
50
- - ".travis.yml"
51
51
  - CHANGELOG.md
52
52
  - Gemfile
53
53
  - Gemfile.mongoid
@@ -138,6 +138,7 @@ files:
138
138
  - spec/models/comment.rb
139
139
  - spec/models/company.rb
140
140
  - spec/models/country.rb
141
+ - spec/models/deal.rb
141
142
  - spec/models/document.rb
142
143
  - spec/models/entry.rb
143
144
  - spec/models/firm.rb
@@ -191,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
191
192
  - !ruby/object:Gem::Version
192
193
  version: 1.3.6
193
194
  requirements: []
194
- rubygems_version: 3.1.4
195
+ rubygems_version: 3.2.22
195
196
  signing_key:
196
197
  specification_version: 4
197
198
  summary: help to kill N+1 queries and unused eager loading.
@@ -226,6 +227,7 @@ test_files:
226
227
  - spec/models/comment.rb
227
228
  - spec/models/company.rb
228
229
  - spec/models/country.rb
230
+ - spec/models/deal.rb
229
231
  - spec/models/document.rb
230
232
  - spec/models/entry.rb
231
233
  - spec/models/firm.rb
data/.travis.yml DELETED
@@ -1,33 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.3.0
4
- - 2.6.0
5
- gemfile:
6
- - Gemfile.rails-6.0
7
- - Gemfile.rails-5.2
8
- - Gemfile.rails-5.1
9
- - Gemfile.rails-5.0
10
- - Gemfile.rails-4.2
11
- - Gemfile.rails-4.1
12
- - Gemfile.rails-4.0
13
- matrix:
14
- exclude:
15
- - rvm: 2.3.0
16
- gemfile: Gemfile.rails-6.0
17
- - rvm: 2.6.0
18
- gemfile: Gemfile.rails-5.2
19
- - rvm: 2.6.0
20
- gemfile: Gemfile.rails-5.1
21
- - rvm: 2.6.0
22
- gemfile: Gemfile.rails-5.0
23
- - rvm: 2.6.0
24
- gemfile: Gemfile.rails-4.2
25
- - rvm: 2.6.0
26
- gemfile: Gemfile.rails-4.1
27
- - rvm: 2.6.0
28
- gemfile: Gemfile.rails-4.0
29
- env:
30
- - DB=sqlite
31
- before_install:
32
- - "find /home/travis/.rvm/rubies -wholename '*default/bundler-*.gemspec' -delete"
33
- - gem install bundler -v '< 2'