bullet 4.0.0 → 4.13.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 (112) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +1 -1
  4. data/.travis.yml +17 -6
  5. data/CHANGELOG.md +86 -0
  6. data/Gemfile +13 -12
  7. data/Gemfile.mongoid +14 -0
  8. data/Gemfile.mongoid-2.4 +19 -0
  9. data/Gemfile.mongoid-2.5 +19 -0
  10. data/Gemfile.mongoid-2.6 +19 -0
  11. data/Gemfile.mongoid-2.7 +19 -0
  12. data/Gemfile.mongoid-2.8 +19 -0
  13. data/Gemfile.mongoid-3.0 +19 -0
  14. data/Gemfile.mongoid-3.1 +19 -0
  15. data/Gemfile.mongoid-4.0 +19 -0
  16. data/Gemfile.rails-3.0 +19 -0
  17. data/Gemfile.rails-3.1 +19 -0
  18. data/Gemfile.rails-3.2 +19 -0
  19. data/Gemfile.rails-4.0 +19 -0
  20. data/Gemfile.rails-4.1 +19 -0
  21. data/{Hacking.textile → Hacking.md} +10 -5
  22. data/README.md +432 -0
  23. data/Rakefile +13 -6
  24. data/bullet.gemspec +6 -3
  25. data/lib/bullet/active_record3.rb +49 -11
  26. data/lib/bullet/active_record3x.rb +134 -0
  27. data/lib/bullet/{active_record31.rb → active_record4.rb} +45 -15
  28. data/lib/bullet/active_record41.rb +128 -0
  29. data/lib/bullet/dependency.rb +81 -0
  30. data/lib/bullet/detector/association.rb +24 -68
  31. data/lib/bullet/detector/base.rb +0 -6
  32. data/lib/bullet/detector/counter_cache.rb +59 -0
  33. data/lib/bullet/detector/n_plus_one_query.rb +56 -15
  34. data/lib/bullet/detector/unused_eager_loading.rb +84 -0
  35. data/lib/bullet/detector.rb +2 -2
  36. data/lib/bullet/ext/object.rb +10 -2
  37. data/lib/bullet/{mongoid.rb → mongoid2x.rb} +18 -18
  38. data/lib/bullet/mongoid3x.rb +56 -0
  39. data/lib/bullet/mongoid4x.rb +56 -0
  40. data/lib/bullet/notification/base.rb +22 -4
  41. data/lib/bullet/notification/n_plus_one_query.rb +1 -1
  42. data/lib/bullet/notification.rb +2 -0
  43. data/lib/bullet/rack.rb +51 -17
  44. data/lib/bullet/registry/base.rb +1 -1
  45. data/lib/bullet/registry/object.rb +4 -4
  46. data/lib/bullet/version.rb +1 -2
  47. data/lib/bullet.rb +119 -39
  48. data/perf/benchmark.rb +1 -4
  49. data/spec/bullet/detector/association_spec.rb +2 -70
  50. data/spec/bullet/detector/base_spec.rb +0 -6
  51. data/spec/bullet/detector/counter_cache_spec.rb +56 -0
  52. data/spec/bullet/detector/n_plus_one_query_spec.rb +78 -34
  53. data/spec/bullet/detector/unused_eager_loading_spec.rb +88 -0
  54. data/spec/bullet/ext/object_spec.rb +18 -7
  55. data/spec/bullet/ext/string_spec.rb +2 -2
  56. data/spec/bullet/notification/base_spec.rb +68 -15
  57. data/spec/bullet/notification/counter_cache_spec.rb +2 -2
  58. data/spec/bullet/notification/n_plus_one_query_spec.rb +4 -3
  59. data/spec/bullet/notification/unused_eager_loading_spec.rb +2 -2
  60. data/spec/bullet/notification_collector_spec.rb +4 -4
  61. data/spec/bullet/rack_spec.rb +40 -39
  62. data/spec/bullet/registry/association_spec.rb +3 -3
  63. data/spec/bullet/registry/base_spec.rb +6 -6
  64. data/spec/bullet/registry/object_spec.rb +4 -5
  65. data/spec/bullet_spec.rb +41 -0
  66. data/spec/integration/active_record3/association_spec.rb +662 -0
  67. data/spec/integration/active_record4/association_spec.rb +660 -0
  68. data/spec/integration/counter_cache_spec.rb +63 -0
  69. data/spec/integration/mongoid/association_spec.rb +190 -208
  70. data/spec/models/category.rb +1 -1
  71. data/spec/models/client.rb +1 -1
  72. data/spec/models/comment.rb +2 -2
  73. data/spec/models/document.rb +2 -2
  74. data/spec/models/firm.rb +1 -1
  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 +4 -0
  81. data/spec/models/mongoid/user.rb +5 -0
  82. data/spec/models/newspaper.rb +1 -1
  83. data/spec/models/pet.rb +1 -1
  84. data/spec/models/post.rb +6 -13
  85. data/spec/spec_helper.rb +60 -25
  86. data/spec/support/bullet_ext.rb +1 -1
  87. data/spec/support/mongo_seed.rb +26 -2
  88. data/spec/support/sqlite_seed.rb +0 -6
  89. data/test.sh +15 -0
  90. metadata +77 -54
  91. data/.rvmrc +0 -2
  92. data/.rvmrc.example +0 -2
  93. data/Gemfile.lock +0 -132
  94. data/Gemfile.rails-2.3.14 +0 -16
  95. data/Gemfile.rails-2.3.14.lock +0 -65
  96. data/Gemfile.rails-3.0.12 +0 -17
  97. data/Gemfile.rails-3.0.12.lock +0 -116
  98. data/Gemfile.rails-3.1.4 +0 -18
  99. data/Gemfile.rails-3.1.4.lock +0 -134
  100. data/README.textile +0 -395
  101. data/README_for_rails2.textile +0 -400
  102. data/lib/bullet/action_controller2.rb +0 -49
  103. data/lib/bullet/active_record2.rb +0 -121
  104. data/lib/bullet/detector/counter.rb +0 -48
  105. data/lib/bullet/detector/unused_eager_association.rb +0 -42
  106. data/spec/bullet/detector/counter_spec.rb +0 -64
  107. data/spec/bullet/detector/unused_eager_association_spec.rb +0 -62
  108. data/spec/integration/association_spec.rb +0 -593
  109. data/spec/integration/counter_spec.rb +0 -39
  110. data/spec/integration/rails2/association_spec.rb +0 -593
  111. data/spec/integration/rails2/counter_spec.rb +0 -39
  112. data/test.result +0 -2293
data/lib/bullet.rb CHANGED
@@ -1,20 +1,16 @@
1
+ require "active_support/core_ext/module/delegation"
1
2
  require 'set'
2
3
  require 'uniform_notifier'
3
4
  require 'bullet/ext/object'
4
5
  require 'bullet/ext/string'
6
+ require 'bullet/dependency'
5
7
 
6
8
  module Bullet
7
- if Rails.version =~ /\A3\.0/
8
- autoload :ActiveRecord, 'bullet/active_record3'
9
- elsif Rails.version =~ /\A3\.[12]/
10
- autoload :ActiveRecord, 'bullet/active_record31'
11
- else
12
- autoload :ActiveRecord, 'bullet/active_record2'
13
- autoload :ActionController, 'bullet/action_controller2'
14
- end
15
- autoload :Mongoid, 'bullet/mongoid'
9
+ extend Dependency
10
+
11
+ autoload :ActiveRecord, "bullet/#{active_record_version}" if active_record?
12
+ autoload :Mongoid, "bullet/#{mongoid_version}" if mongoid?
16
13
  autoload :Rack, 'bullet/rack'
17
- autoload :BulletLogger, 'bullet/logger'
18
14
  autoload :Notification, 'bullet/notification'
19
15
  autoload :Detector, 'bullet/detector'
20
16
  autoload :Registry, 'bullet/registry'
@@ -28,66 +24,122 @@ module Bullet
28
24
  end
29
25
  end
30
26
 
31
- class <<self
32
- attr_accessor :enable, :disable_browser_cache
33
- attr_reader :notification_collector
27
+ class << self
28
+ attr_writer :enable, :n_plus_one_query_enable, :unused_eager_loading_enable, :counter_cache_enable, :stacktrace_includes
29
+ attr_reader :notification_collector, :whitelist
30
+ attr_accessor :add_footer, :orm_pathches_applied
34
31
 
35
- delegate :alert=, :console=, :growl=, :rails_logger=, :xmpp=, :to => UniformNotifier
32
+ delegate :alert=, :console=, :growl=, :rails_logger=, :xmpp=, :airbrake=, :bugsnag=, :to => UniformNotifier
33
+
34
+ def raise=(should_raise)
35
+ UniformNotifier.raise=(should_raise ? Notification::UnoptimizedQueryError : false)
36
+ end
36
37
 
37
38
  DETECTORS = [ Bullet::Detector::NPlusOneQuery,
38
- Bullet::Detector::UnusedEagerAssociation,
39
- Bullet::Detector::Counter ]
39
+ Bullet::Detector::UnusedEagerLoading,
40
+ Bullet::Detector::CounterCache ]
40
41
 
41
42
  def enable=(enable)
42
- @enable = enable
43
+ @enable = @n_plus_one_query_enable = @unused_eager_loading_enable = @counter_cache_enable = enable
43
44
  if enable?
44
- begin
45
- require 'mongoid'
46
- Bullet::Mongoid.enable
47
- rescue LoadError
48
- end
49
- begin
50
- require 'active_record'
51
- Bullet::ActiveRecord.enable
52
- if Rails.version =~ /\A2./
53
- Bullet::ActionController.enable
54
- end
55
- rescue LoadError
45
+ reset_whitelist
46
+ unless orm_pathches_applied
47
+ self.orm_pathches_applied = true
48
+ Bullet::Mongoid.enable if mongoid?
49
+ Bullet::ActiveRecord.enable if active_record?
56
50
  end
57
51
  end
58
52
  end
59
53
 
60
54
  def enable?
61
- @enable == true
55
+ !!@enable
56
+ end
57
+
58
+ def n_plus_one_query_enable?
59
+ self.enable? && !!@n_plus_one_query_enable
60
+ end
61
+
62
+ def unused_eager_loading_enable?
63
+ self.enable? && !!@unused_eager_loading_enable
64
+ end
65
+
66
+ def counter_cache_enable?
67
+ self.enable? && !!@counter_cache_enable
68
+ end
69
+
70
+ def stacktrace_includes
71
+ @stacktrace_includes || []
72
+ end
73
+
74
+ def add_whitelist(options)
75
+ @whitelist[options[:type]][options[:class_name].classify] ||= []
76
+ @whitelist[options[:type]][options[:class_name].classify] << options[:association].to_sym
77
+ end
78
+
79
+ def get_whitelist_associations(type, class_name)
80
+ Array(@whitelist[type][class_name])
81
+ end
82
+
83
+ def reset_whitelist
84
+ @whitelist = {:n_plus_one_query => {}, :unused_eager_loading => {}, :counter_cache => {}}
62
85
  end
63
86
 
64
87
  def bullet_logger=(active)
65
88
  if active
66
- bullet_log_file = File.open('log/bullet.log', 'a+')
67
- bullet_log_file.sync
89
+ require 'fileutils'
90
+ root_path = "#{rails? ? Rails.root.to_s : Dir.pwd}"
91
+ FileUtils.mkdir_p(root_path + '/log')
92
+ bullet_log_file = File.open("#{root_path}/log/bullet.log", 'a+')
93
+ bullet_log_file.sync = true
68
94
  UniformNotifier.customized_logger = bullet_log_file
69
95
  end
70
96
  end
71
97
 
98
+ def debug(title, message)
99
+ puts "[Bullet][#{title}] #{message}" if ENV['BULLET_DEBUG'] == 'true'
100
+ end
101
+
72
102
  def start_request
73
- notification_collector.reset
74
- DETECTORS.each { |bullet| bullet.start_request }
103
+ Thread.current[:bullet_start] = true
104
+ Thread.current[:bullet_notification_collector] = Bullet::NotificationCollector.new
105
+
106
+ Thread.current[:bullet_object_associations] = Bullet::Registry::Base.new
107
+ Thread.current[:bullet_call_object_associations] = Bullet::Registry::Base.new
108
+ Thread.current[:bullet_possible_objects] = Bullet::Registry::Object.new
109
+ Thread.current[:bullet_impossible_objects] = Bullet::Registry::Object.new
110
+ Thread.current[:bullet_inversed_objects] = Bullet::Registry::Base.new
111
+ Thread.current[:bullet_eager_loadings] = Bullet::Registry::Association.new
112
+
113
+ Thread.current[:bullet_counter_possible_objects] ||= Bullet::Registry::Object.new
114
+ Thread.current[:bullet_counter_impossible_objects] ||= Bullet::Registry::Object.new
75
115
  end
76
116
 
77
117
  def end_request
78
- DETECTORS.each { |bullet| bullet.end_request }
118
+ Thread.current[:bullet_start] = nil
119
+ Thread.current[:bullet_notification_collector] = nil
120
+
121
+ Thread.current[:bullet_object_associations] = nil
122
+ Thread.current[:bullet_call_object_associations] = nil
123
+ Thread.current[:bullet_possible_objects] = nil
124
+ Thread.current[:bullet_impossible_objects] = nil
125
+ Thread.current[:bullet_inversed_objects] = nil
126
+ Thread.current[:bullet_eager_loadings] = nil
127
+
128
+ Thread.current[:bullet_counter_possible_objects] = nil
129
+ Thread.current[:bullet_counter_impossible_objects] = nil
79
130
  end
80
131
 
81
- def clear
82
- DETECTORS.each { |bullet| bullet.clear }
132
+ def start?
133
+ Thread.current[:bullet_start]
83
134
  end
84
135
 
85
136
  def notification_collector
86
- @notification_collector ||= Bullet::NotificationCollector.new
137
+ Thread.current[:bullet_notification_collector]
87
138
  end
88
139
 
89
140
  def notification?
90
- Bullet::Detector::UnusedEagerAssociation.check_unused_preload_associations
141
+ return unless start?
142
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
91
143
  notification_collector.notifications_present?
92
144
  end
93
145
 
@@ -106,6 +158,34 @@ module Bullet
106
158
  end
107
159
  end
108
160
 
161
+ def footer_info
162
+ info = []
163
+ for_each_active_notifier_with_notification do |notification|
164
+ info << notification.short_notice
165
+ end
166
+ info
167
+ end
168
+
169
+ def warnings
170
+ notification_collector.collection.inject({}) do |warnings, notification|
171
+ warning_type = notification.class.to_s.split(':').last.tableize
172
+ warnings[warning_type] ||= []
173
+ warnings[warning_type] << notification
174
+ warnings
175
+ end
176
+ end
177
+
178
+ def profile
179
+ Bullet.start_request if Bullet.enable?
180
+
181
+ yield
182
+
183
+ if Bullet.enable? && Bullet.notification?
184
+ Bullet.perform_out_of_channel_notifications
185
+ end
186
+ Bullet.end_request if Bullet.enable?
187
+ end
188
+
109
189
  private
110
190
  def for_each_active_notifier_with_notification
111
191
  UniformNotifier.active_notifiers.each do |notifier|
data/perf/benchmark.rb CHANGED
@@ -27,7 +27,7 @@ class User < ActiveRecord::Base
27
27
  end
28
28
 
29
29
  # create database bullet_benchmark;
30
- ActiveRecord::Base.establish_connection(:adapter => 'mysql', :database => 'bullet_benchmark', :server => '/tmp/mysql.socket', :username => 'root')
30
+ ActiveRecord::Base.establish_connection(:adapter => 'mysql2', :database => 'bullet_benchmark', :server => '/tmp/mysql.socket', :username => 'root')
31
31
 
32
32
  ActiveRecord::Base.connection.tables.each do |table|
33
33
  ActiveRecord::Base.connection.drop_table(table)
@@ -79,8 +79,6 @@ puts "Start benchmarking..."
79
79
 
80
80
  Bullet.enable = true
81
81
 
82
- PerfTools::CpuProfiler.start(ARGV[0]|| "benchmark_profile") if defined? PerfTools
83
-
84
82
  Benchmark.bm(70) do |bm|
85
83
  bm.report("Querying & Iterating #{posts_size} Posts with #{comments_size} Comments and #{users_size} Users") do
86
84
  10.times do
@@ -98,7 +96,6 @@ Benchmark.bm(70) do |bm|
98
96
  end
99
97
  end
100
98
 
101
- PerfTools::CpuProfiler.stop if defined? PerfTools
102
99
  puts "End benchmarking..."
103
100
 
104
101
 
@@ -7,86 +7,18 @@ module Bullet
7
7
  @post1 = Post.first
8
8
  @post2 = Post.last
9
9
  end
10
- before(:each) { Association.clear }
11
-
12
- context ".start_request" do
13
- it "should set @@checked to false" do
14
- Association.start_request
15
- Association.class_variable_get(:@@checked).should be_false
16
- end
17
- end
18
-
19
- context ".clear" do
20
- it "should clear all class variables" do
21
- Association.clear
22
- Association.class_variable_get(:@@object_associations).should be_nil
23
- Association.class_variable_get(:@@possible_objects).should be_nil
24
- Association.class_variable_get(:@@impossible_objects).should be_nil
25
- Association.class_variable_get(:@@call_object_associations).should be_nil
26
- Association.class_variable_get(:@@eager_loadings).should be_nil
27
- end
28
- end
29
10
 
30
11
  context ".add_object_association" do
31
12
  it "should add object, associations pair" do
32
13
  Association.add_object_associations(@post1, :associations)
33
- Association.send(:object_associations).should be_include(@post1.bullet_ar_key, :associations)
14
+ expect(Association.send(:object_associations)).to be_include(@post1.bullet_key, :associations)
34
15
  end
35
16
  end
36
17
 
37
18
  context ".add_call_object_associations" do
38
19
  it "should add call object, associations pair" do
39
20
  Association.add_call_object_associations(@post1, :associations)
40
- Association.send(:call_object_associations).should be_include(@post1.bullet_ar_key, :associations)
41
- end
42
- end
43
-
44
- context ".add_possible_objects" do
45
- it "should add possible objects" do
46
- Association.add_possible_objects([@post1, @post2])
47
- Association.send(:possible_objects).should be_include(@post1.bullet_ar_key)
48
- Association.send(:possible_objects).should be_include(@post2.bullet_ar_key)
49
- end
50
-
51
- it "should not raise error if object is nil" do
52
- lambda { Association.add_possible_objects(nil) }.should_not raise_error
53
- end
54
- end
55
-
56
- context ".add_impossible_object" do
57
- it "should add impossible object" do
58
- Association.add_impossible_object(@post1)
59
- Association.send(:impossible_objects).should be_include(@post1.bullet_ar_key)
60
- end
61
- end
62
-
63
- context ".add_eager_loadings" do
64
- it "should add objects, associations pair when eager_loadings are empty" do
65
- Association.add_eager_loadings([@post1, @post2], :associations)
66
- Association.send(:eager_loadings).should be_include([@post1.bullet_ar_key, @post2.bullet_ar_key], :associations)
67
- end
68
-
69
- it "should add objects, associations pair for existing eager_loadings" do
70
- Association.add_eager_loadings([@post1, @post2], :association1)
71
- Association.add_eager_loadings([@post1, @post2], :association2)
72
- Association.send(:eager_loadings).should be_include([@post1.bullet_ar_key, @post2.bullet_ar_key], :association1)
73
- Association.send(:eager_loadings).should be_include([@post1.bullet_ar_key, @post2.bullet_ar_key], :association2)
74
- end
75
-
76
- it "should merge objects, associations pair for existing eager_loadings" do
77
- Association.add_eager_loadings([@post1], :association1)
78
- Association.add_eager_loadings([@post1, @post2], :association2)
79
- Association.send(:eager_loadings).should be_include([@post1.bullet_ar_key], :association1)
80
- Association.send(:eager_loadings).should be_include([@post1.bullet_ar_key], :association2)
81
- Association.send(:eager_loadings).should be_include([@post1.bullet_ar_key, @post2.bullet_ar_key], :association2)
82
- end
83
-
84
- it "should delete objects, associations pair for existing eager_loadings" do
85
- Association.add_eager_loadings([@post1, @post2], :association1)
86
- Association.add_eager_loadings([@post1], :association2)
87
- Association.send(:eager_loadings).should be_include([@post1.bullet_ar_key], :association1)
88
- Association.send(:eager_loadings).should be_include([@post1.bullet_ar_key], :association2)
89
- Association.send(:eager_loadings).should be_include([@post2.bullet_ar_key], :association1)
21
+ expect(Association.send(:call_object_associations)).to be_include(@post1.bullet_key, :associations)
90
22
  end
91
23
  end
92
24
  end
@@ -3,12 +3,6 @@ require 'spec_helper'
3
3
  module Bullet
4
4
  module Detector
5
5
  describe Base do
6
- context ".end_request" do
7
- it "should call clear" do
8
- Base.should_receive(:clear)
9
- Base.end_request
10
- end
11
- end
12
6
  end
13
7
  end
14
8
  end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ module Bullet
4
+ module Detector
5
+ describe CounterCache do
6
+ before :all do
7
+ @post1 = Post.first
8
+ @post2 = Post.last
9
+ end
10
+
11
+ context ".add_counter_cache" do
12
+ it "should create notification if conditions met" do
13
+ expect(CounterCache).to receive(:conditions_met?).with(@post1.bullet_key, [:comments]).and_return(true)
14
+ expect(CounterCache).to receive(:create_notification).with("Post", [:comments])
15
+ CounterCache.add_counter_cache(@post1, [:comments])
16
+ end
17
+
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)
20
+ expect(CounterCache).to receive(:create_notification).never
21
+ CounterCache.add_counter_cache(@post1, [:comments])
22
+ end
23
+ end
24
+
25
+ context ".add_possible_objects" do
26
+ it "should add possible objects" do
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)
30
+ end
31
+
32
+ it "should add impossible object" do
33
+ CounterCache.add_impossible_object(@post1)
34
+ expect(CounterCache.send(:impossible_objects)).to be_include(@post1.bullet_key)
35
+ end
36
+ end
37
+
38
+ context ".conditions_met?" do
39
+ it "should be true when object is possible, not impossible" do
40
+ CounterCache.add_possible_objects(@post1)
41
+ expect(CounterCache.send(:conditions_met?, @post1.bullet_key, :associations)).to eq true
42
+ end
43
+
44
+ it "should be false when object is not possible" do
45
+ expect(CounterCache.send(:conditions_met?, @post1.bullet_key, :associations)).to eq false
46
+ end
47
+
48
+ it "should be true when object is possible, and impossible" do
49
+ CounterCache.add_possible_objects(@post1)
50
+ CounterCache.add_impossible_object(@post1)
51
+ expect(CounterCache.send(:conditions_met?, @post1.bullet_key, :associations)).to eq false
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -3,17 +3,14 @@ require 'spec_helper'
3
3
  module Bullet
4
4
  module Detector
5
5
  describe NPlusOneQuery do
6
- before(:all) { @post = Post.first }
7
- before(:each) { NPlusOneQuery.clear }
6
+ before(:all) do
7
+ @post = Post.first
8
+ @post2 = Post.last
9
+ end
8
10
 
9
11
  context ".call_association" do
10
- it "should set @@checked to true" do
11
- NPlusOneQuery.call_association(@post, :associations)
12
- NPlusOneQuery.class_variable_get(:@@checked).should be_true
13
- end
14
-
15
12
  it "should add call_object_associations" do
16
- NPlusOneQuery.should_receive(:add_call_object_associations).with(@post, :associations)
13
+ expect(NPlusOneQuery).to receive(:add_call_object_associations).with(@post, :associations)
17
14
  NPlusOneQuery.call_association(@post, :associations)
18
15
  end
19
16
  end
@@ -21,74 +18,121 @@ module Bullet
21
18
  context ".possible?" do
22
19
  it "should be true if possible_objects contain" do
23
20
  NPlusOneQuery.add_possible_objects(@post)
24
- NPlusOneQuery.send(:possible?, @post.bullet_ar_key).should be_true
21
+ expect(NPlusOneQuery.send(:possible?, @post.bullet_key)).to eq true
25
22
  end
26
23
  end
27
24
 
28
25
  context ".impossible?" do
29
26
  it "should be true if impossible_objects contain" do
30
27
  NPlusOneQuery.add_impossible_object(@post)
31
- NPlusOneQuery.send(:impossible?, @post.bullet_ar_key).should be_true
28
+ expect(NPlusOneQuery.send(:impossible?, @post.bullet_key)).to eq true
32
29
  end
33
30
  end
34
31
 
35
32
  context ".association?" do
36
33
  it "should be true if object, associations pair is already existed" do
37
34
  NPlusOneQuery.add_object_associations(@post, :association)
38
- NPlusOneQuery.send(:association?, @post.bullet_ar_key, :association).should be_true
35
+ expect(NPlusOneQuery.send(:association?, @post.bullet_key, :association)).to eq true
39
36
  end
40
37
 
41
38
  it "should be false if object, association pair is not existed" do
42
39
  NPlusOneQuery.add_object_associations(@post, :association1)
43
- NPlusOneQuery.send(:association?, @post.bullet_ar_key, :associatio2).should be_false
40
+ expect(NPlusOneQuery.send(:association?, @post.bullet_key, :associatio2)).to eq false
44
41
  end
45
42
  end
46
43
 
47
44
  context ".conditions_met?" do
48
45
  it "should be true if object is possible, not impossible and object, associations pair is not already existed" do
49
- NPlusOneQuery.stub(:possible?).with(@post.bullet_ar_key).and_return(true)
50
- NPlusOneQuery.stub(:impossible?).with(@post.bullet_ar_key).and_return(false)
51
- NPlusOneQuery.stub(:association?).with(@post.bullet_ar_key, :associations).and_return(false)
52
- NPlusOneQuery.send(:conditions_met?, @post.bullet_ar_key, :associations).should be_true
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
53
50
  end
54
51
 
55
52
  it "should be false if object is not possible, not impossible and object, associations pair is not already existed" do
56
- NPlusOneQuery.stub(:possible?).with(@post.bullet_ar_key).and_return(false)
57
- NPlusOneQuery.stub(:impossible?).with(@post.bullet_ar_key).and_return(false)
58
- NPlusOneQuery.stub(:association?).with(@post.bullet_ar_key, :associations).and_return(false)
59
- NPlusOneQuery.send(:conditions_met?, @post.bullet_ar_key, :associations).should be_false
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
60
57
  end
61
58
 
62
59
  it "should be false if object is possible, but impossible and object, associations pair is not already existed" do
63
- NPlusOneQuery.stub(:possible?).with(@post.bullet_ar_key).and_return(true)
64
- NPlusOneQuery.stub(:impossible?).with(@post.bullet_ar_key).and_return(true)
65
- NPlusOneQuery.stub(:association?).with(@post.bullet_ar_key, :associations).and_return(false)
66
- NPlusOneQuery.send(:conditions_met?, @post.bullet_ar_key, :associations).should be_false
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
67
64
  end
68
65
 
69
66
  it "should be false if object is possible, not impossible and object, associations pair is already existed" do
70
- NPlusOneQuery.stub(:possible?).with(@post.bullet_ar_key).and_return(true)
71
- NPlusOneQuery.stub(:impossible?).with(@post.bullet_ar_key).and_return(false)
72
- NPlusOneQuery.stub(:association?).with(@post.bullet_ar_key, :associations).and_return(true)
73
- NPlusOneQuery.send(:conditions_met?, @post.bullet_ar_key, :associations).should be_false
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
74
71
  end
75
72
  end
76
73
 
77
74
  context ".call_association" do
78
75
  it "should create notification if conditions met" do
79
- NPlusOneQuery.should_receive(:conditions_met?).with(@post.bullet_ar_key, :association).and_return(true)
80
- NPlusOneQuery.should_receive(:caller_in_project).and_return(["caller"])
81
- NPlusOneQuery.should_receive(:create_notification).with(["caller"], "Post", :association)
76
+ expect(NPlusOneQuery).to receive(:conditions_met?).with(@post.bullet_key, :association).and_return(true)
77
+ expect(NPlusOneQuery).to receive(:caller_in_project).and_return(["caller"])
78
+ expect(NPlusOneQuery).to receive(:create_notification).with(["caller"], "Post", :association)
82
79
  NPlusOneQuery.call_association(@post, :association)
83
80
  end
84
81
 
85
82
  it "should not create notification if conditions not met" do
86
- NPlusOneQuery.should_receive(:conditions_met?).with(@post.bullet_ar_key, :association).and_return(false)
87
- NPlusOneQuery.should_not_receive(:caller_in_project!)
88
- NPlusOneQuery.should_not_receive(:create_notification).with("Post", :association)
83
+ expect(NPlusOneQuery).to receive(:conditions_met?).with(@post.bullet_key, :association).and_return(false)
84
+ expect(NPlusOneQuery).not_to receive(:caller_in_project!)
85
+ expect(NPlusOneQuery).not_to receive(:create_notification).with("Post", :association)
89
86
  NPlusOneQuery.call_association(@post, :association)
90
87
  end
91
88
  end
89
+
90
+ context ".caller_in_project" do
91
+ it "should include only paths that are in the project" do
92
+ in_project = File.join(Dir.pwd, 'abc', 'abc.rb')
93
+ not_in_project = '/def/def.rb'
94
+
95
+ 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)
97
+ expect(NPlusOneQuery).to receive(:create_notification).with([in_project], "Post", :association)
98
+ NPlusOneQuery.call_association(@post, :association)
99
+ end
100
+
101
+ context "stacktrace_includes" do
102
+ before { Bullet.stacktrace_includes = [ 'def' ] }
103
+ after { Bullet.stacktrace_includes = nil }
104
+
105
+ it "should include paths that are in the stacktrace_include list" do
106
+ in_project = File.join(Dir.pwd, 'abc', 'abc.rb')
107
+ included_gem = '/def/def.rb'
108
+ excluded_gem = '/ghi/ghi.rb'
109
+
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)
113
+ NPlusOneQuery.call_association(@post, :association)
114
+ end
115
+ end
116
+ end
117
+
118
+ context ".add_possible_objects" do
119
+ it "should add possible objects" do
120
+ 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)
123
+ end
124
+
125
+ it "should not raise error if object is nil" do
126
+ expect { NPlusOneQuery.add_possible_objects(nil) }.not_to raise_error
127
+ end
128
+ end
129
+
130
+ context ".add_impossible_object" do
131
+ it "should add impossible object" do
132
+ NPlusOneQuery.add_impossible_object(@post)
133
+ expect(NPlusOneQuery.send(:impossible_objects)).to be_include(@post.bullet_key)
134
+ end
135
+ end
92
136
  end
93
137
  end
94
138
  end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ module Bullet
4
+ module Detector
5
+ describe UnusedEagerLoading do
6
+ before(:all) do
7
+ @post = Post.first
8
+ @post2 = Post.last
9
+ end
10
+
11
+ context ".call_associations" do
12
+ it "should get empty array if eager_loadings" do
13
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty
14
+ end
15
+
16
+ it "should get call associations if object and association are both in eager_loadings and call_object_associations" do
17
+ UnusedEagerLoading.add_eager_loadings([@post], :association)
18
+ UnusedEagerLoading.add_call_object_associations(@post, :association)
19
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq([:association])
20
+ end
21
+
22
+ it "should not get call associations if not exist in call_object_associations" do
23
+ UnusedEagerLoading.add_eager_loadings([@post], :association)
24
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty
25
+ end
26
+ end
27
+
28
+ context ".diff_object_associations" do
29
+ it "should return associations not exist in call_association" do
30
+ expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq([:association])
31
+ end
32
+
33
+ it "should return empty if associations exist in call_association" do
34
+ UnusedEagerLoading.add_eager_loadings([@post], :association)
35
+ UnusedEagerLoading.add_call_object_associations(@post, :association)
36
+ expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to be_empty
37
+ end
38
+ end
39
+
40
+ context ".check_unused_preload_associations" do
41
+ it "should create notification if object_association_diff is not empty" do
42
+ UnusedEagerLoading.add_object_associations(@post, :association)
43
+ expect(UnusedEagerLoading).to receive(:create_notification).with("Post", [:association])
44
+ UnusedEagerLoading.check_unused_preload_associations
45
+ end
46
+
47
+ it "should not create notification if object_association_diff is empty" do
48
+ UnusedEagerLoading.add_object_associations(@post, :association)
49
+ UnusedEagerLoading.add_eager_loadings([@post], :association)
50
+ UnusedEagerLoading.add_call_object_associations(@post, :association)
51
+ expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to be_empty
52
+ expect(UnusedEagerLoading).not_to receive(:create_notification).with("Post", [:association])
53
+ UnusedEagerLoading.check_unused_preload_associations
54
+ end
55
+ end
56
+
57
+ context ".add_eager_loadings" do
58
+ it "should add objects, associations pair when eager_loadings are empty" do
59
+ UnusedEagerLoading.add_eager_loadings([@post, @post2], :associations)
60
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key, @post2.bullet_key], :associations)
61
+ end
62
+
63
+ it "should add objects, associations pair for existing eager_loadings" do
64
+ UnusedEagerLoading.add_eager_loadings([@post, @post2], :association1)
65
+ UnusedEagerLoading.add_eager_loadings([@post, @post2], :association2)
66
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key, @post2.bullet_key], :association1)
67
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key, @post2.bullet_key], :association2)
68
+ end
69
+
70
+ it "should merge objects, associations pair for existing eager_loadings" do
71
+ UnusedEagerLoading.add_eager_loadings([@post], :association1)
72
+ UnusedEagerLoading.add_eager_loadings([@post, @post2], :association2)
73
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association1)
74
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association2)
75
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key, @post2.bullet_key], :association2)
76
+ end
77
+
78
+ it "should delete objects, associations pair for existing eager_loadings" do
79
+ UnusedEagerLoading.add_eager_loadings([@post, @post2], :association1)
80
+ UnusedEagerLoading.add_eager_loadings([@post], :association2)
81
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association1)
82
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post.bullet_key], :association2)
83
+ expect(UnusedEagerLoading.send(:eager_loadings)).to be_include([@post2.bullet_key], :association1)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end