bullet 6.1.3 → 7.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +82 -0
  3. data/CHANGELOG.md +22 -0
  4. data/Gemfile.rails-7.0 +10 -0
  5. data/README.md +12 -10
  6. data/lib/bullet/active_record41.rb +1 -0
  7. data/lib/bullet/active_record42.rb +1 -0
  8. data/lib/bullet/active_record52.rb +11 -17
  9. data/lib/bullet/active_record60.rb +11 -17
  10. data/lib/bullet/active_record61.rb +11 -17
  11. data/lib/bullet/active_record70.rb +275 -0
  12. data/lib/bullet/bullet_xhr.js +1 -0
  13. data/lib/bullet/dependency.rb +10 -0
  14. data/lib/bullet/detector/base.rb +2 -1
  15. data/lib/bullet/detector/counter_cache.rb +1 -1
  16. data/lib/bullet/detector/n_plus_one_query.rb +3 -3
  17. data/lib/bullet/detector/unused_eager_loading.rb +1 -1
  18. data/lib/bullet/mongoid7x.rb +26 -9
  19. data/lib/bullet/notification.rb +2 -1
  20. data/lib/bullet/rack.rb +6 -3
  21. data/lib/bullet/stack_trace_filter.rb +7 -8
  22. data/lib/bullet/version.rb +1 -1
  23. data/lib/bullet.rb +23 -24
  24. data/perf/benchmark.rb +4 -1
  25. data/spec/bullet/detector/unused_eager_loading_spec.rb +6 -2
  26. data/spec/bullet/ext/object_spec.rb +1 -1
  27. data/spec/bullet/rack_spec.rb +90 -13
  28. data/spec/bullet_spec.rb +15 -15
  29. data/spec/integration/active_record/association_spec.rb +27 -8
  30. data/spec/integration/counter_cache_spec.rb +4 -4
  31. data/spec/integration/mongoid/association_spec.rb +1 -1
  32. data/spec/models/deal.rb +5 -0
  33. data/spec/models/folder.rb +2 -1
  34. data/spec/models/group.rb +2 -1
  35. data/spec/models/page.rb +2 -1
  36. data/spec/models/post.rb +2 -0
  37. data/spec/models/role.rb +7 -0
  38. data/spec/models/user.rb +1 -0
  39. data/spec/models/writer.rb +2 -1
  40. data/spec/spec_helper.rb +0 -2
  41. data/spec/support/mongo_seed.rb +1 -0
  42. data/spec/support/sqlite_seed.rb +30 -0
  43. data/test.sh +2 -0
  44. metadata +10 -4
  45. data/.travis.yml +0 -33
@@ -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)
@@ -23,16 +23,33 @@ module Bullet
23
23
  end
24
24
 
25
25
  def each(&block)
26
- return to_enum unless block
27
-
28
- records = []
29
- origin_each { |record| records << record }
30
- if records.length > 1
31
- Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
32
- elsif records.size == 1
33
- Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
26
+ return to_enum unless block_given?
27
+
28
+ first_document = nil
29
+ document_count = 0
30
+
31
+ origin_each do |document|
32
+ document_count += 1
33
+
34
+ if document_count == 1
35
+ first_document = document
36
+ elsif document_count == 2
37
+ Bullet::Detector::NPlusOneQuery.add_possible_objects([first_document, document])
38
+ yield(first_document)
39
+ first_document = nil
40
+ yield(document)
41
+ else
42
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(document)
43
+ yield(document)
44
+ end
34
45
  end
35
- records.each(&block)
46
+
47
+ if document_count == 1
48
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(first_document)
49
+ yield(first_document)
50
+ end
51
+
52
+ self
36
53
  end
37
54
 
38
55
  def eager_load(docs)
@@ -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,9 +22,11 @@ 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
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
- else
29
+ elsif !Bullet.skip_http_headers
28
30
  set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
29
31
  set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
30
32
  end
@@ -40,6 +42,7 @@ module Bullet
40
42
  def empty?(response)
41
43
  # response may be ["Not Found"], ["Move Permanently"], etc, but
42
44
  # those should not happen if the status is 200
45
+ return true if !response.respond_to?(:body) && !response.respond_to?(:first)
43
46
  body = response_body(response)
44
47
  body.nil? || body.empty?
45
48
  end
@@ -82,7 +85,7 @@ module Bullet
82
85
  def response_body(response)
83
86
  if response.respond_to?(:body)
84
87
  Array === response.body ? response.body.first : response.body
85
- else
88
+ elsif response.respond_to?(:first)
86
89
  response.first
87
90
  end
88
91
  end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
+ require "bundler"
2
3
 
3
4
  module Bullet
4
5
  module StackTraceFilter
5
6
  VENDOR_PATH = '/vendor'
7
+ IS_RUBY_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
6
8
 
7
9
  def caller_in_project
8
10
  vendor_root = Bullet.app_root + VENDOR_PATH
@@ -10,8 +12,9 @@ module Bullet
10
12
  select_caller_locations do |location|
11
13
  caller_path = location_as_path(location)
12
14
  caller_path.include?(Bullet.app_root) && !caller_path.include?(vendor_root) &&
13
- !caller_path.include?(bundler_path) ||
14
- 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
+ }
15
18
  end
16
19
  end
17
20
 
@@ -47,19 +50,15 @@ module Bullet
47
50
  end
48
51
 
49
52
  def location_as_path(location)
50
- ruby_19? ? location : location.absolute_path.to_s
53
+ IS_RUBY_19 ? location : location.absolute_path.to_s
51
54
  end
52
55
 
53
56
  def select_caller_locations
54
- if ruby_19?
57
+ if IS_RUBY_19
55
58
  caller.select { |caller_path| yield caller_path }
56
59
  else
57
60
  caller_locations.select { |location| yield location }
58
61
  end
59
62
  end
60
-
61
- def ruby_19?
62
- @ruby_19 ||= Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
63
- end
64
63
  end
65
64
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '6.1.3'
4
+ VERSION = '7.0.1'
5
5
  end
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
42
- attr_accessor :add_footer, :orm_patches_applied
38
+ attr_reader :safelist
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
- (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,29 @@ module Bullet
96
95
  @stacktrace_excludes ||= []
97
96
  end
98
97
 
99
- 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
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
103
102
  end
104
103
 
105
- 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? }
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? }
110
109
  end
111
110
 
112
- def get_whitelist_associations(type, class_name)
113
- Array(@whitelist[type][class_name])
111
+ def get_safelist_associations(type, class_name)
112
+ Array(@safelist[type][class_name])
114
113
  end
115
114
 
116
- def reset_whitelist
117
- @whitelist ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }
115
+ def reset_safelist
116
+ @safelist ||= { n_plus_one_query: {}, unused_eager_loading: {}, counter_cache: {} }
118
117
  end
119
118
 
120
- def clear_whitelist
121
- @whitelist = nil
119
+ def clear_safelist
120
+ @safelist = nil
122
121
  end
123
122
 
124
123
  def bullet_logger=(active)
@@ -132,7 +131,7 @@ module Bullet
132
131
  end
133
132
 
134
133
  def debug(title, message)
135
- puts "[Bullet][#{title}] #{message}" if ENV[BULLET_DEBUG] == TRUE
134
+ puts "[Bullet][#{title}] #{message}" if ENV['BULLET_DEBUG'] == 'true'
136
135
  end
137
136
 
138
137
  def start_request
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
@@ -54,6 +54,11 @@ module Bullet
54
54
  response = double(body: '')
55
55
  expect(middleware).to be_empty(response)
56
56
  end
57
+
58
+ it 'should be true if no response body' do
59
+ response = double()
60
+ expect(middleware).to be_empty(response)
61
+ end
57
62
  end
58
63
 
59
64
  context '#call' do
@@ -90,7 +95,7 @@ module Bullet
90
95
  before do
91
96
  expect(Bullet).to receive(:notification?).and_return(true)
92
97
  allow(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
93
- allow(middleware).to receive(:xhr_script).and_return('')
98
+ allow(middleware).to receive(:xhr_script).and_return('<script></script>')
94
99
  allow(middleware).to receive(:footer_note).and_return('footer')
95
100
  expect(Bullet).to receive(:perform_out_of_channel_notifications)
96
101
  end
@@ -99,21 +104,28 @@ module Bullet
99
104
  expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
100
105
  _, headers, response = middleware.call('Content-Type' => 'text/html')
101
106
 
102
- expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
103
- expect(response.first).to start_with('<html><head></head><body>')
104
- expect(response.first).to include('<bullet></bullet><')
107
+ expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
108
+ expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
105
109
  end
106
110
 
107
111
  it 'should change response body for html safe string if add_footer is true' do
108
112
  expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
109
- app.response = Support::ResponseDouble.new.tap do |response|
110
- response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
111
- end
113
+ app.response =
114
+ Support::ResponseDouble.new.tap do |response|
115
+ response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
116
+ end
112
117
  _, headers, response = middleware.call('Content-Type' => 'text/html')
113
118
 
114
- expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
115
- expect(response.first).to start_with('<html><head></head><body>')
116
- expect(response.first).to include('<bullet></bullet><')
119
+ expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
120
+ expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
121
+ end
122
+
123
+ it 'should add the footer-text header for non-html requests when add_footer is true' do
124
+ allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
125
+ allow(Bullet).to receive(:footer_info).and_return(['footer text'])
126
+ app.headers = { 'Content-Type' => 'application/json' }
127
+ _, headers, _response = middleware.call({})
128
+ expect(headers).to include('X-bullet-footer-text' => '["footer text"]')
117
129
  end
118
130
 
119
131
  it 'should change response body if console_enabled is true' do
@@ -125,20 +137,71 @@ module Bullet
125
137
 
126
138
  it 'should change response body for html safe string if console_enabled is true' do
127
139
  expect(Bullet).to receive(:console_enabled?).and_return(true)
128
- app.response = Support::ResponseDouble.new.tap do |response|
129
- response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
130
- end
140
+ app.response =
141
+ Support::ResponseDouble.new.tap do |response|
142
+ response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
143
+ end
131
144
  _, headers, response = middleware.call('Content-Type' => 'text/html')
132
145
  expect(headers['Content-Length']).to eq('56')
133
146
  expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
134
147
  end
135
148
 
149
+ it 'should add headers for non-html requests when console_enabled is true' do
150
+ allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
151
+ allow(Bullet).to receive(:text_notifications).and_return(['text notifications'])
152
+ app.headers = { 'Content-Type' => 'application/json' }
153
+ _, headers, _response = middleware.call({})
154
+ expect(headers).to include('X-bullet-console-text' => '["text notifications"]')
155
+ end
156
+
136
157
  it "shouldn't change response body unnecessarily" do
137
158
  expected_response = Support::ResponseDouble.new 'Actual body'
138
159
  app.response = expected_response
139
160
  _, _, response = middleware.call({})
140
161
  expect(response).to eq(expected_response)
141
162
  end
163
+
164
+ it "shouldn't add headers unnecessarily" do
165
+ app.headers = { 'Content-Type' => 'application/json' }
166
+ _, headers, _response = middleware.call({})
167
+ expect(headers).not_to include('X-bullet-footer-text')
168
+ expect(headers).not_to include('X-bullet-console-text')
169
+ end
170
+
171
+ context 'when skip_http_headers is enabled' do
172
+ before do
173
+ allow(Bullet).to receive(:skip_http_headers).and_return(true)
174
+ end
175
+
176
+ it 'should include the footer but not the xhr script tag if add_footer is true' do
177
+ expect(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
178
+ _, headers, response = middleware.call({})
179
+
180
+ expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
181
+ expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet></body></html>])
182
+ end
183
+
184
+ it 'should not include the xhr script tag if console_enabled is true' do
185
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
186
+ _, headers, response = middleware.call({})
187
+ expect(headers['Content-Length']).to eq('56')
188
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
189
+ end
190
+
191
+ it 'should not add the footer-text header for non-html requests when add_footer is true' do
192
+ allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
193
+ app.headers = { 'Content-Type' => 'application/json' }
194
+ _, headers, _response = middleware.call({})
195
+ expect(headers).not_to include('X-bullet-footer-text')
196
+ end
197
+
198
+ it 'should not add headers for non-html requests when console_enabled is true' do
199
+ allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
200
+ app.headers = { 'Content-Type' => 'application/json' }
201
+ _, headers, _response = middleware.call({})
202
+ expect(headers).not_to include('X-bullet-console-text')
203
+ end
204
+ end
142
205
  end
143
206
 
144
207
  context 'when skip_html_injection is enabled' do
@@ -203,6 +266,20 @@ module Bullet
203
266
  expect(middleware.response_body(response)).to eq body_string
204
267
  end
205
268
  end
269
+
270
+ begin
271
+ require 'rack/files'
272
+
273
+ context 'when `response` is a Rack::Files::Iterator' do
274
+ let(:response) { instance_double(::Rack::Files::Iterator) }
275
+ before { allow(response).to receive(:is_a?).with(::Rack::Files::Iterator) { true } }
276
+
277
+ it 'should return nil' do
278
+ expect(middleware.response_body(response)).to be_nil
279
+ end
280
+ end
281
+ rescue LoadError
282
+ end
206
283
  end
207
284
  end
208
285
  end
data/spec/bullet_spec.rb CHANGED
@@ -74,31 +74,31 @@ describe Bullet, focused: true do
74
74
  end
75
75
  end
76
76
 
77
- describe '#add_whitelist' do
77
+ describe '#add_safelist' do
78
78
  context "for 'special' class names" do
79
- it 'is added to the whitelist successfully' do
80
- 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
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
82
  end
83
83
  end
84
84
  end
85
85
 
86
- describe '#delete_whitelist' do
86
+ describe '#delete_safelist' do
87
87
  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)
90
- Bullet.delete_whitelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
91
- expect(Bullet.whitelist[:n_plus_one_query]).to eq({})
88
+ it 'is deleted from the safelist successfully' do
89
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
90
+ Bullet.delete_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
91
+ expect(Bullet.safelist[:n_plus_one_query]).to eq({})
92
92
  end
93
93
  end
94
94
 
95
95
  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)
99
- 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
96
+ it 'is deleted from the safelist successfully' do
97
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :department)
98
+ Bullet.add_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
99
+ Bullet.delete_safelist(type: :n_plus_one_query, class_name: 'Klass', association: :team)
100
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to include :department
101
+ expect(Bullet.get_safelist_associations(:n_plus_one_query, 'Klass')).to_not include :team
102
102
  end
103
103
  end
104
104
  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) }
@@ -442,6 +451,16 @@ if active_record?
442
451
  expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Student, :teachers)
443
452
  end
444
453
  end
454
+
455
+ context 'user => roles' do
456
+ it 'should detect preload associations' do
457
+ User.first.roles.includes(:resource).each { |role| role.resource }
458
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
459
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
460
+
461
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
462
+ end
463
+ end
445
464
  end
446
465
 
447
466
  describe Bullet::Detector::Association, 'has_many :through' do
@@ -729,9 +748,9 @@ if active_record?
729
748
  end
730
749
  end
731
750
 
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 }
751
+ context 'add n plus one query to safelist' do
752
+ before { Bullet.add_safelist type: :n_plus_one_query, class_name: 'Post', association: :comments }
753
+ after { Bullet.clear_safelist }
735
754
 
736
755
  it 'should not detect n plus one query' do
737
756
  Post.all.each { |post| post.comments.map(&:name) }
@@ -750,9 +769,9 @@ if active_record?
750
769
  end
751
770
  end
752
771
 
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 }
772
+ context 'add unused eager loading to safelist' do
773
+ before { Bullet.add_safelist type: :unused_eager_loading, class_name: 'Post', association: :comments }
774
+ after { Bullet.clear_safelist }
756
775
 
757
776
  it 'should not detect unused eager loading' do
758
777
  Post.includes(:comments).map(&:name)
@@ -28,7 +28,7 @@ if !mongoid? && active_record?
28
28
  expect(Bullet.collected_counter_cache_notifications).to be_empty
29
29
  end
30
30
 
31
- if active_record5? || active_record6?
31
+ if ActiveRecord::VERSION::MAJOR > 4
32
32
  it 'should not need counter cache for has_many through' do
33
33
  Client.all.each { |client| client.firms.size }
34
34
  expect(Bullet.collected_counter_cache_notifications).to be_empty
@@ -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