bullet 5.7.5 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.travis.yml +2 -0
- data/CHANGELOG.md +17 -14
- data/Gemfile.mongoid-7.0 +15 -0
- data/Gemfile.rails-4.0 +1 -1
- data/Gemfile.rails-4.1 +1 -1
- data/Gemfile.rails-4.2 +1 -1
- data/Gemfile.rails-5.0 +1 -1
- data/Gemfile.rails-5.1 +1 -1
- data/Gemfile.rails-5.2 +2 -2
- data/Gemfile.rails-6.0 +15 -0
- data/README.md +8 -5
- data/Rakefile +1 -1
- data/bullet.gemspec +8 -3
- data/lib/bullet/active_record4.rb +5 -13
- data/lib/bullet/active_record41.rb +5 -13
- data/lib/bullet/active_record42.rb +6 -13
- data/lib/bullet/active_record5.rb +15 -13
- data/lib/bullet/active_record52.rb +12 -13
- data/lib/bullet/active_record60.rb +245 -0
- data/lib/bullet/bullet_xhr.js +58 -0
- data/lib/bullet/dependency.rb +17 -5
- data/lib/bullet/detector/association.rb +4 -4
- data/lib/bullet/detector/counter_cache.rb +4 -3
- data/lib/bullet/detector/n_plus_one_query.rb +13 -13
- data/lib/bullet/detector/unused_eager_loading.rb +2 -1
- data/lib/bullet/ext/object.rb +5 -3
- data/lib/bullet/ext/string.rb +1 -1
- data/lib/bullet/mongoid4x.rb +1 -0
- data/lib/bullet/mongoid5x.rb +1 -0
- data/lib/bullet/mongoid6x.rb +5 -4
- data/lib/bullet/mongoid7x.rb +61 -0
- data/lib/bullet/notification/base.rb +1 -1
- data/lib/bullet/rack.rb +45 -27
- data/lib/bullet/stack_trace_filter.rb +43 -25
- data/lib/bullet/version.rb +1 -1
- data/lib/bullet.rb +21 -5
- data/lib/generators/bullet/install_generator.rb +3 -3
- data/spec/bullet/detector/n_plus_one_query_spec.rb +23 -0
- data/spec/bullet/ext/object_spec.rb +9 -4
- data/spec/bullet/rack_spec.rb +12 -3
- data/spec/integration/active_record/association_spec.rb +52 -3
- data/spec/integration/counter_cache_spec.rb +1 -1
- data/spec/models/client.rb +2 -0
- data/spec/models/firm.rb +1 -0
- data/spec/models/group.rb +4 -0
- data/spec/models/post.rb +15 -0
- data/spec/support/sqlite_seed.rb +9 -2
- data/test.sh +2 -0
- data/update.sh +1 -0
- metadata +16 -8
data/lib/bullet/rack.rb
CHANGED
|
@@ -10,17 +10,23 @@ module Bullet
|
|
|
10
10
|
|
|
11
11
|
def call(env)
|
|
12
12
|
return @app.call(env) unless Bullet.enable?
|
|
13
|
+
|
|
13
14
|
Bullet.start_request
|
|
14
15
|
status, headers, response = @app.call(env)
|
|
15
16
|
|
|
16
17
|
response_body = nil
|
|
17
18
|
if Bullet.notification?
|
|
18
|
-
if !file?(headers) && !sse?(headers) && !empty?(response) &&
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
if !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
|
|
20
|
+
if html_request?(headers, response)
|
|
21
|
+
response_body = response_body(response)
|
|
22
|
+
response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
|
|
23
|
+
response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
|
|
24
|
+
response_body = append_to_html_body(response_body, xhr_script)
|
|
25
|
+
headers['Content-Length'] = response_body.bytesize.to_s
|
|
26
|
+
else
|
|
27
|
+
set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
|
|
28
|
+
set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
|
|
29
|
+
end
|
|
24
30
|
end
|
|
25
31
|
Bullet.perform_out_of_channel_notifications(env)
|
|
26
32
|
end
|
|
@@ -31,16 +37,10 @@ module Bullet
|
|
|
31
37
|
|
|
32
38
|
# fix issue if response's body is a Proc
|
|
33
39
|
def empty?(response)
|
|
34
|
-
# response may be ["Not Found"], ["Move Permanently"], etc
|
|
35
|
-
if
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
!response_body(response).respond_to?(:empty?) ||
|
|
39
|
-
response_body(response).empty?
|
|
40
|
-
else
|
|
41
|
-
body = response_body(response)
|
|
42
|
-
body.nil? || body.empty?
|
|
43
|
-
end
|
|
40
|
+
# response may be ["Not Found"], ["Move Permanently"], etc, but
|
|
41
|
+
# those should not happen if the status is 200
|
|
42
|
+
body = response_body(response)
|
|
43
|
+
body.nil? || body.empty?
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
def append_to_html_body(response_body, content)
|
|
@@ -54,7 +54,15 @@ module Bullet
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def footer_note
|
|
57
|
-
"<div #{footer_div_attributes}>" +
|
|
57
|
+
"<div #{footer_div_attributes}>" + footer_header + '<br>' + Bullet.footer_info.uniq.join('<br>') + '</div>'
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def set_header(headers, header_name, header_array)
|
|
61
|
+
# Many proxy applications such as Nginx and AWS ELB limit
|
|
62
|
+
# the size a header to 8KB, so truncate the list of reports to
|
|
63
|
+
# be under that limit
|
|
64
|
+
header_array.pop while header_array.to_json.length > 8 * 1024
|
|
65
|
+
headers[header_name] = header_array.to_json
|
|
58
66
|
end
|
|
59
67
|
|
|
60
68
|
def file?(headers)
|
|
@@ -66,7 +74,7 @@ module Bullet
|
|
|
66
74
|
end
|
|
67
75
|
|
|
68
76
|
def html_request?(headers, response)
|
|
69
|
-
headers['Content-Type']
|
|
77
|
+
headers['Content-Type']&.include?('text/html') && response_body(response).include?('<html')
|
|
70
78
|
end
|
|
71
79
|
|
|
72
80
|
def response_body(response)
|
|
@@ -80,17 +88,27 @@ module Bullet
|
|
|
80
88
|
private
|
|
81
89
|
|
|
82
90
|
def footer_div_attributes
|
|
83
|
-
|
|
84
|
-
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);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
EOF
|
|
91
|
+
<<~EOF
|
|
92
|
+
id="bullet-footer" data-is-bullet-footer ondblclick="this.parentNode.removeChild(this);" style="position: fixed; bottom: 0pt; left: 0pt; cursor: pointer; border-style: solid; border-color: rgb(153, 153, 153);
|
|
93
|
+
-moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
|
|
94
|
+
-moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
|
|
95
|
+
padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
|
|
96
|
+
color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
|
|
97
|
+
EOF
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def footer_header
|
|
101
|
+
cancel_button = "<span onclick='this.parentNode.remove()' style='position:absolute; right: 10px; top: 0px; font-weight: bold; color: #333;'>×</span>"
|
|
102
|
+
if Bullet.console_enabled?
|
|
103
|
+
"<span>See 'Uniform Notifier' in JS Console for Stacktrace</span>#{cancel_button}"
|
|
104
|
+
else
|
|
105
|
+
cancel_button
|
|
106
|
+
end
|
|
90
107
|
end
|
|
91
108
|
|
|
92
|
-
|
|
93
|
-
|
|
109
|
+
# Make footer work for XHR requests by appending data to the footer
|
|
110
|
+
def xhr_script
|
|
111
|
+
"<script type='text/javascript'>#{File.read("#{__dir__}/bullet_xhr.js")}</script>"
|
|
94
112
|
end
|
|
95
113
|
end
|
|
96
114
|
end
|
|
@@ -2,52 +2,70 @@
|
|
|
2
2
|
|
|
3
3
|
module Bullet
|
|
4
4
|
module StackTraceFilter
|
|
5
|
-
VENDOR_PATH = '/vendor'
|
|
5
|
+
VENDOR_PATH = '/vendor'
|
|
6
6
|
|
|
7
7
|
def caller_in_project
|
|
8
|
-
|
|
9
|
-
vendor_root = app_root + VENDOR_PATH
|
|
8
|
+
vendor_root = Bullet.app_root + VENDOR_PATH
|
|
10
9
|
bundler_path = Bundler.bundle_path.to_s
|
|
11
|
-
select_caller_locations do |
|
|
12
|
-
caller_path
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
when String
|
|
16
|
-
caller_path.include?(include_pattern)
|
|
17
|
-
when Regexp
|
|
18
|
-
caller_path =~ include_pattern
|
|
19
|
-
end
|
|
20
|
-
end
|
|
10
|
+
select_caller_locations do |location|
|
|
11
|
+
caller_path = location_as_path(location)
|
|
12
|
+
caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) && !caller_path.include?(bundler_path) ||
|
|
13
|
+
Bullet.stacktrace_includes.any? { |include_pattern| pattern_matches?(location, include_pattern) }
|
|
21
14
|
end
|
|
22
15
|
end
|
|
23
16
|
|
|
24
17
|
def excluded_stacktrace_path?
|
|
25
18
|
Bullet.stacktrace_excludes.any? do |exclude_pattern|
|
|
26
|
-
caller_in_project.any?
|
|
27
|
-
caller_path = location.absolute_path.to_s
|
|
28
|
-
case exclude_pattern
|
|
29
|
-
when String
|
|
30
|
-
caller_path.include?(exclude_pattern)
|
|
31
|
-
when Regexp
|
|
32
|
-
caller_path =~ exclude_pattern
|
|
33
|
-
end
|
|
34
|
-
end
|
|
19
|
+
caller_in_project.any? { |location| pattern_matches?(location, exclude_pattern) }
|
|
35
20
|
end
|
|
36
21
|
end
|
|
37
22
|
|
|
38
23
|
private
|
|
39
24
|
|
|
25
|
+
def pattern_matches?(location, pattern)
|
|
26
|
+
path = location_as_path(location)
|
|
27
|
+
case pattern
|
|
28
|
+
when Array
|
|
29
|
+
pattern_path = pattern.first
|
|
30
|
+
filter = pattern.last
|
|
31
|
+
return false unless pattern_matches?(location, pattern_path)
|
|
32
|
+
|
|
33
|
+
case filter
|
|
34
|
+
when Range
|
|
35
|
+
filter.include?(location.lineno)
|
|
36
|
+
when Integer
|
|
37
|
+
filter == location.lineno
|
|
38
|
+
when String
|
|
39
|
+
filter == location.base_label
|
|
40
|
+
end
|
|
41
|
+
when String
|
|
42
|
+
path.include?(pattern)
|
|
43
|
+
when Regexp
|
|
44
|
+
path =~ pattern
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def location_as_path(location)
|
|
49
|
+
ruby_19? ? location : location.absolute_path.to_s
|
|
50
|
+
end
|
|
51
|
+
|
|
40
52
|
def select_caller_locations
|
|
41
|
-
if
|
|
53
|
+
if ruby_19?
|
|
42
54
|
caller.select do |caller_path|
|
|
43
55
|
yield caller_path
|
|
44
56
|
end
|
|
45
57
|
else
|
|
46
58
|
caller_locations.select do |location|
|
|
47
|
-
|
|
48
|
-
yield caller_path
|
|
59
|
+
yield location
|
|
49
60
|
end
|
|
50
61
|
end
|
|
51
62
|
end
|
|
63
|
+
|
|
64
|
+
def ruby_19?
|
|
65
|
+
if @ruby_19.nil?
|
|
66
|
+
@ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
|
|
67
|
+
end
|
|
68
|
+
@ruby_19
|
|
69
|
+
end
|
|
52
70
|
end
|
|
53
71
|
end
|
data/lib/bullet/version.rb
CHANGED
data/lib/bullet.rb
CHANGED
|
@@ -19,8 +19,8 @@ module Bullet
|
|
|
19
19
|
autoload :Registry, 'bullet/registry'
|
|
20
20
|
autoload :NotificationCollector, 'bullet/notification_collector'
|
|
21
21
|
|
|
22
|
-
BULLET_DEBUG = 'BULLET_DEBUG'
|
|
23
|
-
TRUE = 'true'
|
|
22
|
+
BULLET_DEBUG = 'BULLET_DEBUG'
|
|
23
|
+
TRUE = 'true'
|
|
24
24
|
|
|
25
25
|
if defined? Rails::Railtie
|
|
26
26
|
class BulletRailtie < Rails::Railtie
|
|
@@ -63,6 +63,10 @@ module Bullet
|
|
|
63
63
|
!!@enable
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
+
def app_root
|
|
67
|
+
(defined?(::Rails.root) ? Rails.root.to_s : Dir.pwd).to_s
|
|
68
|
+
end
|
|
69
|
+
|
|
66
70
|
def n_plus_one_query_enable?
|
|
67
71
|
enable? && !!@n_plus_one_query_enable
|
|
68
72
|
end
|
|
@@ -111,9 +115,8 @@ module Bullet
|
|
|
111
115
|
def bullet_logger=(active)
|
|
112
116
|
if active
|
|
113
117
|
require 'fileutils'
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
bullet_log_file = File.open("#{root_path}/log/bullet.log", 'a+')
|
|
118
|
+
FileUtils.mkdir_p(app_root + '/log')
|
|
119
|
+
bullet_log_file = File.open("#{app_root}/log/bullet.log", 'a+')
|
|
117
120
|
bullet_log_file.sync = true
|
|
118
121
|
UniformNotifier.customized_logger = bullet_log_file
|
|
119
122
|
end
|
|
@@ -163,6 +166,7 @@ module Bullet
|
|
|
163
166
|
|
|
164
167
|
def notification?
|
|
165
168
|
return unless start?
|
|
169
|
+
|
|
166
170
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
167
171
|
notification_collector.notifications_present?
|
|
168
172
|
end
|
|
@@ -191,6 +195,14 @@ module Bullet
|
|
|
191
195
|
info
|
|
192
196
|
end
|
|
193
197
|
|
|
198
|
+
def text_notifications
|
|
199
|
+
info = []
|
|
200
|
+
notification_collector.collection.each do |notification|
|
|
201
|
+
info << notification.notification_data.values.compact.join("\n")
|
|
202
|
+
end
|
|
203
|
+
info
|
|
204
|
+
end
|
|
205
|
+
|
|
194
206
|
def warnings
|
|
195
207
|
notification_collector.collection.each_with_object({}) do |notification, warnings|
|
|
196
208
|
warning_type = notification.class.to_s.split(':').last.tableize
|
|
@@ -218,6 +230,10 @@ module Bullet
|
|
|
218
230
|
return_value
|
|
219
231
|
end
|
|
220
232
|
|
|
233
|
+
def console_enabled?
|
|
234
|
+
UniformNotifier.active_notifiers.include?(UniformNotifier::JavascriptConsole)
|
|
235
|
+
end
|
|
236
|
+
|
|
221
237
|
private
|
|
222
238
|
|
|
223
239
|
def for_each_active_notifier_with_notification
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
module Bullet
|
|
4
4
|
module Generators
|
|
5
5
|
class InstallGenerator < ::Rails::Generators::Base
|
|
6
|
-
desc
|
|
7
|
-
Description:
|
|
8
|
-
|
|
6
|
+
desc <<~DESC
|
|
7
|
+
Description:
|
|
8
|
+
Enable bullet in development/test for your application.
|
|
9
9
|
DESC
|
|
10
10
|
|
|
11
11
|
def enable_in_development
|
|
@@ -101,6 +101,29 @@ module Bullet
|
|
|
101
101
|
expect(NPlusOneQuery).to_not receive(:create_notification)
|
|
102
102
|
NPlusOneQuery.call_association(@post, :association)
|
|
103
103
|
end
|
|
104
|
+
|
|
105
|
+
# just a sanity spec to make sure the following spec works correctly
|
|
106
|
+
it "should create notification when stacktrace contains methods that aren't in the exclude list" do
|
|
107
|
+
method = NPlusOneQuery.method(:excluded_stacktrace_path?).source_location
|
|
108
|
+
in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
|
|
109
|
+
excluded_path = OpenStruct.new(absolute_path: method.first, lineno: method.last)
|
|
110
|
+
|
|
111
|
+
expect(NPlusOneQuery).to receive(:caller_locations).at_least(1).and_return([in_project, excluded_path])
|
|
112
|
+
expect(NPlusOneQuery).to receive(:conditions_met?).and_return(true)
|
|
113
|
+
expect(NPlusOneQuery).to receive(:create_notification)
|
|
114
|
+
NPlusOneQuery.call_association(@post, :association)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'should not create notification when stacktrace contains methods that are in the exclude list' do
|
|
118
|
+
method = NPlusOneQuery.method(:excluded_stacktrace_path?).source_location
|
|
119
|
+
Bullet.stacktrace_excludes = [method]
|
|
120
|
+
in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
|
|
121
|
+
excluded_path = OpenStruct.new(absolute_path: method.first, lineno: method.last)
|
|
122
|
+
|
|
123
|
+
expect(NPlusOneQuery).to receive(:caller_locations).and_return([in_project, excluded_path])
|
|
124
|
+
expect(NPlusOneQuery).to_not receive(:create_notification)
|
|
125
|
+
NPlusOneQuery.call_association(@post, :association)
|
|
126
|
+
end
|
|
104
127
|
end
|
|
105
128
|
end
|
|
106
129
|
|
|
@@ -17,23 +17,28 @@ describe Object do
|
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
context '
|
|
20
|
+
context 'bullet_primary_key_value' do
|
|
21
21
|
it 'should return id' do
|
|
22
22
|
post = Post.first
|
|
23
|
-
expect(post.
|
|
23
|
+
expect(post.bullet_primary_key_value).to eq(post.id)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
it 'should return primary key value' do
|
|
27
27
|
post = Post.first
|
|
28
28
|
Post.primary_key = 'name'
|
|
29
|
-
expect(post.
|
|
29
|
+
expect(post.bullet_primary_key_value).to eq(post.name)
|
|
30
30
|
Post.primary_key = 'id'
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
it 'should return value for multiple primary keys' do
|
|
34
34
|
post = Post.first
|
|
35
35
|
allow(Post).to receive(:primary_keys).and_return(%i[category_id writer_id])
|
|
36
|
-
expect(post.
|
|
36
|
+
expect(post.bullet_primary_key_value).to eq("#{post.category_id},#{post.writer_id}")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'it should return nil for unpersisted records' do
|
|
40
|
+
post = Post.new(id: 123)
|
|
41
|
+
expect(post.bullet_primary_key_value).to be_nil
|
|
37
42
|
end
|
|
38
43
|
end
|
|
39
44
|
end
|
data/spec/bullet/rack_spec.rb
CHANGED
|
@@ -45,9 +45,9 @@ module Bullet
|
|
|
45
45
|
expect(middleware).not_to be_empty(response)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
it 'should be
|
|
48
|
+
it 'should be false if response is not found' do
|
|
49
49
|
response = ['Not Found']
|
|
50
|
-
expect(middleware).
|
|
50
|
+
expect(middleware).not_to be_empty(response)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
it 'should be true if response body is empty' do
|
|
@@ -68,6 +68,7 @@ module Bullet
|
|
|
68
68
|
it 'should change response body if notification is active' do
|
|
69
69
|
expect(Bullet).to receive(:notification?).and_return(true)
|
|
70
70
|
expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
|
|
71
|
+
expect(middleware).to receive(:xhr_script).and_return('')
|
|
71
72
|
expect(Bullet).to receive(:perform_out_of_channel_notifications)
|
|
72
73
|
status, headers, response = middleware.call('Content-Type' => 'text/html')
|
|
73
74
|
expect(headers['Content-Length']).to eq('56')
|
|
@@ -81,7 +82,7 @@ module Bullet
|
|
|
81
82
|
expect(Bullet).to receive(:notification?).and_return(true)
|
|
82
83
|
expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
|
|
83
84
|
status, headers, response = middleware.call('Content-Type' => 'text/html')
|
|
84
|
-
expect(headers['Content-Length']).to eq(
|
|
85
|
+
expect(headers['Content-Length']).to eq((58 + middleware.send(:xhr_script).length).to_s)
|
|
85
86
|
end
|
|
86
87
|
end
|
|
87
88
|
|
|
@@ -95,6 +96,14 @@ module Bullet
|
|
|
95
96
|
end
|
|
96
97
|
end
|
|
97
98
|
|
|
99
|
+
context '#set_header' do
|
|
100
|
+
it 'should truncate headers to under 8kb' do
|
|
101
|
+
long_header = ['a' * 1024] * 10
|
|
102
|
+
expected_res = (['a' * 1024] * 7).to_json
|
|
103
|
+
expect(middleware.set_header({}, 'Dummy-Header', long_header)).to eq(expected_res)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
98
107
|
describe '#response_body' do
|
|
99
108
|
let(:response) { double }
|
|
100
109
|
let(:body_string) { '<html><body>My Body</body></html>' }
|
|
@@ -226,7 +226,7 @@ if active_record?
|
|
|
226
226
|
context 'post => comment' do
|
|
227
227
|
it 'should detect unused preload with post => comments' do
|
|
228
228
|
Post.includes(:comments).each do |post|
|
|
229
|
-
post.comments.first
|
|
229
|
+
post.comments.first&.name
|
|
230
230
|
end
|
|
231
231
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
232
232
|
expect(Bullet::Detector::Association).not_to be_unused_preload_associations_for(Post, :comments)
|
|
@@ -246,7 +246,7 @@ if active_record?
|
|
|
246
246
|
category = Category.first
|
|
247
247
|
category.draft_post.destroy!
|
|
248
248
|
post = category.draft_post
|
|
249
|
-
post.
|
|
249
|
+
post.update!(link: true)
|
|
250
250
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
251
251
|
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
|
252
252
|
|
|
@@ -331,7 +331,7 @@ if active_record?
|
|
|
331
331
|
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
|
332
332
|
end
|
|
333
333
|
|
|
334
|
-
it 'should
|
|
334
|
+
it 'should detect preload with comment => post' do
|
|
335
335
|
Comment.includes(:post).each do |comment|
|
|
336
336
|
comment.post.name
|
|
337
337
|
end
|
|
@@ -356,6 +356,17 @@ if active_record?
|
|
|
356
356
|
|
|
357
357
|
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
|
358
358
|
end
|
|
359
|
+
|
|
360
|
+
it 'should not detect newly assigned object in an after_save' do
|
|
361
|
+
new_post = Post.new(category: Category.first)
|
|
362
|
+
|
|
363
|
+
new_post.trigger_after_save = true
|
|
364
|
+
new_post.save!
|
|
365
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
366
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
|
367
|
+
|
|
368
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
|
369
|
+
end
|
|
359
370
|
end
|
|
360
371
|
|
|
361
372
|
context 'comment => post => category' do
|
|
@@ -528,6 +539,44 @@ if active_record?
|
|
|
528
539
|
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
|
529
540
|
end
|
|
530
541
|
end
|
|
542
|
+
|
|
543
|
+
context 'firm => clients => groups' do
|
|
544
|
+
it 'should detect non preload associations' do
|
|
545
|
+
Firm.all.each do |firm|
|
|
546
|
+
firm.groups.map(&:name)
|
|
547
|
+
end
|
|
548
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
549
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
|
550
|
+
|
|
551
|
+
expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Firm, :groups)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
it 'should detect preload associations' do
|
|
555
|
+
Firm.includes(:groups).each do |firm|
|
|
556
|
+
firm.groups.map(&:name)
|
|
557
|
+
end
|
|
558
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
559
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
|
560
|
+
|
|
561
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
it 'should not detect preload associations' do
|
|
565
|
+
Firm.all.map(&:name)
|
|
566
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
567
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
|
568
|
+
|
|
569
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
it 'should detect unused preload associations' do
|
|
573
|
+
Firm.includes(:groups).map(&:name)
|
|
574
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
|
575
|
+
expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Firm, :groups)
|
|
576
|
+
|
|
577
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
|
578
|
+
end
|
|
579
|
+
end
|
|
531
580
|
end
|
|
532
581
|
|
|
533
582
|
describe Bullet::Detector::Association, 'has_one' do
|
|
@@ -38,7 +38,7 @@ if !mongoid? && active_record?
|
|
|
38
38
|
expect(Bullet.collected_counter_cache_notifications).to be_empty
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
if active_record5?
|
|
41
|
+
if active_record5? || active_record6?
|
|
42
42
|
it 'should not need counter cache for has_many through' do
|
|
43
43
|
Client.all.each do |client|
|
|
44
44
|
client.firms.size
|
data/spec/models/client.rb
CHANGED
data/spec/models/firm.rb
CHANGED
data/spec/models/post.rb
CHANGED
|
@@ -14,4 +14,19 @@ class Post < ActiveRecord::Base
|
|
|
14
14
|
def link=(*)
|
|
15
15
|
comments.new
|
|
16
16
|
end
|
|
17
|
+
|
|
18
|
+
# see association_spec.rb 'should not detect newly assigned object in an after_save'
|
|
19
|
+
attr_accessor :trigger_after_save
|
|
20
|
+
after_save do
|
|
21
|
+
next unless trigger_after_save
|
|
22
|
+
|
|
23
|
+
temp_comment = Comment.new(post: self)
|
|
24
|
+
# this triggers self to be "possible", even though it's
|
|
25
|
+
# not saved yet
|
|
26
|
+
temp_comment.post
|
|
27
|
+
|
|
28
|
+
# category should NOT whine about not being pre-loaded, because
|
|
29
|
+
# it's obviously attached to a new object
|
|
30
|
+
category
|
|
31
|
+
end
|
|
17
32
|
end
|
data/spec/support/sqlite_seed.rb
CHANGED
|
@@ -45,8 +45,10 @@ module Support
|
|
|
45
45
|
|
|
46
46
|
firm1 = Firm.create(name: 'first')
|
|
47
47
|
firm2 = Firm.create(name: 'second')
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
group1 = Group.create(name: 'first')
|
|
49
|
+
group2 = Group.create(name: 'second')
|
|
50
|
+
client1 = Client.create(name: 'first', group: group1)
|
|
51
|
+
client2 = Client.create(name: 'second', group: group2)
|
|
50
52
|
firm1.clients = [client1, client2]
|
|
51
53
|
firm2.clients = [client1, client2]
|
|
52
54
|
client1.firms << firm1
|
|
@@ -125,6 +127,7 @@ module Support
|
|
|
125
127
|
|
|
126
128
|
create_table :clients do |t|
|
|
127
129
|
t.column :name, :string
|
|
130
|
+
t.column :group_id, :integer
|
|
128
131
|
end
|
|
129
132
|
|
|
130
133
|
create_table :comments do |t|
|
|
@@ -171,6 +174,10 @@ module Support
|
|
|
171
174
|
t.column :name, :string
|
|
172
175
|
end
|
|
173
176
|
|
|
177
|
+
create_table :groups do |t|
|
|
178
|
+
t.column :name, :string
|
|
179
|
+
end
|
|
180
|
+
|
|
174
181
|
create_table :hotels do |t|
|
|
175
182
|
t.column :name, :string
|
|
176
183
|
t.column :location_id, :integer
|
data/test.sh
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
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.0 bundle && BUNDLE_GEMFILE=Gemfile.rails-6.0 bundle exec rspec spec
|
|
3
4
|
BUNDLE_GEMFILE=Gemfile.rails-5.2 bundle && BUNDLE_GEMFILE=Gemfile.rails-5.2 bundle exec rspec spec
|
|
4
5
|
BUNDLE_GEMFILE=Gemfile.rails-5.1 bundle && BUNDLE_GEMFILE=Gemfile.rails-5.1 bundle exec rspec spec
|
|
5
6
|
BUNDLE_GEMFILE=Gemfile.rails-5.0 bundle && BUNDLE_GEMFILE=Gemfile.rails-5.0 bundle exec rspec spec
|
|
6
7
|
BUNDLE_GEMFILE=Gemfile.rails-4.2 bundle && BUNDLE_GEMFILE=Gemfile.rails-4.2 bundle exec rspec spec
|
|
7
8
|
BUNDLE_GEMFILE=Gemfile.rails-4.1 bundle && BUNDLE_GEMFILE=Gemfile.rails-4.1 bundle exec rspec spec
|
|
8
9
|
BUNDLE_GEMFILE=Gemfile.rails-4.0 bundle && BUNDLE_GEMFILE=Gemfile.rails-4.0 bundle exec rspec spec
|
|
10
|
+
BUNDLE_GEMFILE=Gemfile.mongoid-7.0 bundle && BUNDLE_GEMFILE=Gemfile.mongoid-7.0 bundle exec rspec spec
|
|
9
11
|
BUNDLE_GEMFILE=Gemfile.mongoid-6.0 bundle && BUNDLE_GEMFILE=Gemfile.mongoid-6.0 bundle exec rspec spec
|
|
10
12
|
BUNDLE_GEMFILE=Gemfile.mongoid-5.0 bundle && BUNDLE_GEMFILE=Gemfile.mongoid-5.0 bundle exec rspec spec
|
|
11
13
|
BUNDLE_GEMFILE=Gemfile.mongoid-4.0 bundle && BUNDLE_GEMFILE=Gemfile.mongoid-4.0 bundle exec rspec spec
|
data/update.sh
CHANGED
|
@@ -4,6 +4,7 @@ BUNDLE_GEMFILE=Gemfile.rails-5.0 bundle update
|
|
|
4
4
|
BUNDLE_GEMFILE=Gemfile.rails-4.2 bundle update
|
|
5
5
|
BUNDLE_GEMFILE=Gemfile.rails-4.1 bundle update
|
|
6
6
|
BUNDLE_GEMFILE=Gemfile.rails-4.0 bundle update
|
|
7
|
+
BUNDLE_GEMFILE=Gemfile.mongoid-7.0 bundle update
|
|
7
8
|
BUNDLE_GEMFILE=Gemfile.mongoid-6.0 bundle update
|
|
8
9
|
BUNDLE_GEMFILE=Gemfile.mongoid-5.0 bundle update
|
|
9
10
|
BUNDLE_GEMFILE=Gemfile.mongoid-4.0 bundle update
|