bullet 6.1.3 → 7.0.1

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 (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