bullet 6.1.4 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +66 -0
- data/CHANGELOG.md +7 -0
- data/README.md +8 -6
- data/lib/bullet.rb +62 -18
- data/lib/bullet/active_record41.rb +1 -0
- data/lib/bullet/active_record42.rb +1 -0
- data/lib/bullet/bullet_xhr.js +1 -0
- data/lib/bullet/detector/base.rb +2 -1
- data/lib/bullet/detector/counter_cache.rb +1 -1
- data/lib/bullet/detector/n_plus_one_query.rb +3 -3
- data/lib/bullet/detector/unused_eager_loading.rb +1 -1
- data/lib/bullet/notification.rb +2 -1
- data/lib/bullet/rack.rb +4 -2
- data/lib/bullet/stack_trace_filter.rb +4 -2
- data/lib/bullet/version.rb +1 -1
- data/perf/benchmark.rb +4 -1
- data/spec/bullet/detector/unused_eager_loading_spec.rb +6 -2
- data/spec/bullet/ext/object_spec.rb +1 -1
- data/spec/bullet/rack_spec.rb +28 -12
- data/spec/bullet_spec.rb +39 -10
- data/spec/integration/active_record/association_spec.rb +17 -8
- data/spec/integration/counter_cache_spec.rb +3 -3
- data/spec/integration/mongoid/association_spec.rb +1 -1
- data/spec/models/deal.rb +5 -0
- data/spec/models/folder.rb +2 -1
- data/spec/models/group.rb +2 -1
- data/spec/models/page.rb +2 -1
- data/spec/models/post.rb +2 -0
- data/spec/models/writer.rb +2 -1
- data/spec/spec_helper.rb +0 -2
- data/spec/support/mongo_seed.rb +1 -0
- data/spec/support/sqlite_seed.rb +12 -0
- data/test.sh +1 -0
- metadata +6 -4
- data/.travis.yml +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b21c2c4ca3caf6c41961a7182a5c8d37f6b89aa78b8dfb7badfb940f11a23191
|
4
|
+
data.tar.gz: 0f432034f9b4cb2fe6c6572481c797aefcfa69373839fbd0985a7efd2a0bbea9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
+

|
3
4
|
[](http://badge.fury.io/rb/bullet)
|
4
|
-
[](http://travis-ci.org/flyerhzm/bullet)
|
5
5
|
[](https://awesomecode.io/repos/flyerhzm/bullet)
|
6
6
|
[](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
|
-
##
|
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
|
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.
|
129
|
-
Bullet.
|
130
|
-
Bullet.
|
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 :
|
38
|
+
attr_reader :safelist
|
42
39
|
attr_accessor :add_footer, :orm_patches_applied, :skip_http_headers
|
43
40
|
|
44
|
-
available_notifiers =
|
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
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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] ==
|
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
|
data/lib/bullet/bullet_xhr.js
CHANGED
data/lib/bullet/detector/base.rb
CHANGED
@@ -54,7 +54,7 @@ module Bullet
|
|
54
54
|
private
|
55
55
|
|
56
56
|
def create_notification(klazz, associations)
|
57
|
-
notify_associations = Array(associations) - Bullet.
|
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.
|
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.
|
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)
|
data/lib/bullet/notification.rb
CHANGED
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
|
-
|
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
|
-
|
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
|
-
|
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
|
|
data/lib/bullet/version.rb
CHANGED
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',
|
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(
|
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(
|
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
|
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
|
data/spec/bullet/rack_spec.rb
CHANGED
@@ -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 =
|
109
|
-
|
110
|
-
|
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 =
|
135
|
-
|
136
|
-
|
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
|
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
|
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.
|
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
|
89
|
-
Bullet.
|
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.
|
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
|
97
|
-
Bullet.
|
98
|
-
Bullet.
|
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.
|
101
|
-
expect(Bullet.
|
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 =>
|
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 =>
|
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 '
|
733
|
-
before { Bullet.
|
734
|
-
after { Bullet.
|
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 '
|
754
|
-
before { Bullet.
|
755
|
-
after { Bullet.
|
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 '
|
59
|
-
before { Bullet.
|
60
|
-
after { Bullet.
|
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 =>
|
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
|
data/spec/models/deal.rb
ADDED
data/spec/models/folder.rb
CHANGED
data/spec/models/group.rb
CHANGED
data/spec/models/page.rb
CHANGED
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
|
data/spec/models/writer.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
data/spec/support/mongo_seed.rb
CHANGED
@@ -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
|
data/spec/support/sqlite_seed.rb
CHANGED
@@ -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
|
+
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-
|
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.
|
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'
|