bullet 5.1.0 → 5.4.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +13 -13
  3. data/CHANGELOG.md +20 -2
  4. data/Gemfile.mongoid-2.4 +0 -2
  5. data/Gemfile.mongoid-2.5 +0 -2
  6. data/Gemfile.mongoid-2.6 +0 -2
  7. data/Gemfile.mongoid-2.7 +0 -2
  8. data/Gemfile.mongoid-2.8 +0 -2
  9. data/Gemfile.mongoid-3.0 +0 -2
  10. data/Gemfile.mongoid-3.1 +0 -2
  11. data/Gemfile.mongoid-4.0 +0 -2
  12. data/Gemfile.mongoid-5.0 +0 -2
  13. data/Gemfile.rails-3.0 +0 -2
  14. data/Gemfile.rails-3.1 +0 -2
  15. data/Gemfile.rails-3.2 +0 -2
  16. data/Gemfile.rails-4.0 +0 -2
  17. data/Gemfile.rails-4.1 +0 -2
  18. data/Gemfile.rails-4.2 +0 -2
  19. data/Gemfile.rails-5.0 +1 -3
  20. data/README.md +5 -4
  21. data/lib/bullet/active_record3.rb +34 -3
  22. data/lib/bullet/active_record3x.rb +34 -3
  23. data/lib/bullet/active_record4.rb +34 -3
  24. data/lib/bullet/active_record41.rb +19 -0
  25. data/lib/bullet/active_record42.rb +37 -0
  26. data/lib/bullet/active_record5.rb +61 -48
  27. data/lib/bullet/dependency.rb +7 -1
  28. data/lib/bullet/detector/n_plus_one_query.rb +1 -15
  29. data/lib/bullet/detector/unused_eager_loading.rb +6 -3
  30. data/lib/bullet/notification/unused_eager_loading.rb +12 -0
  31. data/lib/bullet/rack.rb +2 -2
  32. data/lib/bullet/stack_trace_filter.rb +34 -0
  33. data/lib/bullet/version.rb +1 -1
  34. data/lib/bullet.rb +17 -2
  35. data/spec/bullet/detector/n_plus_one_query_spec.rb +5 -5
  36. data/spec/bullet/detector/unused_eager_loading_spec.rb +3 -1
  37. data/spec/bullet/notification/unused_eager_loading_spec.rb +1 -1
  38. data/spec/bullet/rack_spec.rb +4 -4
  39. data/spec/bullet_spec.rb +43 -0
  40. data/spec/integration/active_record3/association_spec.rb +21 -1
  41. data/spec/integration/active_record4/association_spec.rb +21 -1
  42. data/spec/integration/active_record5/association_spec.rb +21 -1
  43. data/spec/spec_helper.rb +0 -3
  44. data/spec/support/sqlite_seed.rb +1 -0
  45. metadata +3 -2
@@ -20,9 +20,9 @@ module Bullet
20
20
  require 'active_record'
21
21
  ::ActiveRecord::Base.class_eval do
22
22
  class <<self
23
- alias_method :origin_find, :find
24
- def find(*args)
25
- result = origin_find(*args)
23
+ alias_method :origin_find_by_sql, :find_by_sql
24
+ def find_by_sql(sql, binds = [], preparable: nil)
25
+ result = origin_find_by_sql(sql, binds, preparable: nil)
26
26
  if Bullet.start?
27
27
  if result.is_a? Array
28
28
  Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
@@ -40,38 +40,30 @@ module Bullet
40
40
  ::ActiveRecord::Base.prepend(SaveWithBulletSupport)
41
41
 
42
42
  ::ActiveRecord::Relation.class_eval do
43
- alias_method :origin_to_a, :to_a
43
+ alias_method :origin_records, :records
44
44
  # if select a collection of objects, then these objects have possible to cause N+1 query.
45
45
  # if select only one object, then the only one object has impossible to cause N+1 query.
46
- def to_a
47
- records = origin_to_a
46
+ def records
47
+ result = origin_records
48
48
  if Bullet.start?
49
- if records.first.class.name !~ /^HABTM_/
50
- if records.size > 1
51
- Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
52
- Bullet::Detector::CounterCache.add_possible_objects(records)
53
- elsif records.size == 1
54
- Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
55
- Bullet::Detector::CounterCache.add_impossible_object(records.first)
49
+ if result.first.class.name !~ /^HABTM_/
50
+ if result.size > 1
51
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
52
+ Bullet::Detector::CounterCache.add_possible_objects(result)
53
+ elsif result.size == 1
54
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
55
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
56
56
  end
57
57
  end
58
58
  end
59
- records
60
- end
61
- end
62
-
63
- ::ActiveRecord::Associations::Association.class_eval do
64
- alias_method :origin_initialize, :initialize
65
- def initialize(owner, reflection)
66
- origin_initialize(owner, reflection)
67
- reflection.set_owner owner
59
+ result
68
60
  end
69
61
  end
70
62
 
71
63
  ::ActiveRecord::Associations::Preloader.class_eval do
72
- alias_method :origin_preloaders_on, :preloaders_on
64
+ alias_method :origin_preloaders_for_one, :preloaders_for_one
73
65
 
74
- def preloaders_on(association, records, scope)
66
+ def preloaders_for_one(association, records, scope)
75
67
  if Bullet.start?
76
68
  records.compact!
77
69
  if records.first.class.name !~ /^HABTM_/
@@ -81,7 +73,7 @@ module Bullet
81
73
  Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
82
74
  end
83
75
  end
84
- origin_preloaders_on(association, records, scope)
76
+ origin_preloaders_for_one(association, records, scope)
85
77
  end
86
78
  end
87
79
 
@@ -104,6 +96,7 @@ module Bullet
104
96
 
105
97
  ::ActiveRecord::Associations::JoinDependency.class_eval do
106
98
  alias_method :origin_instantiate, :instantiate
99
+ alias_method :origin_construct, :construct
107
100
  alias_method :origin_construct_model, :construct_model
108
101
 
109
102
  def instantiate(result_set, aliases)
@@ -119,6 +112,27 @@ module Bullet
119
112
  records
120
113
  end
121
114
 
115
+ def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
116
+ if Bullet.start?
117
+ unless ar_parent.nil?
118
+ parent.children.each do |node|
119
+ key = aliases.column_alias(node, node.primary_key)
120
+ id = row[key]
121
+ if id.nil?
122
+ associations = node.reflection.name
123
+ Bullet::Detector::Association.add_object_associations(ar_parent, associations)
124
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
125
+ @bullet_eager_loadings[ar_parent.class] ||= {}
126
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
127
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << associations
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ origin_construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
134
+ end
135
+
122
136
  # call join associations
123
137
  def construct_model(record, node, row, model_cache, id, aliases)
124
138
  result = origin_construct_model(record, node, row, model_cache, id, aliases)
@@ -143,7 +157,14 @@ module Bullet
143
157
  records = origin_load_target
144
158
 
145
159
  if Bullet.start?
146
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless @inversed
160
+ if self.is_a? ::ActiveRecord::Associations::ThroughAssociation
161
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
162
+ association = self.owner.association self.through_reflection.name
163
+ Array(association.target).each do |through_record|
164
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
165
+ end
166
+ end
167
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
147
168
  if records.first.class.name !~ /^HABTM_/
148
169
  if records.size > 1
149
170
  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
@@ -159,8 +180,8 @@ module Bullet
159
180
 
160
181
  alias_method :origin_empty?, :empty?
161
182
  def empty?
162
- if Bullet.start? && !@reflection.has_cached_counter?
163
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
183
+ if Bullet.start? && !reflection.has_cached_counter?
184
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
164
185
  end
165
186
  origin_empty?
166
187
  end
@@ -168,7 +189,7 @@ module Bullet
168
189
  alias_method :origin_include?, :include?
169
190
  def include?(object)
170
191
  if Bullet.start?
171
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
192
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
172
193
  end
173
194
  origin_include?(object)
174
195
  end
@@ -180,9 +201,9 @@ module Bullet
180
201
  def reader(force_reload = false)
181
202
  result = origin_reader(force_reload)
182
203
  if Bullet.start?
183
- if @owner.class.name !~ /^HABTM_/ && !@inversed
184
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
185
- if Bullet::Detector::NPlusOneQuery.impossible?(@owner)
204
+ if owner.class.name !~ /^HABTM_/ && !@inversed
205
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
206
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
186
207
  Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
187
208
  else
188
209
  Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
@@ -196,28 +217,20 @@ module Bullet
196
217
  ::ActiveRecord::Associations::HasManyAssociation.class_eval do
197
218
  alias_method :origin_many_empty?, :empty?
198
219
  def empty?
199
- Thread.current[:bullet_collection_empty] = true
200
220
  result = origin_many_empty?
201
- Thread.current[:bullet_collection_empty] = nil
202
- if Bullet.start? && !@reflection.has_cached_counter?
203
- Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
221
+ if Bullet.start? && !reflection.has_cached_counter?
222
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
204
223
  end
205
224
  result
206
225
  end
207
- end
208
-
209
- ::ActiveRecord::Reflection::AbstractReflection.class_eval do
210
- def set_owner(owner)
211
- @owner = owner
212
- end
213
226
 
214
- alias_method :origin_has_cached_counter?, :has_cached_counter?
215
- def has_cached_counter?
216
- result = origin_has_cached_counter?
217
- if Bullet.start? && !result && !Thread.current[:bullet_collection_empty]
218
- Bullet::Detector::CounterCache.add_counter_cache(@owner, @name)
227
+ alias_method :origin_count_records, :count_records
228
+ def count_records
229
+ result = reflection.has_cached_counter?
230
+ if Bullet.start? && !result && !self.is_a?(::ActiveRecord::Associations::ThroughAssociation)
231
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
219
232
  end
220
- result
233
+ origin_count_records
221
234
  end
222
235
  end
223
236
  end
@@ -26,6 +26,8 @@ module Bullet
26
26
  'active_record42'
27
27
  elsif active_record50?
28
28
  'active_record5'
29
+ elsif active_record51?
30
+ 'active_record5'
29
31
  end
30
32
  end
31
33
  end
@@ -84,8 +86,12 @@ module Bullet
84
86
  active_record5? && ::ActiveRecord::VERSION::MINOR == 0
85
87
  end
86
88
 
89
+ def active_record51?
90
+ active_record5? && ::ActiveRecord::VERSION::MINOR == 1
91
+ end
92
+
87
93
  def mongoid2x?
88
- mongoid? && ::Mongoid::VERSION =~ /\A2\.[4-8]/
94
+ mongoid? && ::Mongoid::VERSION =~ /\A2\.[4-9]/
89
95
  end
90
96
 
91
97
  def mongoid3x?
@@ -2,6 +2,7 @@ module Bullet
2
2
  module Detector
3
3
  class NPlusOneQuery < Association
4
4
  extend Dependency
5
+ extend StackTraceFilter
5
6
 
6
7
  class <<self
7
8
  # executed when object.assocations is called.
@@ -87,21 +88,6 @@ module Bullet
87
88
  Bullet.notification_collector.add(notice)
88
89
  end
89
90
  end
90
-
91
- def caller_in_project
92
- app_root = rails? ? Rails.root.to_s : Dir.pwd
93
- vendor_root = app_root + "/vendor"
94
- caller.select do |c|
95
- c.include?(app_root) && !c.include?(vendor_root) ||
96
- Bullet.stacktrace_includes.any? { |include| c.include?(include) }
97
- end
98
- end
99
-
100
- def excluded_stacktrace_path?
101
- Bullet.stacktrace_excludes.any? do |excluded_path|
102
- caller_in_project.any? { |c| c.include?(excluded_path) }
103
- end
104
- end
105
91
  end
106
92
  end
107
93
  end
@@ -1,6 +1,9 @@
1
1
  module Bullet
2
2
  module Detector
3
3
  class UnusedEagerLoading < Association
4
+ extend Dependency
5
+ extend StackTraceFilter
6
+
4
7
  class <<self
5
8
  # check if there are unused preload associations.
6
9
  # get related_objects from eager_loadings associated with object and associations
@@ -15,7 +18,7 @@ module Bullet
15
18
  next if object_association_diff.empty?
16
19
 
17
20
  Bullet.debug("detect unused preload", "object: #{bullet_key}, associations: #{object_association_diff}")
18
- create_notification bullet_key.bullet_class_name, object_association_diff
21
+ create_notification(caller_in_project, bullet_key.bullet_class_name, object_association_diff)
19
22
  end
20
23
  end
21
24
 
@@ -53,11 +56,11 @@ module Bullet
53
56
  end
54
57
 
55
58
  private
56
- def create_notification(klazz, associations)
59
+ def create_notification(callers, klazz, associations)
57
60
  notify_associations = Array(associations) - Bullet.get_whitelist_associations(:unused_eager_loading, klazz)
58
61
 
59
62
  if notify_associations.present?
60
- notice = Bullet::Notification::UnusedEagerLoading.new(klazz, notify_associations)
63
+ notice = Bullet::Notification::UnusedEagerLoading.new(callers, klazz, notify_associations)
61
64
  Bullet.notification_collector.add(notice)
62
65
  end
63
66
  end
@@ -1,6 +1,18 @@
1
1
  module Bullet
2
2
  module Notification
3
3
  class UnusedEagerLoading < Base
4
+ def initialize(callers, base_class, associations, path = nil)
5
+ super(base_class, associations, path)
6
+
7
+ @callers = callers
8
+ end
9
+
10
+ def notification_data
11
+ super.merge(
12
+ :backtrace => @callers
13
+ )
14
+ end
15
+
4
16
  def body
5
17
  "#{klazz_associations_str}\n Remove from your finder: #{associations_str}"
6
18
  end
data/lib/bullet/rack.rb CHANGED
@@ -80,8 +80,8 @@ module Bullet
80
80
  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);
81
81
  -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
82
82
  -moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
83
- padding: 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
84
- color: rgb(119, 119, 119); font-size: 18px; font-family: 'Arial', sans-serif; z-index:9999;"
83
+ padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
84
+ color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
85
85
  EOF
86
86
  end
87
87
  end
@@ -0,0 +1,34 @@
1
+ module Bullet
2
+ module StackTraceFilter
3
+ VENDOR_PATH = "/vendor"
4
+
5
+ def caller_in_project
6
+ app_root = rails? ? Rails.root.to_s : Dir.pwd
7
+ vendor_root = app_root + VENDOR_PATH
8
+ caller.select do |caller_path|
9
+ caller_path.include?(app_root) && !caller_path.include?(vendor_root) ||
10
+ Bullet.stacktrace_includes.any? do |include_pattern|
11
+ case include_pattern
12
+ when String
13
+ caller_path.include?(include_pattern)
14
+ when Regexp
15
+ caller_path =~ include_pattern
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ def excluded_stacktrace_path?
22
+ Bullet.stacktrace_excludes.any? do |exclude_pattern|
23
+ caller_in_project.any? do |caller_path|
24
+ case exclude_pattern
25
+ when String
26
+ caller_path.include?(exclude_pattern)
27
+ when Regexp
28
+ caller_path =~ exclude_pattern
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module Bullet
3
- VERSION = "5.1.0"
3
+ VERSION = "5.4.0"
4
4
  end
data/lib/bullet.rb CHANGED
@@ -4,6 +4,7 @@ require 'uniform_notifier'
4
4
  require 'bullet/ext/object'
5
5
  require 'bullet/ext/string'
6
6
  require 'bullet/dependency'
7
+ require 'bullet/stack_trace_filter'
7
8
 
8
9
  module Bullet
9
10
  extend Dependency
@@ -166,8 +167,9 @@ module Bullet
166
167
  end
167
168
 
168
169
  def perform_out_of_channel_notifications(env = {})
170
+ request_uri = env['REQUEST_URI'] || build_request_uri(env)
169
171
  for_each_active_notifier_with_notification do |notification|
170
- notification.url = env['REQUEST_URI']
172
+ notification.url = request_uri
171
173
  notification.notify_out_of_channel
172
174
  end
173
175
  end
@@ -190,17 +192,22 @@ module Bullet
190
192
  end
191
193
 
192
194
  def profile
195
+ return_value = nil
193
196
  if Bullet.enable?
194
197
  begin
195
198
  Bullet.start_request
196
199
 
197
- yield
200
+ return_value = yield
198
201
 
199
202
  Bullet.perform_out_of_channel_notifications if Bullet.notification?
200
203
  ensure
201
204
  Bullet.end_request
202
205
  end
206
+ else
207
+ return_value = yield
203
208
  end
209
+
210
+ return_value
204
211
  end
205
212
 
206
213
  private
@@ -212,5 +219,13 @@ module Bullet
212
219
  end
213
220
  end
214
221
  end
222
+
223
+ def build_request_uri(env)
224
+ if env['QUERY_STRING'].present?
225
+ "#{env['PATH_INFO']}?#{env['QUERY_STRING']}"
226
+ else
227
+ env['PATH_INFO']
228
+ end
229
+ end
215
230
  end
216
231
  end
@@ -87,7 +87,7 @@ module Bullet
87
87
  end
88
88
 
89
89
  context "stacktrace_excludes" do
90
- before { Bullet.stacktrace_excludes = [ 'def' ] }
90
+ before { Bullet.stacktrace_excludes = [ /def/ ] }
91
91
  after { Bullet.stacktrace_excludes = nil }
92
92
 
93
93
  it "should not create notification when stacktrace contains paths that are in the exclude list" do
@@ -114,17 +114,17 @@ module Bullet
114
114
  end
115
115
 
116
116
  context "stacktrace_includes" do
117
- before { Bullet.stacktrace_includes = [ 'def' ] }
117
+ before { Bullet.stacktrace_includes = [ 'def', /xyz/ ] }
118
118
  after { Bullet.stacktrace_includes = nil }
119
119
 
120
120
  it "should include paths that are in the stacktrace_include list" do
121
121
  in_project = File.join(Dir.pwd, 'abc', 'abc.rb')
122
- included_gem = '/def/def.rb'
122
+ included_gems = ['/def/def.rb', 'xyz/xyz.rb']
123
123
  excluded_gem = '/ghi/ghi.rb'
124
124
 
125
- expect(NPlusOneQuery).to receive(:caller).and_return([in_project, included_gem, excluded_gem])
125
+ expect(NPlusOneQuery).to receive(:caller).and_return([in_project, *included_gems, excluded_gem])
126
126
  expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(true)
127
- expect(NPlusOneQuery).to receive(:create_notification).with([in_project, included_gem], "Post", :association)
127
+ expect(NPlusOneQuery).to receive(:create_notification).with([in_project, *included_gems], "Post", :association)
128
128
  NPlusOneQuery.call_association(@post, :association)
129
129
  end
130
130
  end
@@ -39,9 +39,11 @@ module Bullet
39
39
  end
40
40
 
41
41
  context ".check_unused_preload_associations" do
42
+ let(:paths) { ["/dir1", "/dir1/subdir"] }
42
43
  it "should create notification if object_association_diff is not empty" do
43
44
  UnusedEagerLoading.add_object_associations(@post, :association)
44
- expect(UnusedEagerLoading).to receive(:create_notification).with("Post", [:association])
45
+ allow(UnusedEagerLoading).to receive(:caller_in_project).and_return(paths)
46
+ expect(UnusedEagerLoading).to receive(:create_notification).with(paths, "Post", [:association])
45
47
  UnusedEagerLoading.check_unused_preload_associations
46
48
  end
47
49
 
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  module Bullet
4
4
  module Notification
5
5
  describe UnusedEagerLoading do
6
- subject { UnusedEagerLoading.new(Post, [:comments, :votes], "path") }
6
+ subject { UnusedEagerLoading.new([""], Post, [:comments, :votes], "path") }
7
7
 
8
8
  it { expect(subject.body).to eq(" Post => [:comments, :votes]\n Remove from your finder: :includes => [:comments, :votes]") }
9
9
  it { expect(subject.title).to eq("Unused Eager Loading in path") }
@@ -60,7 +60,7 @@ module Bullet
60
60
  it "should return original response body" do
61
61
  expected_response = Support::ResponseDouble.new "Actual body"
62
62
  app.response = expected_response
63
- _, _, response = middleware.call([])
63
+ _, _, response = middleware.call({})
64
64
  expect(response).to eq(expected_response)
65
65
  end
66
66
 
@@ -68,7 +68,7 @@ module Bullet
68
68
  expect(Bullet).to receive(:notification?).and_return(true)
69
69
  expect(Bullet).to receive(:gather_inline_notifications).and_return("<bullet></bullet>")
70
70
  expect(Bullet).to receive(:perform_out_of_channel_notifications)
71
- status, headers, response = middleware.call([200, {"Content-Type" => "text/html"}])
71
+ status, headers, response = middleware.call({"Content-Type" => "text/html"})
72
72
  expect(headers["Content-Length"]).to eq("56")
73
73
  expect(response).to eq(["<html><head></head><body><bullet></bullet></body></html>"])
74
74
  end
@@ -79,7 +79,7 @@ module Bullet
79
79
  app.response = response
80
80
  expect(Bullet).to receive(:notification?).and_return(true)
81
81
  expect(Bullet).to receive(:gather_inline_notifications).and_return("<bullet></bullet>")
82
- status, headers, response = middleware.call([200, {"Content-Type" => "text/html"}])
82
+ status, headers, response = middleware.call({"Content-Type" => "text/html"})
83
83
  expect(headers["Content-Length"]).to eq("58")
84
84
  end
85
85
  end
@@ -89,7 +89,7 @@ module Bullet
89
89
 
90
90
  it "should not call Bullet.start_request" do
91
91
  expect(Bullet).not_to receive(:start_request)
92
- middleware.call([])
92
+ middleware.call({})
93
93
  end
94
94
  end
95
95
  end
data/spec/bullet_spec.rb CHANGED
@@ -94,4 +94,47 @@ describe Bullet, focused: true do
94
94
  end
95
95
  end
96
96
  end
97
+
98
+ describe '#perform_out_of_channel_notifications' do
99
+ let(:notification) { double }
100
+
101
+ before do
102
+ allow(Bullet).to receive(:for_each_active_notifier_with_notification).and_yield(notification)
103
+ allow(notification).to receive(:notify_out_of_channel)
104
+ end
105
+
106
+ context 'when called with no args' do
107
+ it 'should notification.url is nil' do
108
+ expect(notification).to receive(:url=).with(nil)
109
+ Bullet.perform_out_of_channel_notifications
110
+ end
111
+ end
112
+
113
+ context 'when called with Rack environment hash' do
114
+ let(:env) {
115
+ {
116
+ 'PATH_INFO' => '/path',
117
+ 'QUERY_STRING' => 'foo=bar',
118
+ }
119
+ }
120
+
121
+ context "when env['REQUEST_URI'] is nil" do
122
+ before { env['REQUEST_URI'] = nil }
123
+
124
+ it 'should notification.url is built' do
125
+ expect(notification).to receive(:url=).with('/path?foo=bar')
126
+ Bullet.perform_out_of_channel_notifications(env)
127
+ end
128
+ end
129
+
130
+ context "when env['REQUEST_URI'] is present" do
131
+ before { env['REQUEST_URI'] = 'http://example.com/path' }
132
+
133
+ it "should notification.url is env['REQUEST_URI']" do
134
+ expect(notification).to receive(:url=).with(env['REQUEST_URI'])
135
+ Bullet.perform_out_of_channel_notifications(env)
136
+ end
137
+ end
138
+ end
139
+ end
97
140
  end
@@ -13,6 +13,16 @@ if !mongoid? && active_record3?
13
13
  expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :comments)
14
14
  end
15
15
 
16
+ it "should detect non preload post => comments for find_by_sql" do
17
+ Post.find_by_sql("SELECT * FROM posts").each do |post|
18
+ post.comments.map(&:name)
19
+ end
20
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
21
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
22
+
23
+ expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :comments)
24
+ end
25
+
16
26
  it "should detect preload with post => comments" do
17
27
  Post.includes(:comments).each do |post|
18
28
  post.comments.map(&:name)
@@ -214,7 +224,7 @@ if !mongoid? && active_record3?
214
224
  context "post => comment" do
215
225
  it "should detect unused preload with post => comments" do
216
226
  Post.includes(:comments).each do |post|
217
- post.comments.first.name
227
+ post.comments.first.name if post.comments.first
218
228
  end
219
229
  Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
220
230
  expect(Bullet::Detector::Association).not_to be_unused_preload_associations_for(Post, :comments)
@@ -453,6 +463,16 @@ if !mongoid? && active_record3?
453
463
 
454
464
  expect(Bullet::Detector::Association).to be_completely_preloading_associations
455
465
  end
466
+
467
+ it "should detect non preload student => teachers with empty?" do
468
+ Student.all.each do |student|
469
+ student.teachers.empty?
470
+ end
471
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
472
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
473
+
474
+ expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Student, :teachers)
475
+ end
456
476
  end
457
477
  end
458
478
 
@@ -13,6 +13,16 @@ if !mongoid? && active_record4?
13
13
  expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :comments)
14
14
  end
15
15
 
16
+ it "should detect non preload post => comments for find_by_sql" do
17
+ Post.find_by_sql("SELECT * FROM posts").each do |post|
18
+ post.comments.map(&:name)
19
+ end
20
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
21
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
22
+
23
+ expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Post, :comments)
24
+ end
25
+
16
26
  it "should detect preload with post => comments" do
17
27
  Post.includes(:comments).each do |post|
18
28
  post.comments.map(&:name)
@@ -214,7 +224,7 @@ if !mongoid? && active_record4?
214
224
  context "post => comment" do
215
225
  it "should detect unused preload with post => comments" do
216
226
  Post.includes(:comments).each do |post|
217
- post.comments.first.name
227
+ post.comments.first.name if post.comments.first
218
228
  end
219
229
  Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
220
230
  expect(Bullet::Detector::Association).not_to be_unused_preload_associations_for(Post, :comments)
@@ -465,6 +475,16 @@ if !mongoid? && active_record4?
465
475
 
466
476
  expect(Bullet::Detector::Association).to be_completely_preloading_associations
467
477
  end
478
+
479
+ it "should detect non preload student => teachers with empty?" do
480
+ Student.all.each do |student|
481
+ student.teachers.empty?
482
+ end
483
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
484
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
485
+
486
+ expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Student, :teachers)
487
+ end
468
488
  end
469
489
  end
470
490