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.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +17 -14
  4. data/Gemfile.mongoid-7.0 +15 -0
  5. data/Gemfile.rails-4.0 +1 -1
  6. data/Gemfile.rails-4.1 +1 -1
  7. data/Gemfile.rails-4.2 +1 -1
  8. data/Gemfile.rails-5.0 +1 -1
  9. data/Gemfile.rails-5.1 +1 -1
  10. data/Gemfile.rails-5.2 +2 -2
  11. data/Gemfile.rails-6.0 +15 -0
  12. data/README.md +8 -5
  13. data/Rakefile +1 -1
  14. data/bullet.gemspec +8 -3
  15. data/lib/bullet/active_record4.rb +5 -13
  16. data/lib/bullet/active_record41.rb +5 -13
  17. data/lib/bullet/active_record42.rb +6 -13
  18. data/lib/bullet/active_record5.rb +15 -13
  19. data/lib/bullet/active_record52.rb +12 -13
  20. data/lib/bullet/active_record60.rb +245 -0
  21. data/lib/bullet/bullet_xhr.js +58 -0
  22. data/lib/bullet/dependency.rb +17 -5
  23. data/lib/bullet/detector/association.rb +4 -4
  24. data/lib/bullet/detector/counter_cache.rb +4 -3
  25. data/lib/bullet/detector/n_plus_one_query.rb +13 -13
  26. data/lib/bullet/detector/unused_eager_loading.rb +2 -1
  27. data/lib/bullet/ext/object.rb +5 -3
  28. data/lib/bullet/ext/string.rb +1 -1
  29. data/lib/bullet/mongoid4x.rb +1 -0
  30. data/lib/bullet/mongoid5x.rb +1 -0
  31. data/lib/bullet/mongoid6x.rb +5 -4
  32. data/lib/bullet/mongoid7x.rb +61 -0
  33. data/lib/bullet/notification/base.rb +1 -1
  34. data/lib/bullet/rack.rb +45 -27
  35. data/lib/bullet/stack_trace_filter.rb +43 -25
  36. data/lib/bullet/version.rb +1 -1
  37. data/lib/bullet.rb +21 -5
  38. data/lib/generators/bullet/install_generator.rb +3 -3
  39. data/spec/bullet/detector/n_plus_one_query_spec.rb +23 -0
  40. data/spec/bullet/ext/object_spec.rb +9 -4
  41. data/spec/bullet/rack_spec.rb +12 -3
  42. data/spec/integration/active_record/association_spec.rb +52 -3
  43. data/spec/integration/counter_cache_spec.rb +1 -1
  44. data/spec/models/client.rb +2 -0
  45. data/spec/models/firm.rb +1 -0
  46. data/spec/models/group.rb +4 -0
  47. data/spec/models/post.rb +15 -0
  48. data/spec/support/sqlite_seed.rb +9 -2
  49. data/test.sh +2 -0
  50. data/update.sh +1 -0
  51. 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
- status == 200 && html_request?(headers, response)
20
- response_body = response_body(response)
21
- response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
22
- response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
23
- headers['Content-Length'] = response_body.bytesize.to_s
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 rails?
36
- (response.is_a?(Array) && response.size <= 1) ||
37
- !response.respond_to?(:body) ||
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}>" + footer_close_button + Bullet.footer_info.uniq.join('<br>') + '</div>'
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'] && headers['Content-Type'].include?('text/html') && response_body(response).include?('<html')
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
- <<EOF
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
- -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
86
- -moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
87
- padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
88
- color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
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;'>&times;</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
- def footer_close_button
93
- "<span onclick='this.parentNode.remove()' style='position:absolute; right: 10px; top: 0px; font-weight: bold; color: #333;'>&times;</span>"
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'.freeze
5
+ VENDOR_PATH = '/vendor'
6
6
 
7
7
  def caller_in_project
8
- app_root = rails? ? Rails.root.to_s : Dir.pwd
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 |caller_path|
12
- caller_path.include?(app_root) && !caller_path.include?(vendor_root) && !caller_path.include?(bundler_path) ||
13
- Bullet.stacktrace_includes.any? do |include_pattern|
14
- case include_pattern
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? do |location|
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 Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
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
- caller_path = location.absolute_path.to_s
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '5.7.5'.freeze
4
+ VERSION = '6.0.0'
5
5
  end
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'.freeze
23
- TRUE = 'true'.freeze
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
- root_path = (rails? ? Rails.root.to_s : Dir.pwd).to_s
115
- FileUtils.mkdir_p(root_path + '/log')
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 <<-DESC
7
- Description:
8
- Enable bullet in development/test for your application.
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 'primary_key_value' do
20
+ context 'bullet_primary_key_value' do
21
21
  it 'should return id' do
22
22
  post = Post.first
23
- expect(post.primary_key_value).to eq(post.id)
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.primary_key_value).to eq(post.name)
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.primary_key_value).to eq("#{post.category_id},#{post.writer_id}")
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
@@ -45,9 +45,9 @@ module Bullet
45
45
  expect(middleware).not_to be_empty(response)
46
46
  end
47
47
 
48
- it 'should be true if response is not found' do
48
+ it 'should be false if response is not found' do
49
49
  response = ['Not Found']
50
- expect(middleware).to be_empty(response)
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('58')
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.name if 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.update_attributes!(link: true)
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 dtect preload with comment => post' do
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
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Client < ActiveRecord::Base
4
+ belongs_to :group
5
+
4
6
  has_many :relationships
5
7
  has_many :firms, through: :relationships
6
8
  end
data/spec/models/firm.rb CHANGED
@@ -3,4 +3,5 @@
3
3
  class Firm < ActiveRecord::Base
4
4
  has_many :relationships
5
5
  has_many :clients, through: :relationships
6
+ has_many :groups, through: :clients
6
7
  end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Group < ActiveRecord::Base
4
+ end
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
@@ -45,8 +45,10 @@ module Support
45
45
 
46
46
  firm1 = Firm.create(name: 'first')
47
47
  firm2 = Firm.create(name: 'second')
48
- client1 = Client.create(name: 'first')
49
- client2 = Client.create(name: 'second')
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