bullet 4.13.1 → 5.5.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +7 -11
  4. data/CHANGELOG.md +62 -3
  5. data/Gemfile.mongoid +0 -2
  6. data/Gemfile.mongoid-4.0 +1 -5
  7. data/{Gemfile.mongoid-2.8 → Gemfile.mongoid-5.0} +2 -6
  8. data/{Gemfile.mongoid-2.5 → Gemfile.mongoid-6.0} +2 -6
  9. data/Gemfile.rails-4.0 +2 -5
  10. data/Gemfile.rails-4.1 +2 -5
  11. data/{Gemfile.rails-3.0 → Gemfile.rails-4.2} +3 -6
  12. data/{Gemfile.rails-3.1 → Gemfile.rails-5.0} +2 -6
  13. data/Hacking.md +1 -0
  14. data/README.md +39 -7
  15. data/bullet.gemspec +1 -1
  16. data/lib/bullet/active_record4.rb +109 -33
  17. data/lib/bullet/active_record41.rb +99 -37
  18. data/lib/bullet/active_record42.rb +251 -0
  19. data/lib/bullet/active_record5.rb +243 -0
  20. data/lib/bullet/dependency.rb +34 -28
  21. data/lib/bullet/detector/association.rb +16 -16
  22. data/lib/bullet/detector/counter_cache.rb +13 -13
  23. data/lib/bullet/detector/n_plus_one_query.rb +30 -35
  24. data/lib/bullet/detector/unused_eager_loading.rb +12 -11
  25. data/lib/bullet/ext/object.rb +4 -2
  26. data/lib/bullet/{mongoid3x.rb → mongoid5x.rb} +3 -3
  27. data/lib/bullet/{mongoid2x.rb → mongoid6x.rb} +12 -12
  28. data/lib/bullet/notification/base.rb +9 -13
  29. data/lib/bullet/notification/n_plus_one_query.rb +8 -6
  30. data/lib/bullet/notification/unused_eager_loading.rb +18 -1
  31. data/lib/bullet/rack.rb +28 -16
  32. data/lib/bullet/stack_trace_filter.rb +34 -0
  33. data/lib/bullet/version.rb +1 -1
  34. data/lib/bullet.rb +48 -14
  35. data/spec/bullet/detector/counter_cache_spec.rb +8 -8
  36. data/spec/bullet/detector/n_plus_one_query_spec.rb +46 -31
  37. data/spec/bullet/detector/unused_eager_loading_spec.rb +17 -3
  38. data/spec/bullet/ext/object_spec.rb +6 -0
  39. data/spec/bullet/notification/base_spec.rb +4 -29
  40. data/spec/bullet/notification/n_plus_one_query_spec.rb +4 -4
  41. data/spec/bullet/notification/unused_eager_loading_spec.rb +3 -3
  42. data/spec/bullet/rack_spec.rb +38 -7
  43. data/spec/bullet_spec.rb +93 -0
  44. data/spec/integration/active_record4/association_spec.rb +112 -4
  45. data/spec/integration/{active_record3 → active_record5}/association_spec.rb +121 -15
  46. data/spec/integration/counter_cache_spec.rb +24 -1
  47. data/spec/models/category.rb +4 -1
  48. data/spec/models/comment.rb +2 -0
  49. data/spec/models/post.rb +7 -2
  50. data/spec/models/reply.rb +3 -0
  51. data/spec/models/submission.rb +1 -1
  52. data/spec/spec_helper.rb +3 -5
  53. data/spec/support/mongo_seed.rb +13 -0
  54. data/spec/support/sqlite_seed.rb +15 -6
  55. data/test.sh +4 -10
  56. data/update.sh +7 -0
  57. metadata +21 -23
  58. data/Gemfile.mongoid-2.4 +0 -19
  59. data/Gemfile.mongoid-2.6 +0 -19
  60. data/Gemfile.mongoid-2.7 +0 -19
  61. data/Gemfile.mongoid-3.0 +0 -19
  62. data/Gemfile.mongoid-3.1 +0 -19
  63. data/Gemfile.rails-3.2 +0 -19
  64. data/lib/bullet/active_record3.rb +0 -154
  65. data/lib/bullet/active_record3x.rb +0 -134
@@ -2,11 +2,10 @@ module Bullet
2
2
  module Mongoid
3
3
  def self.enable
4
4
  require 'mongoid'
5
-
6
- ::Mongoid::Contexts::Mongo.class_eval do
5
+ ::Mongoid::Contextual::Mongo.class_eval do
7
6
  alias_method :origin_first, :first
8
7
  alias_method :origin_last, :last
9
- alias_method :origin_iterate, :iterate
8
+ alias_method :origin_each, :each
10
9
  alias_method :origin_eager_load, :eager_load
11
10
 
12
11
  def first
@@ -21,20 +20,20 @@ module Bullet
21
20
  result
22
21
  end
23
22
 
24
- def iterate(&block)
25
- records = execute.to_a
26
- if records.size > 1
23
+ def each(&block)
24
+ records = view.map{ |doc| ::Mongoid::Factory.from_db(klass, doc) }
25
+ if records.length > 1
27
26
  Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
28
27
  elsif records.size == 1
29
28
  Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
30
29
  end
31
- origin_iterate(&block)
30
+ origin_each(&block)
32
31
  end
33
32
 
34
33
  def eager_load(docs)
35
34
  associations = criteria.inclusions.map(&:name)
36
35
  docs.each do |doc|
37
- Bullet::Detector::Association.add_object_associations(doc, associations)
36
+ Bullet::Detector::NPlusOneQuery.add_object_associations(doc, associations)
38
37
  end
39
38
  Bullet::Detector::UnusedEagerLoading.add_eager_loadings(docs, associations)
40
39
  origin_eager_load(docs)
@@ -42,13 +41,14 @@ module Bullet
42
41
  end
43
42
 
44
43
  ::Mongoid::Relations::Accessors.class_eval do
45
- alias_method :origin_set_relation, :set_relation
44
+ alias_method :origin_get_relation, :get_relation
46
45
 
47
- def set_relation(name, relation)
48
- if relation && relation.metadata.macro !~ /embed/
46
+ def get_relation(name, metadata, object, reload = false)
47
+ result = origin_get_relation(name, metadata, object, reload)
48
+ if metadata.macro !~ /embed/
49
49
  Bullet::Detector::NPlusOneQuery.call_association(self, name)
50
50
  end
51
- origin_set_relation(name, relation)
51
+ result
52
52
  end
53
53
  end
54
54
  end
@@ -18,6 +18,10 @@ module Bullet
18
18
  raise NoMethodError.new("no method body defined")
19
19
  end
20
20
 
21
+ def call_stack_messages
22
+ ""
23
+ end
24
+
21
25
  def whoami
22
26
  @user ||= ENV['USER'].presence || (`whoami`.chomp rescue "")
23
27
  if @user.present?
@@ -28,15 +32,7 @@ module Bullet
28
32
  end
29
33
 
30
34
  def body_with_caller
31
- body
32
- end
33
-
34
- def standard_notice
35
- @standard_notifice ||= title + "\n" + body
36
- end
37
-
38
- def full_notice
39
- [whoami.presence, url, title, body_with_caller].compact.join("\n")
35
+ "#{body}\n#{call_stack_messages}\n"
40
36
  end
41
37
 
42
38
  def notify_inline
@@ -48,7 +44,7 @@ module Bullet
48
44
  end
49
45
 
50
46
  def short_notice
51
- [whoami.presence, url, title, body].compact.join("\n")
47
+ [whoami.presence, url, title, body].compact.join(" ")
52
48
  end
53
49
 
54
50
  def notification_data
@@ -61,11 +57,11 @@ module Bullet
61
57
  end
62
58
 
63
59
  def eql?(other)
64
- klazz_associations_str == other.klazz_associations_str
60
+ self.class == other.class && klazz_associations_str == other.klazz_associations_str
65
61
  end
66
62
 
67
63
  def hash
68
- klazz_associations_str.hash
64
+ [self.class, klazz_associations_str].hash
69
65
  end
70
66
 
71
67
  protected
@@ -74,7 +70,7 @@ module Bullet
74
70
  end
75
71
 
76
72
  def associations_str
77
- ":include => #{@associations.map{ |a| a.to_s.to_sym unless a.is_a? Hash }.inspect}"
73
+ ":includes => #{@associations.map{ |a| a.to_s.to_sym unless a.is_a? Hash }.inspect}"
78
74
  end
79
75
  end
80
76
  end
@@ -7,21 +7,23 @@ module Bullet
7
7
  @callers = callers
8
8
  end
9
9
 
10
- def body_with_caller
11
- "#{body}\n#{call_stack_messages}"
12
- end
13
-
14
10
  def body
15
11
  "#{klazz_associations_str}\n Add to your finder: #{associations_str}"
16
12
  end
17
13
 
18
14
  def title
19
- "N+1 Query #{@path ? "in #{@path}" : 'detected'}"
15
+ "USE eager loading #{@path ? "in #{@path}" : 'detected'}"
16
+ end
17
+
18
+ def notification_data
19
+ super.merge(
20
+ :backtrace => []
21
+ )
20
22
  end
21
23
 
22
24
  protected
23
25
  def call_stack_messages
24
- (['N+1 Query method call stack'] + @callers).join( "\n " )
26
+ (['Call stack'] + @callers).join( "\n " )
25
27
  end
26
28
  end
27
29
  end
@@ -1,13 +1,30 @@
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
+
4
10
  def body
5
11
  "#{klazz_associations_str}\n Remove from your finder: #{associations_str}"
6
12
  end
7
13
 
8
14
  def title
9
- "Unused Eager Loading #{@path ? "in #{@path}" : 'detected'}"
15
+ "AVOID eager loading #{@path ? "in #{@path}" : 'detected'}"
10
16
  end
17
+
18
+ def notification_data
19
+ super.merge(
20
+ :backtrace => []
21
+ )
22
+ end
23
+
24
+ protected
25
+ def call_stack_messages
26
+ (['Call stack'] + @callers).join( "\n " )
27
+ end
11
28
  end
12
29
  end
13
30
  end
data/lib/bullet/rack.rb CHANGED
@@ -10,17 +10,16 @@ module Bullet
10
10
  return @app.call(env) unless Bullet.enable?
11
11
  Bullet.start_request
12
12
  status, headers, response = @app.call(env)
13
- return [status, headers, response] if file?(headers) || sse?(response) || empty?(response)
14
13
 
15
14
  response_body = nil
16
15
  if Bullet.notification?
17
- if status == 200 && !response_body(response).frozen? && html_request?(headers, response)
18
- response_body = response_body(response) << Bullet.gather_inline_notifications
19
- add_footer_note(response_body) if Bullet.add_footer
16
+ if !file?(headers) && !sse?(headers) && !empty?(response) &&
17
+ status == 200 && !response_body(response).frozen? && html_request?(headers, response)
18
+ 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)
20
21
  headers['Content-Length'] = response_body.bytesize.to_s
21
22
  end
22
- end
23
- if Bullet.enable? && Bullet.notification?
24
23
  Bullet.perform_out_of_channel_notifications(env)
25
24
  end
26
25
  [status, headers, response_body ? [response_body] : response]
@@ -42,16 +41,25 @@ module Bullet
42
41
  end
43
42
  end
44
43
 
45
- def add_footer_note(response_body)
46
- response_body << "<div #{footer_div_style}>" + Bullet.footer_info.uniq.join("<br>") + "</div>"
44
+ 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
+ else
49
+ response_body << content
50
+ end
51
+ end
52
+
53
+ def footer_note
54
+ "<div #{footer_div_attributes}>" + footer_close_button + Bullet.footer_info.uniq.join("<br>") + "</div>"
47
55
  end
48
56
 
49
57
  def file?(headers)
50
58
  headers["Content-Transfer-Encoding"] == "binary"
51
59
  end
52
60
 
53
- def sse?(response)
54
- response.respond_to?(:stream) && response.stream.is_a?(ActionController::Live::Buffer)
61
+ def sse?(headers)
62
+ headers["Content-Type"] == "text/event-stream"
55
63
  end
56
64
 
57
65
  def html_request?(headers, response)
@@ -59,7 +67,7 @@ module Bullet
59
67
  end
60
68
 
61
69
  def response_body(response)
62
- if rails?
70
+ if response.respond_to?(:body)
63
71
  Array === response.body ? response.body.first : response.body
64
72
  else
65
73
  response.first
@@ -67,15 +75,19 @@ module Bullet
67
75
  end
68
76
 
69
77
  private
70
- def footer_div_style
78
+
79
+ def footer_div_attributes
71
80
  <<EOF
72
- style="position: fixed; bottom: 0pt; left: 0pt; cursor: pointer; border-style: solid; border-color: rgb(153, 153, 153);
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);
73
82
  -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
74
83
  -moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
75
- padding: 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
76
- color: rgb(119, 119, 119); font-size: 18px; font-family: 'Arial', sans-serif; z-index:9999;"
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;"
77
86
  EOF
78
87
  end
88
+
89
+ def footer_close_button
90
+ "<span onclick='this.parentNode.remove()' style='position:absolute; right: 10px; top: 0px; font-weight: bold; color: #333;'>&times;</span>"
91
+ end
79
92
  end
80
93
  end
81
-
@@ -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 = "4.13.1"
3
+ VERSION = "5.5.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
@@ -16,6 +17,9 @@ module Bullet
16
17
  autoload :Registry, 'bullet/registry'
17
18
  autoload :NotificationCollector, 'bullet/notification_collector'
18
19
 
20
+ BULLET_DEBUG = 'BULLET_DEBUG'.freeze
21
+ TRUE = 'true'.freeze
22
+
19
23
  if defined? Rails::Railtie
20
24
  class BulletRailtie < Rails::Railtie
21
25
  initializer "bullet.configure_rails_initialization" do |app|
@@ -25,11 +29,13 @@ module Bullet
25
29
  end
26
30
 
27
31
  class << self
28
- attr_writer :enable, :n_plus_one_query_enable, :unused_eager_loading_enable, :counter_cache_enable, :stacktrace_includes
32
+ attr_writer :enable, :n_plus_one_query_enable, :unused_eager_loading_enable, :counter_cache_enable, :stacktrace_includes, :stacktrace_excludes
29
33
  attr_reader :notification_collector, :whitelist
30
34
  attr_accessor :add_footer, :orm_pathches_applied
31
35
 
32
- delegate :alert=, :console=, :growl=, :rails_logger=, :xmpp=, :airbrake=, :bugsnag=, :to => UniformNotifier
36
+ available_notifiers = UniformNotifier::AVAILABLE_NOTIFIERS.map { |notifier| "#{notifier}=" }
37
+ available_notifiers << { :to => UniformNotifier }
38
+ delegate *available_notifiers
33
39
 
34
40
  def raise=(should_raise)
35
41
  UniformNotifier.raise=(should_raise ? Notification::UnoptimizedQueryError : false)
@@ -71,9 +77,14 @@ module Bullet
71
77
  @stacktrace_includes || []
72
78
  end
73
79
 
80
+ def stacktrace_excludes
81
+ @stacktrace_excludes || []
82
+ end
83
+
74
84
  def add_whitelist(options)
75
- @whitelist[options[:type]][options[:class_name].classify] ||= []
76
- @whitelist[options[:type]][options[:class_name].classify] << options[:association].to_sym
85
+ reset_whitelist
86
+ @whitelist[options[:type]][options[:class_name]] ||= []
87
+ @whitelist[options[:type]][options[:class_name]] << options[:association].to_sym
77
88
  end
78
89
 
79
90
  def get_whitelist_associations(type, class_name)
@@ -81,7 +92,11 @@ module Bullet
81
92
  end
82
93
 
83
94
  def reset_whitelist
84
- @whitelist = {:n_plus_one_query => {}, :unused_eager_loading => {}, :counter_cache => {}}
95
+ @whitelist ||= {:n_plus_one_query => {}, :unused_eager_loading => {}, :counter_cache => {}}
96
+ end
97
+
98
+ def clear_whitelist
99
+ @whitelist = nil
85
100
  end
86
101
 
87
102
  def bullet_logger=(active)
@@ -96,7 +111,7 @@ module Bullet
96
111
  end
97
112
 
98
113
  def debug(title, message)
99
- puts "[Bullet][#{title}] #{message}" if ENV['BULLET_DEBUG'] == 'true'
114
+ puts "[Bullet][#{title}] #{message}" if ENV[BULLET_DEBUG] == TRUE
100
115
  end
101
116
 
102
117
  def start_request
@@ -130,7 +145,7 @@ module Bullet
130
145
  end
131
146
 
132
147
  def start?
133
- Thread.current[:bullet_start]
148
+ enable? && Thread.current[:bullet_start]
134
149
  end
135
150
 
136
151
  def notification_collector
@@ -152,15 +167,16 @@ module Bullet
152
167
  end
153
168
 
154
169
  def perform_out_of_channel_notifications(env = {})
170
+ request_uri = build_request_uri(env)
155
171
  for_each_active_notifier_with_notification do |notification|
156
- notification.url = [env['HTTP_HOST'], env['REQUEST_URI']].compact.join
172
+ notification.url = request_uri
157
173
  notification.notify_out_of_channel
158
174
  end
159
175
  end
160
176
 
161
177
  def footer_info
162
178
  info = []
163
- for_each_active_notifier_with_notification do |notification|
179
+ notification_collector.collection.each do |notification|
164
180
  info << notification.short_notice
165
181
  end
166
182
  info
@@ -176,14 +192,22 @@ module Bullet
176
192
  end
177
193
 
178
194
  def profile
179
- Bullet.start_request if Bullet.enable?
195
+ return_value = nil
196
+ if Bullet.enable?
197
+ begin
198
+ Bullet.start_request
180
199
 
181
- yield
200
+ return_value = yield
182
201
 
183
- if Bullet.enable? && Bullet.notification?
184
- Bullet.perform_out_of_channel_notifications
202
+ Bullet.perform_out_of_channel_notifications if Bullet.notification?
203
+ ensure
204
+ Bullet.end_request
205
+ end
206
+ else
207
+ return_value = yield
185
208
  end
186
- Bullet.end_request if Bullet.enable?
209
+
210
+ return_value
187
211
  end
188
212
 
189
213
  private
@@ -195,5 +219,15 @@ module Bullet
195
219
  end
196
220
  end
197
221
  end
222
+
223
+ def build_request_uri(env)
224
+ return "#{env['REQUEST_METHOD']} #{env['REQUEST_URI']}" if env['REQUEST_URI']
225
+
226
+ if env['QUERY_STRING'].present?
227
+ "#{env['REQUEST_METHOD']} #{env['PATH_INFO']}?#{env['QUERY_STRING']}"
228
+ else
229
+ "#{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
230
+ end
231
+ end
198
232
  end
199
233
  end
@@ -10,13 +10,13 @@ module Bullet
10
10
 
11
11
  context ".add_counter_cache" do
12
12
  it "should create notification if conditions met" do
13
- expect(CounterCache).to receive(:conditions_met?).with(@post1.bullet_key, [:comments]).and_return(true)
13
+ expect(CounterCache).to receive(:conditions_met?).with(@post1, [:comments]).and_return(true)
14
14
  expect(CounterCache).to receive(:create_notification).with("Post", [:comments])
15
15
  CounterCache.add_counter_cache(@post1, [:comments])
16
16
  end
17
17
 
18
18
  it "should not create notification if conditions not met" do
19
- expect(CounterCache).to receive(:conditions_met?).with(@post1.bullet_key, [:comments]).and_return(false)
19
+ expect(CounterCache).to receive(:conditions_met?).with(@post1, [:comments]).and_return(false)
20
20
  expect(CounterCache).to receive(:create_notification).never
21
21
  CounterCache.add_counter_cache(@post1, [:comments])
22
22
  end
@@ -25,30 +25,30 @@ module Bullet
25
25
  context ".add_possible_objects" do
26
26
  it "should add possible objects" do
27
27
  CounterCache.add_possible_objects([@post1, @post2])
28
- expect(CounterCache.send(:possible_objects)).to be_include(@post1.bullet_key)
29
- expect(CounterCache.send(:possible_objects)).to be_include(@post2.bullet_key)
28
+ expect(CounterCache.possible_objects).to be_include(@post1.bullet_key)
29
+ expect(CounterCache.possible_objects).to be_include(@post2.bullet_key)
30
30
  end
31
31
 
32
32
  it "should add impossible object" do
33
33
  CounterCache.add_impossible_object(@post1)
34
- expect(CounterCache.send(:impossible_objects)).to be_include(@post1.bullet_key)
34
+ expect(CounterCache.impossible_objects).to be_include(@post1.bullet_key)
35
35
  end
36
36
  end
37
37
 
38
38
  context ".conditions_met?" do
39
39
  it "should be true when object is possible, not impossible" do
40
40
  CounterCache.add_possible_objects(@post1)
41
- expect(CounterCache.send(:conditions_met?, @post1.bullet_key, :associations)).to eq true
41
+ expect(CounterCache.conditions_met?(@post1, :associations)).to eq true
42
42
  end
43
43
 
44
44
  it "should be false when object is not possible" do
45
- expect(CounterCache.send(:conditions_met?, @post1.bullet_key, :associations)).to eq false
45
+ expect(CounterCache.conditions_met?(@post1, :associations)).to eq false
46
46
  end
47
47
 
48
48
  it "should be true when object is possible, and impossible" do
49
49
  CounterCache.add_possible_objects(@post1)
50
50
  CounterCache.add_impossible_object(@post1)
51
- expect(CounterCache.send(:conditions_met?, @post1.bullet_key, :associations)).to eq false
51
+ expect(CounterCache.conditions_met?(@post1, :associations)).to eq false
52
52
  end
53
53
  end
54
54
  end
@@ -18,73 +18,88 @@ module Bullet
18
18
  context ".possible?" do
19
19
  it "should be true if possible_objects contain" do
20
20
  NPlusOneQuery.add_possible_objects(@post)
21
- expect(NPlusOneQuery.send(:possible?, @post.bullet_key)).to eq true
21
+ expect(NPlusOneQuery.possible?(@post)).to eq true
22
22
  end
23
23
  end
24
24
 
25
25
  context ".impossible?" do
26
26
  it "should be true if impossible_objects contain" do
27
27
  NPlusOneQuery.add_impossible_object(@post)
28
- expect(NPlusOneQuery.send(:impossible?, @post.bullet_key)).to eq true
28
+ expect(NPlusOneQuery.impossible?(@post)).to eq true
29
29
  end
30
30
  end
31
31
 
32
32
  context ".association?" do
33
33
  it "should be true if object, associations pair is already existed" do
34
34
  NPlusOneQuery.add_object_associations(@post, :association)
35
- expect(NPlusOneQuery.send(:association?, @post.bullet_key, :association)).to eq true
35
+ expect(NPlusOneQuery.association?(@post, :association)).to eq true
36
36
  end
37
37
 
38
38
  it "should be false if object, association pair is not existed" do
39
39
  NPlusOneQuery.add_object_associations(@post, :association1)
40
- expect(NPlusOneQuery.send(:association?, @post.bullet_key, :associatio2)).to eq false
40
+ expect(NPlusOneQuery.association?(@post, :associatio2)).to eq false
41
41
  end
42
42
  end
43
43
 
44
44
  context ".conditions_met?" do
45
45
  it "should be true if object is possible, not impossible and object, associations pair is not already existed" do
46
- allow(NPlusOneQuery).to receive(:possible?).with(@post.bullet_key).and_return(true)
47
- allow(NPlusOneQuery).to receive(:impossible?).with(@post.bullet_key).and_return(false)
48
- allow(NPlusOneQuery).to receive(:association?).with(@post.bullet_key, :associations).and_return(false)
49
- expect(NPlusOneQuery.send(:conditions_met?, @post.bullet_key, :associations)).to eq true
46
+ allow(NPlusOneQuery).to receive(:possible?).with(@post).and_return(true)
47
+ allow(NPlusOneQuery).to receive(:impossible?).with(@post).and_return(false)
48
+ allow(NPlusOneQuery).to receive(:association?).with(@post, :associations).and_return(false)
49
+ expect(NPlusOneQuery.conditions_met?(@post, :associations)).to eq true
50
50
  end
51
51
 
52
52
  it "should be false if object is not possible, not impossible and object, associations pair is not already existed" do
53
- allow(NPlusOneQuery).to receive(:possible?).with(@post.bullet_key).and_return(false)
54
- allow(NPlusOneQuery).to receive(:impossible?).with(@post.bullet_key).and_return(false)
55
- allow(NPlusOneQuery).to receive(:association?).with(@post.bullet_key, :associations).and_return(false)
56
- expect(NPlusOneQuery.send(:conditions_met?, @post.bullet_key, :associations)).to eq false
53
+ allow(NPlusOneQuery).to receive(:possible?).with(@post).and_return(false)
54
+ allow(NPlusOneQuery).to receive(:impossible?).with(@post).and_return(false)
55
+ allow(NPlusOneQuery).to receive(:association?).with(@post, :associations).and_return(false)
56
+ expect(NPlusOneQuery.conditions_met?(@post, :associations)).to eq false
57
57
  end
58
58
 
59
59
  it "should be false if object is possible, but impossible and object, associations pair is not already existed" do
60
- allow(NPlusOneQuery).to receive(:possible?).with(@post.bullet_key).and_return(true)
61
- allow(NPlusOneQuery).to receive(:impossible?).with(@post.bullet_key).and_return(true)
62
- allow(NPlusOneQuery).to receive(:association?).with(@post.bullet_key, :associations).and_return(false)
63
- expect(NPlusOneQuery.send(:conditions_met?, @post.bullet_key, :associations)).to eq false
60
+ allow(NPlusOneQuery).to receive(:possible?).with(@post).and_return(true)
61
+ allow(NPlusOneQuery).to receive(:impossible?).with(@post).and_return(true)
62
+ allow(NPlusOneQuery).to receive(:association?).with(@post, :associations).and_return(false)
63
+ expect(NPlusOneQuery.conditions_met?(@post, :associations)).to eq false
64
64
  end
65
65
 
66
66
  it "should be false if object is possible, not impossible and object, associations pair is already existed" do
67
- allow(NPlusOneQuery).to receive(:possible?).with(@post.bullet_key).and_return(true)
68
- allow(NPlusOneQuery).to receive(:impossible?).with(@post.bullet_key).and_return(false)
69
- allow(NPlusOneQuery).to receive(:association?).with(@post.bullet_key, :associations).and_return(true)
70
- expect(NPlusOneQuery.send(:conditions_met?, @post.bullet_key, :associations)).to eq false
67
+ allow(NPlusOneQuery).to receive(:possible?).with(@post).and_return(true)
68
+ allow(NPlusOneQuery).to receive(:impossible?).with(@post).and_return(false)
69
+ allow(NPlusOneQuery).to receive(:association?).with(@post, :associations).and_return(true)
70
+ expect(NPlusOneQuery.conditions_met?(@post, :associations)).to eq false
71
71
  end
72
72
  end
73
73
 
74
74
  context ".call_association" do
75
75
  it "should create notification if conditions met" do
76
- expect(NPlusOneQuery).to receive(:conditions_met?).with(@post.bullet_key, :association).and_return(true)
76
+ expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(true)
77
77
  expect(NPlusOneQuery).to receive(:caller_in_project).and_return(["caller"])
78
78
  expect(NPlusOneQuery).to receive(:create_notification).with(["caller"], "Post", :association)
79
79
  NPlusOneQuery.call_association(@post, :association)
80
80
  end
81
81
 
82
82
  it "should not create notification if conditions not met" do
83
- expect(NPlusOneQuery).to receive(:conditions_met?).with(@post.bullet_key, :association).and_return(false)
83
+ expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(false)
84
84
  expect(NPlusOneQuery).not_to receive(:caller_in_project!)
85
85
  expect(NPlusOneQuery).not_to receive(:create_notification).with("Post", :association)
86
86
  NPlusOneQuery.call_association(@post, :association)
87
87
  end
88
+
89
+ context "stacktrace_excludes" do
90
+ before { Bullet.stacktrace_excludes = [ /def/ ] }
91
+ after { Bullet.stacktrace_excludes = nil }
92
+
93
+ it "should not create notification when stacktrace contains paths that are in the exclude list" do
94
+ in_project = File.join(Dir.pwd, 'abc', 'abc.rb')
95
+ included_path = '/ghi/ghi.rb'
96
+ excluded_path = '/def/def.rb'
97
+
98
+ expect(NPlusOneQuery).to receive(:caller).and_return([in_project, included_path, excluded_path])
99
+ expect(NPlusOneQuery).to_not receive(:create_notification)
100
+ NPlusOneQuery.call_association(@post, :association)
101
+ end
102
+ end
88
103
  end
89
104
 
90
105
  context ".caller_in_project" do
@@ -93,23 +108,23 @@ module Bullet
93
108
  not_in_project = '/def/def.rb'
94
109
 
95
110
  expect(NPlusOneQuery).to receive(:caller).and_return([in_project, not_in_project])
96
- expect(NPlusOneQuery).to receive(:conditions_met?).with(@post.bullet_key, :association).and_return(true)
111
+ expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(true)
97
112
  expect(NPlusOneQuery).to receive(:create_notification).with([in_project], "Post", :association)
98
113
  NPlusOneQuery.call_association(@post, :association)
99
114
  end
100
115
 
101
116
  context "stacktrace_includes" do
102
- before { Bullet.stacktrace_includes = [ 'def' ] }
117
+ before { Bullet.stacktrace_includes = [ 'def', /xyz/ ] }
103
118
  after { Bullet.stacktrace_includes = nil }
104
119
 
105
120
  it "should include paths that are in the stacktrace_include list" do
106
121
  in_project = File.join(Dir.pwd, 'abc', 'abc.rb')
107
- included_gem = '/def/def.rb'
122
+ included_gems = ['/def/def.rb', 'xyz/xyz.rb']
108
123
  excluded_gem = '/ghi/ghi.rb'
109
124
 
110
- expect(NPlusOneQuery).to receive(:caller).and_return([in_project, included_gem, excluded_gem])
111
- expect(NPlusOneQuery).to receive(:conditions_met?).with(@post.bullet_key, :association).and_return(true)
112
- expect(NPlusOneQuery).to receive(:create_notification).with([in_project, included_gem], "Post", :association)
125
+ expect(NPlusOneQuery).to receive(:caller).and_return([in_project, *included_gems, excluded_gem])
126
+ expect(NPlusOneQuery).to receive(:conditions_met?).with(@post, :association).and_return(true)
127
+ expect(NPlusOneQuery).to receive(:create_notification).with([in_project, *included_gems], "Post", :association)
113
128
  NPlusOneQuery.call_association(@post, :association)
114
129
  end
115
130
  end
@@ -118,8 +133,8 @@ module Bullet
118
133
  context ".add_possible_objects" do
119
134
  it "should add possible objects" do
120
135
  NPlusOneQuery.add_possible_objects([@post, @post2])
121
- expect(NPlusOneQuery.send(:possible_objects)).to be_include(@post.bullet_key)
122
- expect(NPlusOneQuery.send(:possible_objects)).to be_include(@post2.bullet_key)
136
+ expect(NPlusOneQuery.possible_objects).to be_include(@post.bullet_key)
137
+ expect(NPlusOneQuery.possible_objects).to be_include(@post2.bullet_key)
123
138
  end
124
139
 
125
140
  it "should not raise error if object is nil" do
@@ -130,7 +145,7 @@ module Bullet
130
145
  context ".add_impossible_object" do
131
146
  it "should add impossible object" do
132
147
  NPlusOneQuery.add_impossible_object(@post)
133
- expect(NPlusOneQuery.send(:impossible_objects)).to be_include(@post.bullet_key)
148
+ expect(NPlusOneQuery.impossible_objects).to be_include(@post.bullet_key)
134
149
  end
135
150
  end
136
151
  end