bullet 5.7.4 → 5.8.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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -11
  3. data/Gemfile.rails-5.2 +1 -1
  4. data/README.md +7 -4
  5. data/Rakefile +1 -1
  6. data/bullet.gemspec +9 -3
  7. data/lib/bullet/active_record4.rb +15 -13
  8. data/lib/bullet/active_record41.rb +15 -13
  9. data/lib/bullet/active_record42.rb +16 -13
  10. data/lib/bullet/active_record5.rb +26 -13
  11. data/lib/bullet/active_record52.rb +21 -13
  12. data/lib/bullet/dependency.rb +3 -1
  13. data/lib/bullet/detector/association.rb +4 -2
  14. data/lib/bullet/detector/base.rb +2 -0
  15. data/lib/bullet/detector/counter_cache.rb +3 -0
  16. data/lib/bullet/detector/n_plus_one_query.rb +11 -9
  17. data/lib/bullet/detector/unused_eager_loading.rb +3 -0
  18. data/lib/bullet/detector.rb +2 -0
  19. data/lib/bullet/ext/object.rb +3 -1
  20. data/lib/bullet/ext/string.rb +3 -1
  21. data/lib/bullet/mongoid4x.rb +7 -2
  22. data/lib/bullet/mongoid5x.rb +7 -2
  23. data/lib/bullet/mongoid6x.rb +7 -4
  24. data/lib/bullet/notification/base.rb +3 -1
  25. data/lib/bullet/notification/counter_cache.rb +2 -0
  26. data/lib/bullet/notification/n_plus_one_query.rb +2 -0
  27. data/lib/bullet/notification/unused_eager_loading.rb +2 -0
  28. data/lib/bullet/notification.rb +2 -0
  29. data/lib/bullet/notification_collector.rb +2 -0
  30. data/lib/bullet/rack.rb +19 -15
  31. data/lib/bullet/registry/association.rb +2 -0
  32. data/lib/bullet/registry/base.rb +2 -0
  33. data/lib/bullet/registry/object.rb +2 -0
  34. data/lib/bullet/registry.rb +2 -0
  35. data/lib/bullet/stack_trace_filter.rb +43 -22
  36. data/lib/bullet/version.rb +3 -1
  37. data/lib/bullet.rb +5 -2
  38. data/lib/generators/bullet/install_generator.rb +5 -3
  39. data/perf/benchmark.rb +2 -0
  40. data/rails/init.rb +2 -0
  41. data/spec/bullet/detector/association_spec.rb +2 -0
  42. data/spec/bullet/detector/base_spec.rb +2 -0
  43. data/spec/bullet/detector/counter_cache_spec.rb +2 -0
  44. data/spec/bullet/detector/n_plus_one_query_spec.rb +25 -0
  45. data/spec/bullet/detector/unused_eager_loading_spec.rb +2 -0
  46. data/spec/bullet/ext/object_spec.rb +2 -0
  47. data/spec/bullet/ext/string_spec.rb +2 -0
  48. data/spec/bullet/notification/base_spec.rb +2 -0
  49. data/spec/bullet/notification/counter_cache_spec.rb +2 -0
  50. data/spec/bullet/notification/n_plus_one_query_spec.rb +2 -0
  51. data/spec/bullet/notification/unused_eager_loading_spec.rb +2 -0
  52. data/spec/bullet/notification_collector_spec.rb +2 -0
  53. data/spec/bullet/rack_spec.rb +2 -0
  54. data/spec/bullet/registry/association_spec.rb +2 -0
  55. data/spec/bullet/registry/base_spec.rb +2 -0
  56. data/spec/bullet/registry/object_spec.rb +2 -0
  57. data/spec/bullet_spec.rb +2 -0
  58. data/spec/integration/active_record/association_spec.rb +63 -1
  59. data/spec/integration/counter_cache_spec.rb +2 -0
  60. data/spec/integration/mongoid/association_spec.rb +2 -0
  61. data/spec/models/address.rb +2 -0
  62. data/spec/models/author.rb +2 -0
  63. data/spec/models/base_user.rb +2 -0
  64. data/spec/models/category.rb +2 -0
  65. data/spec/models/city.rb +2 -0
  66. data/spec/models/client.rb +4 -0
  67. data/spec/models/comment.rb +2 -0
  68. data/spec/models/company.rb +2 -0
  69. data/spec/models/country.rb +2 -0
  70. data/spec/models/document.rb +2 -0
  71. data/spec/models/entry.rb +2 -0
  72. data/spec/models/firm.rb +3 -0
  73. data/spec/models/folder.rb +2 -0
  74. data/spec/models/group.rb +4 -0
  75. data/spec/models/mongoid/address.rb +2 -0
  76. data/spec/models/mongoid/category.rb +2 -0
  77. data/spec/models/mongoid/comment.rb +2 -0
  78. data/spec/models/mongoid/company.rb +2 -0
  79. data/spec/models/mongoid/entry.rb +2 -0
  80. data/spec/models/mongoid/post.rb +2 -0
  81. data/spec/models/mongoid/user.rb +2 -0
  82. data/spec/models/newspaper.rb +2 -0
  83. data/spec/models/page.rb +2 -0
  84. data/spec/models/person.rb +2 -0
  85. data/spec/models/pet.rb +2 -0
  86. data/spec/models/post.rb +17 -0
  87. data/spec/models/relationship.rb +2 -0
  88. data/spec/models/reply.rb +2 -0
  89. data/spec/models/student.rb +2 -0
  90. data/spec/models/submission.rb +2 -0
  91. data/spec/models/teacher.rb +2 -0
  92. data/spec/models/user.rb +2 -0
  93. data/spec/models/writer.rb +2 -0
  94. data/spec/spec_helper.rb +2 -0
  95. data/spec/support/bullet_ext.rb +2 -0
  96. data/spec/support/mongo_seed.rb +4 -2
  97. data/spec/support/rack_double.rb +2 -0
  98. data/spec/support/sqlite_seed.rb +13 -4
  99. data/tasks/bullet_tasks.rake +2 -0
  100. metadata +9 -5
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Mongoid
3
5
  def self.enable
@@ -21,13 +23,16 @@ module Bullet
21
23
  end
22
24
 
23
25
  def each(&block)
24
- records = view.map { |doc| ::Mongoid::Factory.from_db(klass, doc) }
26
+ return to_enum unless block_given?
27
+
28
+ records = []
29
+ origin_each { |record| records << record }
25
30
  if records.length > 1
26
31
  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
27
32
  elsif records.size == 1
28
33
  Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
29
34
  end
30
- origin_each(&block)
35
+ records.each(&block)
31
36
  end
32
37
 
33
38
  def eager_load(docs)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Mongoid
3
5
  def self.enable
@@ -8,20 +10,21 @@ module Bullet
8
10
  alias_method :origin_each, :each
9
11
  alias_method :origin_eager_load, :eager_load
10
12
 
11
- def first
12
- result = origin_first
13
+ def first(opt = {})
14
+ result = origin_first(opt)
13
15
  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
14
16
  result
15
17
  end
16
18
 
17
- def last
18
- result = origin_last
19
+ def last(opt = {})
20
+ result = origin_last(opt)
19
21
  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
20
22
  result
21
23
  end
22
24
 
23
25
  def each(&block)
24
26
  return to_enum unless block_given?
27
+
25
28
  records = []
26
29
  origin_each { |record| records << record }
27
30
  if records.length > 1
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Notification
3
5
  class Base
@@ -71,7 +73,7 @@ module Bullet
71
73
  protected
72
74
 
73
75
  def klazz_associations_str
74
- " #{@base_class} => [#{@associations.map(&:inspect).join(', '.freeze)}]"
76
+ " #{@base_class} => [#{@associations.map(&:inspect).join(', ')}]"
75
77
  end
76
78
 
77
79
  def associations_str
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Notification
3
5
  class CounterCache < Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Notification
3
5
  class NPlusOneQuery < Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Notification
3
5
  class UnusedEagerLoading < Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Notification
3
5
  autoload :Base, 'bullet/notification/base'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
5
  module Bullet
data/lib/bullet/rack.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  class Rack
3
5
  include Dependency
@@ -8,16 +10,17 @@ module Bullet
8
10
 
9
11
  def call(env)
10
12
  return @app.call(env) unless Bullet.enable?
13
+
11
14
  Bullet.start_request
12
15
  status, headers, response = @app.call(env)
13
16
 
14
17
  response_body = nil
15
18
  if Bullet.notification?
16
19
  if !file?(headers) && !sse?(headers) && !empty?(response) &&
17
- status == 200 && !response_body(response).frozen? && html_request?(headers, response)
20
+ status == 200 && html_request?(headers, response)
18
21
  response_body = response_body(response)
19
- append_to_html_body(response_body, footer_note) if Bullet.add_footer
20
- append_to_html_body(response_body, Bullet.gather_inline_notifications)
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)
21
24
  headers['Content-Length'] = response_body.bytesize.to_s
22
25
  end
23
26
  Bullet.perform_out_of_channel_notifications(env)
@@ -42,11 +45,12 @@ module Bullet
42
45
  end
43
46
 
44
47
  def append_to_html_body(response_body, content)
45
- if response_body.include?('</body>')
46
- position = response_body.rindex('</body>')
47
- response_body.insert(position, content)
48
+ body = response_body.dup
49
+ if body.include?('</body>')
50
+ position = body.rindex('</body>')
51
+ body.insert(position, content)
48
52
  else
49
- response_body << content
53
+ body << content
50
54
  end
51
55
  end
52
56
 
@@ -63,7 +67,7 @@ module Bullet
63
67
  end
64
68
 
65
69
  def html_request?(headers, response)
66
- headers['Content-Type'] && headers['Content-Type'].include?('text/html') && response_body(response).include?('<html')
70
+ headers['Content-Type']&.include?('text/html') && response_body(response).include?('<html')
67
71
  end
68
72
 
69
73
  def response_body(response)
@@ -77,13 +81,13 @@ module Bullet
77
81
  private
78
82
 
79
83
  def footer_div_attributes
80
- <<EOF
81
- 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);
82
- -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
83
- -moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
84
- padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
85
- color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
86
- EOF
84
+ <<~EOF
85
+ 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);
86
+ -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
87
+ -moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
88
+ padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
89
+ color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
90
+ EOF
87
91
  end
88
92
 
89
93
  def footer_close_button
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Registry
3
5
  class Association < Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Registry
3
5
  class Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Registry
3
5
  class Object < Base
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Registry
3
5
  autoload :Base, 'bullet/registry/base'
@@ -1,51 +1,72 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module StackTraceFilter
3
- VENDOR_PATH = '/vendor'.freeze
5
+ VENDOR_PATH = '/vendor'
4
6
 
5
7
  def caller_in_project
6
8
  app_root = rails? ? Rails.root.to_s : Dir.pwd
7
9
  vendor_root = app_root + VENDOR_PATH
8
10
  bundler_path = Bundler.bundle_path.to_s
9
- select_caller_locations do |caller_path|
11
+ select_caller_locations do |location|
12
+ caller_path = location_as_path(location)
10
13
  caller_path.include?(app_root) && !caller_path.include?(vendor_root) && !caller_path.include?(bundler_path) ||
11
- Bullet.stacktrace_includes.any? do |include_pattern|
12
- case include_pattern
13
- when String
14
- caller_path.include?(include_pattern)
15
- when Regexp
16
- caller_path =~ include_pattern
17
- end
18
- end
14
+ Bullet.stacktrace_includes.any? { |include_pattern| pattern_matches?(location, include_pattern) }
19
15
  end
20
16
  end
21
17
 
22
18
  def excluded_stacktrace_path?
23
19
  Bullet.stacktrace_excludes.any? do |exclude_pattern|
24
- caller_in_project.any? do |location|
25
- caller_path = location.absolute_path.to_s
26
- case exclude_pattern
27
- when String
28
- caller_path.include?(exclude_pattern)
29
- when Regexp
30
- caller_path =~ exclude_pattern
31
- end
32
- end
20
+ caller_in_project.any? { |location| pattern_matches?(location, exclude_pattern) }
33
21
  end
34
22
  end
35
23
 
36
24
  private
37
25
 
26
+ def pattern_matches?(location, pattern)
27
+ path = location_as_path(location)
28
+ case pattern
29
+ when Array
30
+ pattern_path = pattern.first
31
+ filter = pattern.last
32
+ return false unless pattern_matches?(location, pattern_path)
33
+
34
+ case filter
35
+ when Range
36
+ filter.include?(location.lineno)
37
+ when Integer
38
+ filter == location.lineno
39
+ when String
40
+ filter == location.base_label
41
+ end
42
+ when String
43
+ path.include?(pattern)
44
+ when Regexp
45
+ path =~ pattern
46
+ end
47
+ end
48
+
49
+ def location_as_path(location)
50
+ ruby_19? ? location : location.absolute_path.to_s
51
+ end
52
+
38
53
  def select_caller_locations
39
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
54
+ if ruby_19?
40
55
  caller.select do |caller_path|
41
56
  yield caller_path
42
57
  end
43
58
  else
44
59
  caller_locations.select do |location|
45
- caller_path = location.absolute_path.to_s
46
- yield caller_path
60
+ yield location
47
61
  end
48
62
  end
49
63
  end
64
+
65
+ def ruby_19?
66
+ if @ruby_19.nil?
67
+ @ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
68
+ end
69
+ @ruby_19
70
+ end
50
71
  end
51
72
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
- VERSION = '5.7.4'.freeze
4
+ VERSION = '5.8.0'
3
5
  end
data/lib/bullet.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/core_ext/module/delegation'
2
4
  require 'set'
3
5
  require 'uniform_notifier'
@@ -17,8 +19,8 @@ module Bullet
17
19
  autoload :Registry, 'bullet/registry'
18
20
  autoload :NotificationCollector, 'bullet/notification_collector'
19
21
 
20
- BULLET_DEBUG = 'BULLET_DEBUG'.freeze
21
- TRUE = 'true'.freeze
22
+ BULLET_DEBUG = 'BULLET_DEBUG'
23
+ TRUE = 'true'
22
24
 
23
25
  if defined? Rails::Railtie
24
26
  class BulletRailtie < Rails::Railtie
@@ -161,6 +163,7 @@ module Bullet
161
163
 
162
164
  def notification?
163
165
  return unless start?
166
+
164
167
  Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
165
168
  notification_collector.notifications_present?
166
169
  end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  module Generators
3
5
  class InstallGenerator < ::Rails::Generators::Base
4
- desc <<-DESC
5
- Description:
6
- Enable bullet in development/test for your application.
6
+ desc <<~DESC
7
+ Description:
8
+ Enable bullet in development/test for your application.
7
9
  DESC
8
10
 
9
11
  def enable_in_development
data/perf/benchmark.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  $LOAD_PATH << 'lib'
2
4
  require 'benchmark'
3
5
  require 'rails'
data/rails/init.rb CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bullet'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Bullet
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Bullet
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Bullet
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Bullet
@@ -99,6 +101,29 @@ module Bullet
99
101
  expect(NPlusOneQuery).to_not receive(:create_notification)
100
102
  NPlusOneQuery.call_association(@post, :association)
101
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
102
127
  end
103
128
  end
104
129
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Bullet
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Object do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe String do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Bullet
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Bullet
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Bullet
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Bullet
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Bullet
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Bullet
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Bullet
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Bullet
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  module Bullet
data/spec/bullet_spec.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Bullet, focused: true do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  if active_record?
@@ -224,7 +226,7 @@ if active_record?
224
226
  context 'post => comment' do
225
227
  it 'should detect unused preload with post => comments' do
226
228
  Post.includes(:comments).each do |post|
227
- post.comments.first.name if post.comments.first
229
+ post.comments.first&.name
228
230
  end
229
231
  Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
230
232
  expect(Bullet::Detector::Association).not_to be_unused_preload_associations_for(Post, :comments)
@@ -354,6 +356,28 @@ if active_record?
354
356
 
355
357
  expect(Bullet::Detector::Association).to be_completely_preloading_associations
356
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
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
366
+
367
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
368
+ end
369
+
370
+ it 'should not detect "manual" preload' do
371
+ comment = Comment.all.to_a.first
372
+ post = Post.find(comment.post_id)
373
+ # "manually" preload with out-of-band data
374
+ comment.post = post
375
+ # loading it should not trigger anything
376
+ comment.post
377
+
378
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
379
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
380
+ end
357
381
  end
358
382
 
359
383
  context 'comment => post => category' do
@@ -526,6 +550,44 @@ if active_record?
526
550
  expect(Bullet::Detector::Association).to be_completely_preloading_associations
527
551
  end
528
552
  end
553
+
554
+ context 'firm => clients => groups' do
555
+ it 'should detect non preload associations' do
556
+ Firm.all.each do |firm|
557
+ firm.groups.map(&:name)
558
+ end
559
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
560
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
561
+
562
+ expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Firm, :groups)
563
+ end
564
+
565
+ it 'should detect preload associations' do
566
+ Firm.includes(:groups).each do |firm|
567
+ firm.groups.map(&:name)
568
+ end
569
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
570
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
571
+
572
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
573
+ end
574
+
575
+ it 'should not detect preload associations' do
576
+ Firm.all.map(&:name)
577
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
578
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
579
+
580
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
581
+ end
582
+
583
+ it 'should detect unused preload associations' do
584
+ Firm.includes(:groups).map(&:name)
585
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
586
+ expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Firm, :groups)
587
+
588
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
589
+ end
590
+ end
529
591
  end
530
592
 
531
593
  describe Bullet::Detector::Association, 'has_one' do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  if !mongoid? && active_record?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  if mongoid?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Address < ActiveRecord::Base
2
4
  belongs_to :company
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Author < ActiveRecord::Base
2
4
  has_many :documents
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class BaseUser < ActiveRecord::Base
2
4
  has_many :comments
3
5
  has_many :posts
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Category < ActiveRecord::Base
2
4
  has_many :posts, inverse_of: :category
3
5
  has_many :entries
data/spec/models/city.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class City < ActiveRecord::Base
2
4
  belongs_to :country
3
5
  end
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Client < ActiveRecord::Base
4
+ belongs_to :group
5
+
2
6
  has_many :relationships
3
7
  has_many :firms, through: :relationships
4
8
  end