flyerhzm-bullet 1.5.8 → 1.5.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -18,6 +18,7 @@ h2. Thanks
18
18
 
19
19
  flipsasser added Growl, console.log and Rails.log support, very awesome. And he improved README.
20
20
  rainux add group style console.log.
21
+ 2collegebums add some specs to generate red bar.
21
22
 
22
23
  ****************************************************************************
23
24
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.5.8
1
+ 1.5.9
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{bullet}
8
- s.version = "1.5.8"
8
+ s.version = "1.5.9"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Richard Huang"]
12
- s.date = %q{2009-09-22}
12
+ s.date = %q{2009-09-24}
13
13
  s.description = %q{The Bullet plugin is designed to help you increase your application's performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (N+1 queries) or when you're using eager loading that isn't necessary.}
14
14
  s.email = %q{flyerhzm@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -42,9 +42,9 @@ Gem::Specification.new do |s|
42
42
  s.rubygems_version = %q{1.3.5}
43
43
  s.summary = %q{A plugin to kill N+1 queries and unused eager loading}
44
44
  s.test_files = [
45
- "spec/bullet_association_spec.rb",
46
- "spec/bullet_counter_spec.rb",
47
- "spec/spec_helper.rb"
45
+ "spec/bullet_counter_spec.rb",
46
+ "spec/spec_helper.rb",
47
+ "spec/bullet_association_spec.rb"
48
48
  ]
49
49
 
50
50
  if s.respond_to? :specification_version then
@@ -15,7 +15,7 @@ module Bullet
15
15
  Bullet::Counter.add_possible_objects(records)
16
16
  elsif records.size == 1
17
17
  Bullet::Association.add_impossible_object(records.first)
18
- Bullet::Counter.add_impossible_object(records)
18
+ Bullet::Counter.add_impossible_object(records.first)
19
19
  end
20
20
  end
21
21
 
@@ -53,7 +53,7 @@ module Bullet
53
53
  associations = merge_includes(scope(:find, :include), options[:include])
54
54
  records.each do |record|
55
55
  Bullet::Association.add_object_associations(record, associations)
56
- Bullet::Association.add_call_object_associations(record, associations)
56
+ Bullet::Association.call_association(record, associations)
57
57
  end
58
58
  Bullet::Association.add_eager_loadings(records, associations)
59
59
  records
@@ -73,7 +73,8 @@ module Bullet
73
73
  # call has_one and belong_to association
74
74
  alias_method :origin_load_target, :load_target
75
75
  def load_target
76
- Bullet::Association.call_association(@owner, @reflection.name)
76
+ # avoid stack level too deep
77
+ Bullet::Association.call_association(@owner, @reflection.name) unless caller.to_s.include? 'load_target'
77
78
  origin_load_target
78
79
  end
79
80
  end
@@ -19,6 +19,7 @@ module Bullet
19
19
  @@impossible_objects = nil
20
20
  @@call_object_associations = nil
21
21
  @@eager_loadings = nil
22
+ @@klazz_associations = nil
22
23
  end
23
24
 
24
25
  def notification?
@@ -73,8 +74,26 @@ module Bullet
73
74
  def add_eager_loadings(objects, associations)
74
75
  objects = Array(objects)
75
76
  eager_loadings[objects] ||= []
76
- eager_loadings[objects] << associations
77
- unique(eager_loadings[objects])
77
+ eager_loadings.each do |k, v|
78
+ unless (k & objects).empty?
79
+ if (k & objects) == k
80
+ eager_loadings[k] = (eager_loadings[k] + Array(associations))
81
+ unique(eager_loadings[k])
82
+ break
83
+ else
84
+ eager_loadings.merge!({(k & objects) => (eager_loadings[k] + Array(associations))})
85
+ unique(eager_loadings[(k & objects)])
86
+ eager_loadings.merge!({(k - objects) => eager_loadings[k]}) unless (k - objects).empty?
87
+ unique(eager_loadings[(k - objects)])
88
+ eager_loadings.delete(k)
89
+ objects = objects - k
90
+ end
91
+ end
92
+ end
93
+ unless objects.empty?
94
+ eager_loadings[objects] << Array(associations)
95
+ unique(eager_loadings[objects])
96
+ end
78
97
  end
79
98
 
80
99
  def define_association(klazz, associations)
@@ -108,10 +127,29 @@ module Bullet
108
127
 
109
128
  private
110
129
  def unpreload_associations?(object, associations)
130
+ possible?(object) and !impossible?(object) and !association?(object, associations)
131
+ end
132
+
133
+ def possible?(object)
111
134
  klazz = object.class
112
- (!possible_objects[klazz].nil? and possible_objects[klazz].include?(object)) and
113
- (impossible_objects[klazz].nil? or !impossible_objects[klazz].include?(object)) and
114
- (object_associations[object].nil? or !object_associations[object].include?(associations))
135
+ possible_objects[klazz] and possible_objects[klazz].include?(object)
136
+ end
137
+
138
+ def impossible?(object)
139
+ klazz = object.class
140
+ impossible_objects[klazz] and impossible_objects[klazz].include?(object)
141
+ end
142
+
143
+ def association?(object, associations)
144
+ object_associations.each do |key, value|
145
+ if key == object
146
+ value.each do |v|
147
+ result = v.is_a?(Hash) ? v.has_key?(associations) : v == associations
148
+ return true if result
149
+ end
150
+ end
151
+ end
152
+ return false
115
153
  end
116
154
 
117
155
  def notification_response
@@ -3,26 +3,36 @@ require File.dirname(__FILE__) + '/spec_helper'
3
3
  ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
4
4
 
5
5
  describe Bullet::Association, 'has_many' do
6
+
7
+ include BulletTestHelper
8
+
6
9
  def setup_db
7
10
  ActiveRecord::Schema.define(:version => 1) do
8
11
  create_table :categories do |t|
9
12
  t.column :name, :string
10
13
  end
11
-
14
+
12
15
  create_table :posts do |t|
13
16
  t.column :name, :string
14
17
  t.column :category_id, :integer
18
+ t.column :writer_id, :integer
15
19
  end
16
20
 
17
21
  create_table :comments do |t|
18
22
  t.column :name, :string
19
23
  t.column :post_id, :integer
24
+ t.column :author_id, :integer
20
25
  end
21
26
 
22
27
  create_table :entries do |t|
23
28
  t.column :name, :string
24
29
  t.column :category_id, :integer
25
30
  end
31
+
32
+ create_table :base_users do |t|
33
+ t.column :name, :string
34
+ t.column :type, :string
35
+ end
26
36
  end
27
37
  end
28
38
 
@@ -31,7 +41,7 @@ describe Bullet::Association, 'has_many' do
31
41
  ActiveRecord::Base.connection.drop_table(table)
32
42
  end
33
43
  end
34
-
44
+
35
45
  class Category < ActiveRecord::Base
36
46
  has_many :posts
37
47
  has_many :entries
@@ -40,39 +50,59 @@ describe Bullet::Association, 'has_many' do
40
50
  class Post < ActiveRecord::Base
41
51
  belongs_to :category
42
52
  has_many :comments
53
+ belongs_to :writer
54
+
43
55
 
44
56
  named_scope :preload_posts, lambda { {:include => :comments} }
45
57
  named_scope :in_category_name, lambda { |name|
46
58
  { :conditions => ['categories.name = ?', name], :include => :category }
47
59
  }
48
60
  end
49
-
61
+
50
62
  class Entry < ActiveRecord::Base
51
63
  belongs_to :category
52
64
  end
53
65
 
54
66
  class Comment < ActiveRecord::Base
55
67
  belongs_to :post
68
+ belongs_to :author, :class_name => "BaseUser"
69
+ end
70
+
71
+ class BaseUser < ActiveRecord::Base
72
+ has_many :comments
73
+ has_many :posts
56
74
  end
57
75
 
76
+ class Writer < BaseUser
77
+ end
78
+
58
79
  before(:all) do
59
- setup_db
60
-
80
+ silence_logger { setup_db }
81
+
82
+ writer1 = Writer.create(:name => 'first')
83
+ writer2 = Writer.create(:name => 'second')
84
+ user1 = BaseUser.create(:name => 'third')
85
+ user2 = BaseUser.create(:name => 'fourth')
86
+
87
+
61
88
  category1 = Category.create(:name => 'first')
62
89
  category2 = Category.create(:name => 'second')
63
-
64
- post1 = category1.posts.create(:name => 'first')
65
- post2 = category1.posts.create(:name => 'second')
66
-
67
- comment1 = post1.comments.create(:name => 'first')
68
- comment2 = post1.comments.create(:name => 'second')
69
- comment3 = post2.comments.create(:name => 'third')
70
- comment4 = post2.comments.create(:name => 'fourth')
71
-
90
+
91
+ post1 = category1.posts.create(:name => 'first', :writer => writer1)
92
+ post2 = category2.posts.create(:name => 'second', :writer => writer2)
93
+
94
+ comment1 = post1.comments.create(:name => 'first', :author => writer1)
95
+ comment1 = post1.comments.create(:name => 'first2', :author => writer1)
96
+ comment1 = post1.comments.create(:name => 'first3', :author => writer1)
97
+ comment2 = post1.comments.create(:name => 'second', :author => writer2)
98
+ comment3 = post2.comments.create(:name => 'third', :author => user1)
99
+ comment4 = post2.comments.create(:name => 'fourth', :author => user2)
100
+ comment4 = post2.comments.create(:name => 'fourth', :author => writer1)
101
+
72
102
  entry1 = category1.entries.create(:name => 'first')
73
103
  entry2 = category1.entries.create(:name => 'second')
74
104
  end
75
-
105
+
76
106
  after(:all) do
77
107
  teardown_db
78
108
  end
@@ -85,6 +115,92 @@ describe Bullet::Association, 'has_many' do
85
115
  Bullet::Association.end_request
86
116
  end
87
117
 
118
+ context "for unused cases" do
119
+ #If you have the same record created twice with different includes
120
+ # the hash value get's accumulated includes, which leads to false Unused eager loading
121
+ it "should not incorrectly mark associations as unused when multiple object instances" do
122
+ comments_with_author = Comment.all(:include => :author)
123
+ comments_with_post = Comment.all(:include => :post)
124
+ comments_with_author.each { |c| c.author.name }
125
+ comments_with_author.each { |c| c.post.name }
126
+ Bullet::Association.check_unused_preload_associations
127
+ Bullet::Association.should be_unused_preload_associations_for(Comment, :post)
128
+ Bullet::Association.should be_detecting_unpreloaded_association_for(Comment, :post)
129
+ end
130
+
131
+ # same as above with different Models being queried
132
+ it "should not incorrectly mark associations as unused when multiple object instances different Model" do
133
+ post_with_comments = Post.all(:include => :comments)
134
+ comments_with_author = Comment.all(:include => :author)
135
+ post_with_comments.each { |p| p.comments.first.author.name }
136
+ comments_with_author.each { |c| c.name }
137
+ Bullet::Association.check_unused_preload_associations
138
+ Bullet::Association.should be_unused_preload_associations_for(Comment, :author)
139
+ Bullet::Association.should be_detecting_unpreloaded_association_for(Comment, :author)
140
+ end
141
+
142
+ # this test passes right now. But is a regression test to ensure that if only a small set of returned records
143
+ # is not used that a unused preload association error is not generated
144
+ it "should not have unused when small set of returned records are discarded" do
145
+ comments_with_author = Comment.all(:include => :author)
146
+ comment_collection = comments_with_author.first(2)
147
+ comment_collection.collect { |com| com.author.name }
148
+ Bullet::Association.check_unused_preload_associations
149
+ Bullet::Association.should_not be_unused_preload_associations_for(Comment, :author)
150
+ end
151
+ end
152
+
153
+
154
+ context "comments => posts => category" do
155
+
156
+ # this happens because the post isn't a possible object even though the writer is access through the post
157
+ # which leads to an 1+N queries
158
+ it "should detect unpreloaded writer" do
159
+ Comment.all(:include => [:author, :post],
160
+ :conditions => ["base_users.id = ?", BaseUser.first]).each do |com|
161
+ com.post.writer.name
162
+ end
163
+ Bullet::Association.should be_detecting_unpreloaded_association_for(Post, :writer)
164
+ end
165
+
166
+ # this happens because the comment doesn't break down the hash into keys
167
+ # properly creating an association from comment to post
168
+ it "should detect preload of comment => post" do
169
+ comments = Comment.all(:include => [:author, {:post => :writer}],
170
+ :conditions => ["base_users.id = ?", BaseUser.first]).each do |com|
171
+ com.post.writer.name
172
+ end
173
+ Bullet::Association.should_not be_detecting_unpreloaded_association_for(Comment, :post)
174
+ Bullet::Association.should be_completely_preloading_associations
175
+ end
176
+
177
+ # To 2collegebums: This query generate a only one sql with left outer join,
178
+ # so I think the test is not correct.
179
+ #
180
+ # this happens because it doesn't create an object association from post to writer
181
+ # by diving into the hash and creating those object associations
182
+ # it "should detect preload of post => writer" do
183
+ # comments = Comment.all(:include => [:author, {:post => :writer}],
184
+ # :conditions => ["base_users.id = ?", BaseUser.first]).each do |com|
185
+ # com.post.writer.name
186
+ # end
187
+ # Bullet::Association.should be_creating_object_association_for(comments.first, :author)
188
+ # Bullet::Association.should be_creating_object_association_for(comments.first.post, :writer)
189
+ # Bullet::Association.should_not be_detecting_unpreloaded_association_for(Post, :writer)
190
+ # Bullet::Association.should be_completely_preloading_associations
191
+ # end
192
+
193
+ # when we attempt to access category, there is an infinite overflow because load_target is hijacked leading to
194
+ # a repeating loop of calls in this test
195
+ it "should not raise a stack error from posts to category" do
196
+ lambda {
197
+ Comment.all(:include => {:post => :category}).each do |com|
198
+ com.post.category
199
+ end
200
+ }.should_not raise_error(SystemStackError)
201
+ end
202
+ end
203
+
88
204
  context "post => comments" do
89
205
  it "should detect preload with post => comments" do
90
206
  Post.find(:all, :include => :comments).each do |post|
@@ -92,14 +208,14 @@ describe Bullet::Association, 'has_many' do
92
208
  end
93
209
  Bullet::Association.should_not be_has_unpreload_associations
94
210
  end
95
-
211
+
96
212
  it "should detect no preload post => comments" do
97
213
  Post.find(:all).each do |post|
98
214
  post.comments.collect(&:name)
99
215
  end
100
216
  Bullet::Association.should be_has_unpreload_associations
101
217
  end
102
-
218
+
103
219
  it "should detect unused preload post => comments for post" do
104
220
  Post.find(:all, :include => :comments).collect(&:name)
105
221
  Bullet::Association.check_unused_preload_associations
@@ -111,17 +227,17 @@ describe Bullet::Association, 'has_many' do
111
227
  Bullet::Association.check_unused_preload_associations
112
228
  Bullet::Association.should_not be_has_unused_preload_associations
113
229
  end
114
-
230
+
115
231
  it "should detect no unused preload post => comments for comment" do
116
232
  Post.find(:all).each do |post|
117
233
  post.comments.collect(&:name)
118
234
  end
119
235
  Bullet::Association.check_unused_preload_associations
120
236
  Bullet::Association.should_not be_has_unused_preload_associations
121
-
237
+
122
238
  Bullet::Association.end_request
123
239
  Bullet::Association.start_request
124
-
240
+
125
241
  Post.find(:all).each do |post|
126
242
  post.comments.collect(&:name)
127
243
  end
@@ -129,17 +245,17 @@ describe Bullet::Association, 'has_many' do
129
245
  Bullet::Association.should_not be_has_unused_preload_associations
130
246
  end
131
247
  end
132
-
248
+
133
249
  context "category => posts => comments" do
134
250
  it "should detect preload with category => posts => comments" do
135
- Category.find(:all, :include => {:posts => :comments}) do |category|
251
+ Category.find(:all, :include => {:posts => :comments}).each do |category|
136
252
  category.posts.each do |post|
137
253
  post.comments.collect(&:name)
138
254
  end
139
255
  end
140
256
  Bullet::Association.should_not be_has_unpreload_associations
141
257
  end
142
-
258
+
143
259
  it "should detect preload category => posts, but no post => comments" do
144
260
  Category.find(:all, :include => :posts).each do |category|
145
261
  category.posts.each do |post|
@@ -148,7 +264,7 @@ describe Bullet::Association, 'has_many' do
148
264
  end
149
265
  Bullet::Association.should be_has_unpreload_associations
150
266
  end
151
-
267
+
152
268
  it "should detect no preload category => posts => comments" do
153
269
  Category.find(:all).each do |category|
154
270
  category.posts.each do |post|
@@ -157,13 +273,13 @@ describe Bullet::Association, 'has_many' do
157
273
  end
158
274
  Bullet::Association.should be_has_unpreload_associations
159
275
  end
160
-
276
+
161
277
  it "should detect unused preload with category => posts => comments" do
162
278
  Category.find(:all, :include => {:posts => :comments}).collect(&:name)
163
279
  Bullet::Association.check_unused_preload_associations
164
280
  Bullet::Association.should be_has_unused_preload_associations
165
281
  end
166
-
282
+
167
283
  it "should detect unused preload with post => commnets, no category => posts" do
168
284
  Category.find(:all, :include => {:posts => :comments}).each do |category|
169
285
  category.posts.collect(&:name)
@@ -171,7 +287,7 @@ describe Bullet::Association, 'has_many' do
171
287
  Bullet::Association.check_unused_preload_associations
172
288
  Bullet::Association.should be_has_unused_preload_associations
173
289
  end
174
-
290
+
175
291
  it "should no detect preload with category => posts => comments" do
176
292
  Category.find(:all).each do |category|
177
293
  category.posts.each do |post|
@@ -182,7 +298,7 @@ describe Bullet::Association, 'has_many' do
182
298
  Bullet::Association.should_not be_has_unused_preload_associations
183
299
  end
184
300
  end
185
-
301
+
186
302
  context "category => posts, category => entries" do
187
303
  it "should detect preload with category => [posts, entries]" do
188
304
  Category.find(:all, :include => [:posts, :entries]).each do |category|
@@ -207,13 +323,13 @@ describe Bullet::Association, 'has_many' do
207
323
  end
208
324
  Bullet::Association.should be_has_unpreload_associations
209
325
  end
210
-
326
+
211
327
  it "should detect unused with category => [posts, entries]" do
212
328
  Category.find(:all, :include => [:posts, :entries]).collect(&:name)
213
329
  Bullet::Association.check_unused_preload_associations
214
330
  Bullet::Association.should be_has_unused_preload_associations
215
331
  end
216
-
332
+
217
333
  it "should detect unused preload with category => entries, but no category => posts" do
218
334
  Category.find(:all, :include => [:posts, :entries]).each do |category|
219
335
  category.posts.collect(&:name)
@@ -221,7 +337,7 @@ describe Bullet::Association, 'has_many' do
221
337
  Bullet::Association.check_unused_preload_associations
222
338
  Bullet::Association.should be_has_unused_preload_associations
223
339
  end
224
-
340
+
225
341
  it "should detect no unused preload" do
226
342
  Category.find(:all).each do |category|
227
343
  category.posts.collect(&:name)
@@ -247,19 +363,19 @@ describe Bullet::Association, 'has_many' do
247
363
  end
248
364
 
249
365
  context "named_scope for_category_name" do
250
- it "should detect preload with post => category" do
251
- Post.in_category_name('first').all.each do |post|
252
- post.category.name
253
- end
254
- Bullet::Association.should_not be_has_unpreload_associations
255
- end
256
-
257
- it "should not be unused preload post => category" do
258
- Post.in_category_name('first').all.collect(&:name)
259
- Bullet::Association.should_not be_has_unpreload_associations
260
- Bullet::Association.check_unused_preload_associations
261
- Bullet::Association.should_not be_has_unused_preload_associations
262
- end
366
+ it "should detect preload with post => category" do
367
+ Post.in_category_name('first').all.each do |post|
368
+ post.category.name
369
+ end
370
+ Bullet::Association.should_not be_has_unpreload_associations
371
+ end
372
+
373
+ it "should not be unused preload post => category" do
374
+ Post.in_category_name('first').all.collect(&:name)
375
+ Bullet::Association.should_not be_has_unpreload_associations
376
+ Bullet::Association.check_unused_preload_associations
377
+ Bullet::Association.should_not be_has_unused_preload_associations
378
+ end
263
379
  end
264
380
 
265
381
  context "named_scope preload_posts" do
@@ -302,31 +418,31 @@ describe Bullet::Association, 'has_many' do
302
418
  end
303
419
  Bullet::Association.should be_has_unpreload_associations
304
420
  end
305
-
421
+
306
422
  it "should no preload comment => post" do
307
423
  Comment.first.post.name
308
424
  Bullet::Association.should_not be_has_unpreload_associations
309
425
  end
310
-
426
+
311
427
  it "should no preload comments => post" do
312
428
  Comment.find(:all, :include => :post).each do |comment|
313
429
  comment.post.name
314
430
  end
315
431
  Bullet::Association.should_not be_has_unpreload_associations
316
432
  end
317
-
433
+
318
434
  it "should detect no unused preload comments => post" do
319
435
  Comment.find(:all).collect(&:name)
320
436
  Bullet::Association.check_unused_preload_associations
321
437
  Bullet::Association.should_not be_has_unused_preload_associations
322
438
  end
323
-
439
+
324
440
  it "should detect unused preload comments => post" do
325
441
  Comment.find(:all, :include => :post).collect(&:name)
326
442
  Bullet::Association.check_unused_preload_associations
327
443
  Bullet::Association.should be_has_unused_preload_associations
328
444
  end
329
-
445
+
330
446
  it "should dectect no unused preload comments => post" do
331
447
  Comment.find(:all).each do |comment|
332
448
  comment.post.name
@@ -334,7 +450,7 @@ describe Bullet::Association, 'has_many' do
334
450
  Bullet::Association.check_unused_preload_associations
335
451
  Bullet::Association.should_not be_has_unused_preload_associations
336
452
  end
337
-
453
+
338
454
  it "should dectect no unused preload comments => post" do
339
455
  Comment.find(:all, :include => :post).each do |comment|
340
456
  comment.post.name
@@ -346,12 +462,14 @@ describe Bullet::Association, 'has_many' do
346
462
  end
347
463
 
348
464
  describe Bullet::Association, 'has_and_belongs_to_many' do
465
+
466
+ include BulletTestHelper
349
467
  def setup_db
350
468
  ActiveRecord::Schema.define(:version => 1) do
351
469
  create_table :students do |t|
352
470
  t.column :name, :string
353
471
  end
354
-
472
+
355
473
  create_table :teachers do |t|
356
474
  t.column :name, :string
357
475
  end
@@ -378,7 +496,7 @@ describe Bullet::Association, 'has_and_belongs_to_many' do
378
496
  end
379
497
 
380
498
  before(:all) do
381
- setup_db
499
+ silence_logger { setup_db }
382
500
  student1 = Student.create(:name => 'first')
383
501
  student2 = Student.create(:name => 'second')
384
502
  teacher1 = Teacher.create(:name => 'first')
@@ -401,27 +519,27 @@ describe Bullet::Association, 'has_and_belongs_to_many' do
401
519
  Bullet::Association.end_request
402
520
  end
403
521
 
404
- it "should detect unpreload associatoins" do
522
+ it "should detect unpreload associations" do
405
523
  Student.find(:all).each do |student|
406
524
  student.teachers.collect(&:name)
407
525
  end
408
526
  Bullet::Association.should be_has_unpreload_associations
409
527
  end
410
528
 
411
- it "should detect no unpreload associatoins" do
529
+ it "should detect no unpreload associations" do
412
530
  Student.find(:all, :include => :teachers).each do |student|
413
531
  student.teachers.collect(&:name)
414
532
  end
415
533
  Bullet::Association.should_not be_has_unpreload_associations
416
534
  end
417
-
418
- it "should detect unused preload associatoins" do
535
+
536
+ it "should detect unused preload associations" do
419
537
  Student.find(:all, :include => :teachers).collect(&:name)
420
538
  Bullet::Association.check_unused_preload_associations
421
539
  Bullet::Association.should be_has_unused_preload_associations
422
540
  end
423
541
 
424
- it "should detect no unused preload associatoins" do
542
+ it "should detect no unused preload associations" do
425
543
  Student.find(:all).collect(&:name)
426
544
  Bullet::Association.check_unused_preload_associations
427
545
  Bullet::Association.should_not be_has_unused_preload_associations
@@ -429,12 +547,15 @@ describe Bullet::Association, 'has_and_belongs_to_many' do
429
547
  end
430
548
 
431
549
  describe Bullet::Association, 'has_many :through' do
550
+
551
+ include BulletTestHelper
552
+
432
553
  def setup_db
433
554
  ActiveRecord::Schema.define(:version => 1) do
434
555
  create_table :firms do |t|
435
556
  t.column :name, :string
436
557
  end
437
-
558
+
438
559
  create_table :clients do |t|
439
560
  t.column :name, :string
440
561
  end
@@ -468,7 +589,7 @@ describe Bullet::Association, 'has_many :through' do
468
589
  end
469
590
 
470
591
  before(:all) do
471
- setup_db
592
+ silence_logger { setup_db }
472
593
  firm1 = Firm.create(:name => 'first')
473
594
  firm2 = Firm.create(:name => 'second')
474
595
  client1 = Client.create(:name => 'first')
@@ -491,27 +612,27 @@ describe Bullet::Association, 'has_many :through' do
491
612
  Bullet::Association.end_request
492
613
  end
493
614
 
494
- it "should detect unpreload associatoins" do
615
+ it "should detect unpreload associations" do
495
616
  Firm.find(:all).each do |firm|
496
617
  firm.clients.collect(&:name)
497
618
  end
498
619
  Bullet::Association.should be_has_unpreload_associations
499
620
  end
500
621
 
501
- it "should detect no unpreload associatoins" do
622
+ it "should detect no unpreload associations" do
502
623
  Firm.find(:all, :include => :clients).each do |firm|
503
624
  firm.clients.collect(&:name)
504
625
  end
505
626
  Bullet::Association.should_not be_has_unpreload_associations
506
627
  end
507
628
 
508
- it "should detect no unused preload associatoins" do
629
+ it "should detect no unused preload associations" do
509
630
  Firm.find(:all).collect(&:name)
510
631
  Bullet::Association.check_unused_preload_associations
511
632
  Bullet::Association.should_not be_has_unused_preload_associations
512
633
  end
513
634
 
514
- it "should detect unused preload associatoins" do
635
+ it "should detect unused preload associations" do
515
636
  Firm.find(:all, :include => :clients).collect(&:name)
516
637
  Bullet::Association.check_unused_preload_associations
517
638
  Bullet::Association.should be_has_unused_preload_associations
@@ -519,17 +640,25 @@ describe Bullet::Association, 'has_many :through' do
519
640
  end
520
641
 
521
642
  describe Bullet::Association, 'has_many :as' do
643
+
644
+ include BulletTestHelper
645
+
522
646
  def setup_db
523
647
  ActiveRecord::Schema.define(:version => 1) do
524
648
  create_table :votes do |t|
525
649
  t.column :vote, :integer
526
650
  t.references :voteable, :polymorphic => true
527
651
  end
528
-
652
+
529
653
  create_table :users do |t|
530
654
  t.column :name, :string
531
655
  end
532
656
 
657
+ create_table :pets do |t|
658
+ t.column :name, :string
659
+ t.column :user_id, :integer
660
+ end
661
+
533
662
  create_table :news do |t|
534
663
  t.column :name, :string
535
664
  end
@@ -548,6 +677,11 @@ describe Bullet::Association, 'has_many :as' do
548
677
 
549
678
  class User < ActiveRecord::Base
550
679
  has_many :votes, :as => :voteable
680
+ has_many :pets
681
+ end
682
+
683
+ class Pet < ActiveRecord::Base
684
+ belongs_to :user
551
685
  end
552
686
 
553
687
  class News < ActiveRecord::Base
@@ -555,13 +689,25 @@ describe Bullet::Association, 'has_many :as' do
555
689
  end
556
690
 
557
691
  before(:all) do
558
- setup_db
692
+ silence_logger { setup_db }
559
693
  user1 = User.create(:name => 'first')
560
694
  user2 = User.create(:name => 'second')
695
+ user3 = User.create(:name => 'third')
696
+ user4 = User.create(:name => 'fourth')
697
+
698
+ pet1 = User.create(:name => "dog")
699
+ pet2 = User.create(:name => "dog")
700
+ pet3 = User.create(:name => "cat")
701
+ pet4 = User.create(:name => "cat")
702
+
561
703
  user1.votes << Vote.create(:vote => 10)
562
704
  user1.votes << Vote.create(:vote => 20)
563
705
  user2.votes << Vote.create(:vote => 10)
564
706
  user2.votes << Vote.create(:vote => 20)
707
+ user3.votes << Vote.create(:vote => 10)
708
+ user3.votes << Vote.create(:vote => 20)
709
+ user4.votes << Vote.create(:vote => 10)
710
+ user4.votes << Vote.create(:vote => 20)
565
711
 
566
712
  news1 = News.create(:name => 'first')
567
713
  news2 = News.create(:name => 'second')
@@ -582,54 +728,65 @@ describe Bullet::Association, 'has_many :as' do
582
728
  after(:each) do
583
729
  Bullet::Association.end_request
584
730
  end
731
+
732
+ # this happens only when a polymorphic association is included along with another table which is being referenced in the query
733
+ it "should not have unused preloaded associations with conditions" do
734
+ all_users = User.all(:include => :pets)
735
+ users_with_ten_votes = User.all(:include => :votes, :conditions => ["votes.vote = ?", 10])
736
+ users_without_ten_votes = User.all(:conditions => ["users.id not in (?)", users_with_ten_votes.collect(&:id)])
737
+ all_users.collect { |t| t.pets.collect(&:name) }
738
+ Bullet::Association.check_unused_preload_associations
739
+ Bullet::Association.should_not be_unused_preload_associations_for(User, :pets)
740
+ Bullet::Association.should_not be_unused_preload_associations_for(User, :votes)
741
+ end
585
742
 
586
- it "should detect unpreload associatoins" do
743
+ it "should detect unpreload associations" do
587
744
  User.find(:all).each do |user|
588
745
  user.votes.collect(&:vote)
589
746
  end
590
747
  Bullet::Association.should be_has_unpreload_associations
591
748
  end
592
749
 
593
- it "should detect no unpreload associatoins" do
750
+ it "should detect no unpreload associations" do
594
751
  User.find(:all, :include => :votes).each do |user|
595
752
  user.votes.collect(&:vote)
596
753
  end
597
754
  Bullet::Association.should_not be_has_unpreload_associations
598
755
  end
599
756
 
600
- it "should detect unpreload associatoins with voteable" do
757
+ it "should detect unpreload associations with voteable" do
601
758
  Vote.find(:all).each do |vote|
602
759
  vote.voteable.name
603
760
  end
604
761
  Bullet::Association.should be_has_unpreload_associations
605
762
  end
606
763
 
607
- it "should detect no unpreload associatoins with voteable" do
764
+ it "should detect no unpreload associations with voteable" do
608
765
  Vote.find(:all, :include => :voteable).each do |vote|
609
766
  vote.voteable.name
610
767
  end
611
768
  Bullet::Association.should_not be_has_unpreload_associations
612
769
  end
613
-
770
+
614
771
  it "should detect no unused preload associations" do
615
772
  User.find(:all).collect(&:name)
616
773
  Bullet::Association.check_unused_preload_associations
617
774
  Bullet::Association.should_not be_has_unused_preload_associations
618
775
  end
619
-
776
+
620
777
  it "should detect unused preload associations" do
621
778
  User.find(:all, :include => :votes).collect(&:name)
622
779
  Bullet::Association.check_unused_preload_associations
623
780
  Bullet::Association.should be_has_unused_preload_associations
624
781
  end
625
-
782
+
626
783
  it "should detect no unused preload associations with voteable" do
627
784
  Vote.find(:all).collect(&:vote)
628
785
  Bullet::Association.check_unused_preload_associations
629
786
  Bullet::Association.should_not be_has_unused_preload_associations
630
787
  end
631
788
 
632
- it "should detect unused preload associatoins with voteable" do
789
+ it "should detect unused preload associations with voteable" do
633
790
  Vote.find(:all, :include => :voteable).collect(&:vote)
634
791
  Bullet::Association.check_unused_preload_associations
635
792
  Bullet::Association.should be_has_unused_preload_associations
@@ -637,6 +794,9 @@ describe Bullet::Association, 'has_many :as' do
637
794
  end
638
795
 
639
796
  describe Bullet::Association, "has_one" do
797
+
798
+ include BulletTestHelper
799
+
640
800
  def setup_db
641
801
  ActiveRecord::Schema.define(:version => 1) do
642
802
  create_table :companies do |t|
@@ -665,7 +825,7 @@ describe Bullet::Association, "has_one" do
665
825
  end
666
826
 
667
827
  before(:all) do
668
- setup_db
828
+ silence_logger { setup_db }
669
829
 
670
830
  company1 = Company.create(:name => 'first')
671
831
  company2 = Company.create(:name => 'second')
@@ -699,7 +859,7 @@ describe Bullet::Association, "has_one" do
699
859
  end
700
860
  Bullet::Association.should_not be_has_unpreload_associations
701
861
  end
702
-
862
+
703
863
  it "should detect no unused preload association" do
704
864
  Company.find(:all).collect(&:name)
705
865
  Bullet::Association.check_unused_preload_associations
@@ -713,13 +873,14 @@ describe Bullet::Association, "has_one" do
713
873
  end
714
874
  end
715
875
 
716
- describe Bullet::Association, "call one association that in possiable objects" do
876
+ describe Bullet::Association, "call one association that in possible objects" do
877
+ include BulletTestHelper
717
878
  def setup_db
718
879
  ActiveRecord::Schema.define(:version => 1) do
719
880
  create_table :contacts do |t|
720
881
  t.column :name, :string
721
882
  end
722
-
883
+
723
884
  create_table :emails do |t|
724
885
  t.column :name, :string
725
886
  t.column :contact_id, :integer
@@ -732,21 +893,21 @@ describe Bullet::Association, "call one association that in possiable objects" d
732
893
  ActiveRecord::Base.connection.drop_table(table)
733
894
  end
734
895
  end
735
-
896
+
736
897
  class Contact < ActiveRecord::Base
737
898
  has_many :emails
738
899
  end
739
-
900
+
740
901
  class Email < ActiveRecord::Base
741
902
  belongs_to :contact
742
903
  end
743
-
904
+
744
905
  before(:all) do
745
- setup_db
746
-
906
+ silence_logger { setup_db }
907
+
747
908
  contact1 = Contact.create(:name => 'first')
748
909
  contact2 = Contact.create(:name => 'second')
749
-
910
+
750
911
  email1 = contact1.emails.create(:name => 'first')
751
912
  email2 = contact1.emails.create(:name => 'second')
752
913
  email3 = contact2.emails.create(:name => 'third')
@@ -764,10 +925,102 @@ describe Bullet::Association, "call one association that in possiable objects" d
764
925
  after(:each) do
765
926
  Bullet::Association.end_request
766
927
  end
767
-
928
+
768
929
  it "should detect no unpreload association" do
769
930
  Contact.find(:all)
770
931
  Contact.first.emails.collect(&:name)
771
932
  Bullet::Association.should_not be_has_unpreload_associations
772
933
  end
773
934
  end
935
+
936
+ describe Bullet::Association, "STI" do
937
+ include BulletTestHelper
938
+ def setup_db
939
+ ActiveRecord::Schema.define(:version => 1) do
940
+ create_table :documents do |t|
941
+ t.string :name
942
+ t.string :type
943
+ t.integer :parent_id
944
+ t.integer :author_id
945
+ end
946
+
947
+ create_table :authors do |t|
948
+ t.string :name
949
+ end
950
+ end
951
+ end
952
+
953
+ def teardown_db
954
+ ActiveRecord::Base.connection.tables.each do |table|
955
+ ActiveRecord::Base.connection.drop_table(table)
956
+ end
957
+ end
958
+
959
+ class Document < ActiveRecord::Base
960
+ has_many :children, :class_name => "Document", :foreign_key => 'parent_id'
961
+ belongs_to :parent, :class_name => "Document", :foreign_key => 'parent_id'
962
+ belongs_to :author
963
+ end
964
+
965
+ class Page < Document
966
+ end
967
+
968
+ class Folder < Document
969
+ end
970
+
971
+ class Author < ActiveRecord::Base
972
+ has_many :documents
973
+ end
974
+
975
+ before(:all) do
976
+ silence_logger { setup_db }
977
+ author1 = Author.create(:name => 'author1')
978
+ author2 = Author.create(:name => 'author2')
979
+ folder1 = Folder.create(:name => 'folder1', :author_id => author1.id)
980
+ folder2 = Folder.create(:name => 'folder2', :author_id => author2.id)
981
+ page1 = Page.create(:name => 'page1', :parent_id => folder1.id, :author_id => author1.id)
982
+ page2 = Page.create(:name => 'page2', :parent_id => folder1.id, :author_id => author1.id)
983
+ page3 = Page.create(:name => 'page3', :parent_id => folder2.id, :author_id => author2.id)
984
+ page4 = Page.create(:name => 'page4', :parent_id => folder2.id, :author_id => author2.id)
985
+ end
986
+
987
+ before(:each) do
988
+ Bullet::Association.start_request
989
+ end
990
+
991
+ after(:each) do
992
+ Bullet::Association.end_request
993
+ end
994
+
995
+ it "should detect unpreload associations" do
996
+ Page.find(:all).each do |page|
997
+ page.author.name
998
+ end
999
+ Bullet::Association.should be_has_unpreload_associations
1000
+ Bullet::Association.check_unused_preload_associations
1001
+ Bullet::Association.should_not be_has_unused_preload_associations
1002
+ end
1003
+
1004
+ it "should not detect unpreload associations" do
1005
+ Page.find(:all, :include => :author).each do |page|
1006
+ page.author.name
1007
+ end
1008
+ Bullet::Association.should_not be_has_unpreload_associations
1009
+ Bullet::Association.check_unused_preload_associations
1010
+ Bullet::Association.should_not be_has_unused_preload_associations
1011
+ end
1012
+
1013
+ it "should detect unused preload associations" do
1014
+ Page.find(:all, :include => :author).collect(&:name)
1015
+ Bullet::Association.should_not be_has_unpreload_associations
1016
+ Bullet::Association.check_unused_preload_associations
1017
+ Bullet::Association.should be_has_unused_preload_associations
1018
+ end
1019
+
1020
+ it "should not detect unused preload associations" do
1021
+ Page.find(:all).collect(&:name)
1022
+ Bullet::Association.should_not be_has_unpreload_associations
1023
+ Bullet::Association.check_unused_preload_associations
1024
+ Bullet::Association.should_not be_has_unused_preload_associations
1025
+ end
1026
+ end
@@ -13,3 +13,38 @@ require File.expand_path(File.join(File.dirname(__FILE__), '../lib/bullet/counte
13
13
  require File.expand_path(File.join(File.dirname(__FILE__), '../lib/bullet'))
14
14
  require File.expand_path(File.join(File.dirname(__FILE__), '../lib/bulletware'))
15
15
  Bullet.enable = true
16
+
17
+ module BulletTestHelper
18
+ def silence_logger(&block)
19
+ orig_stdout = $stdout
20
+ $stdout = StringIO.new
21
+ block.call
22
+ $stdout = orig_stdout
23
+ end
24
+ end
25
+
26
+ module Bullet
27
+ class Association
28
+ class <<self
29
+ # returns true if all associations are preloaded
30
+ def completely_preloading_associations?
31
+ !has_unpreload_associations?
32
+ end
33
+
34
+ # returns true if a given object has a specific association
35
+ def creating_object_association_for?(object, association)
36
+ object_associations[object].present? && object_associations[object].include?(association)
37
+ end
38
+
39
+ # returns true if a given class includes the specific unpreloaded association
40
+ def detecting_unpreloaded_association_for?(klazz, association)
41
+ unpreload_associations[klazz].present? && unpreload_associations[klazz].include?(association)
42
+ end
43
+
44
+ # returns true if the given class includes the specific unused preloaded association
45
+ def unused_preload_associations_for?(klazz, association)
46
+ unused_preload_associations[klazz].present? && unused_preload_associations[klazz].include?(association)
47
+ end
48
+ end
49
+ end
50
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flyerhzm-bullet
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.8
4
+ version: 1.5.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-09-22 00:00:00 -07:00
12
+ date: 2009-09-24 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -69,6 +69,6 @@ signing_key:
69
69
  specification_version: 3
70
70
  summary: A plugin to kill N+1 queries and unused eager loading
71
71
  test_files:
72
- - spec/bullet_association_spec.rb
73
72
  - spec/bullet_counter_spec.rb
74
73
  - spec/spec_helper.rb
74
+ - spec/bullet_association_spec.rb