bullet 1.6.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.
@@ -0,0 +1,101 @@
1
+ module Bullet
2
+ class Counter
3
+ class <<self
4
+ include Bullet::Notification
5
+
6
+ def start_request
7
+ end
8
+
9
+ def end_request
10
+ clear
11
+ end
12
+
13
+ def clear
14
+ @@klazz_associations = nil
15
+ @@possible_objects = nil
16
+ @@impossible_objects = nil
17
+ end
18
+
19
+ def need_counter_caches?
20
+ !klazz_associations.empty?
21
+ end
22
+
23
+ def notification?
24
+ need_counter_caches?
25
+ end
26
+
27
+ def notification_response
28
+ response = []
29
+ if need_counter_caches?
30
+ response << counter_cache_messages.join("\n")
31
+ end
32
+ response
33
+ end
34
+
35
+ def console_title
36
+ title = ["Need Counter Cache"]
37
+ end
38
+
39
+ def log_messages(path = nil)
40
+ [counter_cache_messages(path)]
41
+ end
42
+
43
+ def add_counter_cache(object, associations)
44
+ klazz = object.class
45
+ if (!possible_objects[klazz].nil? and possible_objects[klazz].include?(object)) and
46
+ (impossible_objects[klazz].nil? or !impossible_objects[klazz].include?(object))
47
+ klazz_associations[klazz] ||= []
48
+ klazz_associations[klazz] << associations
49
+ unique(klazz_associations[klazz])
50
+ end
51
+ end
52
+
53
+ def add_possible_objects(objects)
54
+ klazz = objects.first.class
55
+ possible_objects[klazz] ||= []
56
+ possible_objects[klazz] << objects
57
+ unique(possible_objects[klazz])
58
+ end
59
+
60
+ def add_impossible_object(object)
61
+ klazz = object.class
62
+ impossible_objects[klazz] ||= []
63
+ impossible_objects[klazz] << object
64
+ impossible_objects[klazz].uniq!
65
+ end
66
+
67
+ private
68
+ def counter_cache_messages(path = nil)
69
+ messages = []
70
+ klazz_associations.each do |klazz, associations|
71
+ messages << [
72
+ "Need Counter Cache",
73
+ " #{klazz} => [#{associations.map(&:inspect).join(', ')}]"
74
+ ]
75
+ end
76
+ messages
77
+ end
78
+
79
+ def unique(array)
80
+ array.flatten!
81
+ array.uniq!
82
+ end
83
+
84
+ def call_stack_messages
85
+ []
86
+ end
87
+
88
+ def klazz_associations
89
+ @@klazz_associations ||= {}
90
+ end
91
+
92
+ def possible_objects
93
+ @@possible_objects ||= {}
94
+ end
95
+
96
+ def impossible_objects
97
+ @@impossible_objects ||= {}
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,9 @@
1
+ module Bullet
2
+ class BulletLogger < Logger
3
+ LOG_FILE = ::RAILS_ROOT + '/log/bullet.log'
4
+
5
+ def format_message(severity, timestamp, progname, msg)
6
+ "#{timestamp.to_formatted_s(:db)}[#{severity}] #{msg}\n"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,83 @@
1
+ module Bullet
2
+ class NotificationError < StandardError
3
+ end
4
+
5
+ module Notification
6
+ def notification?
7
+ end
8
+
9
+ def notification_response
10
+ end
11
+
12
+ def console_title
13
+ end
14
+
15
+ def log_message(path = nil)
16
+ end
17
+
18
+ def javascript_notification
19
+ str = ''
20
+ if Bullet.alert || Bullet.console
21
+ response = notification_response
22
+ end
23
+ unless response.blank?
24
+ if Bullet.alert
25
+ str << wrap_js_association("alert(#{response.join("\n").inspect});")
26
+ end
27
+ if Bullet.console
28
+ code = <<-CODE
29
+ if (typeof(console) !== 'undefined') {
30
+
31
+ if (console.groupCollapsed && console.groupEnd && console.log) {
32
+
33
+ console.groupCollapsed(#{console_title.join(', ').inspect});
34
+ console.log(#{response.join("\n").inspect});
35
+ console.log(#{call_stack_messages.join("\n").inspect});
36
+ console.groupEnd();
37
+
38
+ } else if (console.log) {
39
+
40
+ console.log(#{response.join("\n").inspect});
41
+ }
42
+ }
43
+ CODE
44
+ str << wrap_js_association(code)
45
+ end
46
+ end
47
+ str
48
+ end
49
+
50
+ def growl_notification
51
+ if Bullet.growl
52
+ response = notification_response
53
+ unless response.blank?
54
+ begin
55
+ growl = Growl.new('localhost', 'ruby-growl', ['Bullet Notification'], nil, Bullet.growl_password)
56
+ growl.notify('Bullet Notification', 'Bullet Notification', response.join("\n"))
57
+ rescue
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ def log_notification(path)
64
+ if Bullet.bullet_logger || Bullet.rails_logger
65
+ Rails.logger.warn '' if Bullet.rails_logger
66
+ messages = log_messages(path)
67
+ messages.each do |message|
68
+ Bullet.logger.info(message.join("\n")) if Bullet.bullet_logger
69
+ Rails.logger.warn(message.join("\n")) if Bullet.rails_logger
70
+ end
71
+ Bullet.logger_file.flush if Bullet.bullet_logger
72
+ end
73
+ end
74
+
75
+ private
76
+ def wrap_js_association(message)
77
+ str = ''
78
+ str << "<script type=\"text/javascript\">/*<![CDATA[*/"
79
+ str << message
80
+ str << "/*]]>*/</script>\n"
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,42 @@
1
+ class Bulletware
2
+ def initialize(app)
3
+ @app = app
4
+ end
5
+
6
+ def call(env)
7
+ return @app.call(env) unless Bullet.enable?
8
+
9
+ Bullet.start_request
10
+ status, headers, response = @app.call(env)
11
+ return [status, headers, response] if empty?(response)
12
+
13
+ if Bullet.notification?
14
+ if check_html?(headers, response)
15
+ response_body = response.body << Bullet.javascript_notification
16
+ headers['Content-Length'] = response_body.length.to_s
17
+ end
18
+
19
+ Bullet.growl_notification
20
+ Bullet.log_notification(env['PATH_INFO'])
21
+ end
22
+ response_body ||= response.body
23
+ Bullet.end_request
24
+ no_browser_cache(headers) if Bullet.disable_browser_cache
25
+ [status, headers, response_body]
26
+ end
27
+
28
+ # fix issue if response's body is a Proc
29
+ def empty?(response)
30
+ (response.is_a?(Array) && response.empty?) || !response.body.is_a?(String) || response.body.empty?
31
+ end
32
+
33
+ def check_html?(headers, response)
34
+ !headers['Content-Type'].nil? and headers['Content-Type'].include? 'text/html' and response.body =~ %r{<html.*</html>}m
35
+ end
36
+
37
+ def no_browser_cache(headers)
38
+ headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
39
+ headers["Pragma"] = "no-cache"
40
+ headers["Expires"] = "Wed, 09 Sep 2009 09:09:09 GMT"
41
+ end
42
+ end
@@ -0,0 +1 @@
1
+ require 'bullet'
@@ -0,0 +1,1044 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
4
+
5
+ describe Bullet::Association, 'has_many' do
6
+
7
+ include BulletTestHelper
8
+
9
+ def setup_db
10
+ ActiveRecord::Schema.define(:version => 1) do
11
+ create_table :categories do |t|
12
+ t.column :name, :string
13
+ end
14
+
15
+ create_table :posts do |t|
16
+ t.column :name, :string
17
+ t.column :category_id, :integer
18
+ t.column :writer_id, :integer
19
+ end
20
+
21
+ create_table :comments do |t|
22
+ t.column :name, :string
23
+ t.column :post_id, :integer
24
+ t.column :author_id, :integer
25
+ end
26
+
27
+ create_table :entries do |t|
28
+ t.column :name, :string
29
+ t.column :category_id, :integer
30
+ end
31
+
32
+ create_table :base_users do |t|
33
+ t.column :name, :string
34
+ t.column :type, :string
35
+ t.column :newspaper_id, :integer
36
+ end
37
+ create_table :newspapers do |t|
38
+ t.column :name, :string
39
+ end
40
+ end
41
+ end
42
+
43
+ def teardown_db
44
+ ActiveRecord::Base.connection.tables.each do |table|
45
+ ActiveRecord::Base.connection.drop_table(table)
46
+ end
47
+ end
48
+
49
+ class Category < ActiveRecord::Base
50
+ has_many :posts
51
+ has_many :entries
52
+ end
53
+
54
+ class Post < ActiveRecord::Base
55
+ belongs_to :category
56
+ has_many :comments
57
+ belongs_to :writer
58
+
59
+
60
+ named_scope :preload_posts, lambda { {:include => :comments} }
61
+ named_scope :in_category_name, lambda { |name|
62
+ { :conditions => ['categories.name = ?', name], :include => :category }
63
+ }
64
+ end
65
+
66
+ class Entry < ActiveRecord::Base
67
+ belongs_to :category
68
+ end
69
+
70
+ class Comment < ActiveRecord::Base
71
+ belongs_to :post
72
+ belongs_to :author, :class_name => "BaseUser"
73
+ end
74
+
75
+ class BaseUser < ActiveRecord::Base
76
+ has_many :comments
77
+ has_many :posts
78
+ belongs_to :newspaper
79
+ end
80
+
81
+ class Newspaper < ActiveRecord::Base
82
+ has_many :writers, :class_name => "BaseUser"
83
+ end
84
+
85
+ class Writer < BaseUser
86
+ end
87
+
88
+ before(:all) do
89
+ silence_logger { setup_db }
90
+
91
+ newspaper1 = Newspaper.create(:name => "First Newspaper")
92
+ newspaper2 = Newspaper.create(:name => "Second Newspaper")
93
+
94
+ writer1 = Writer.create(:name => 'first', :newspaper => newspaper1)
95
+ writer2 = Writer.create(:name => 'second', :newspaper => newspaper2)
96
+ user1 = BaseUser.create(:name => 'third', :newspaper => newspaper1)
97
+ user2 = BaseUser.create(:name => 'fourth', :newspaper => newspaper2)
98
+
99
+
100
+ category1 = Category.create(:name => 'first')
101
+ category2 = Category.create(:name => 'second')
102
+
103
+ post1 = category1.posts.create(:name => 'first', :writer => writer1)
104
+ post1a = category1.posts.create(:name => 'like first', :writer => writer2)
105
+ post2 = category2.posts.create(:name => 'second', :writer => writer2)
106
+
107
+ comment1 = post1.comments.create(:name => 'first', :author => writer1)
108
+ comment2 = post1.comments.create(:name => 'first2', :author => writer1)
109
+ comment3 = post1.comments.create(:name => 'first3', :author => writer1)
110
+ comment4 = post1.comments.create(:name => 'second', :author => writer2)
111
+ comment8 = post1a.comments.create(:name => "like first 1", :author => writer1)
112
+ comment9 = post1a.comments.create(:name => "like first 2", :author => writer2)
113
+ comment5 = post2.comments.create(:name => 'third', :author => user1)
114
+ comment6 = post2.comments.create(:name => 'fourth', :author => user2)
115
+ comment7 = post2.comments.create(:name => 'fourth', :author => writer1)
116
+
117
+ entry1 = category1.entries.create(:name => 'first')
118
+ entry2 = category1.entries.create(:name => 'second')
119
+ end
120
+
121
+ after(:all) do
122
+ teardown_db
123
+ end
124
+
125
+ before(:each) do
126
+ Bullet::Association.start_request
127
+ end
128
+
129
+ after(:each) do
130
+ Bullet::Association.end_request
131
+ end
132
+
133
+ context "for unused cases" do
134
+ #If you have the same record created twice with different includes
135
+ # the hash value get's accumulated includes, which leads to false Unused eager loading
136
+ it "should not incorrectly mark associations as unused when multiple object instances" do
137
+ comments_with_author = Comment.all(:include => :author)
138
+ comments_with_post = Comment.all(:include => :post)
139
+ comments_with_author.each { |c| c.author.name }
140
+ comments_with_author.each { |c| c.post.name }
141
+ Bullet::Association.check_unused_preload_associations
142
+ Bullet::Association.should be_unused_preload_associations_for(Comment, :post)
143
+ Bullet::Association.should be_detecting_unpreloaded_association_for(Comment, :post)
144
+ end
145
+
146
+ # same as above with different Models being queried
147
+ it "should not incorrectly mark associations as unused when multiple object instances different Model" do
148
+ post_with_comments = Post.all(:include => :comments)
149
+ comments_with_author = Comment.all(:include => :author)
150
+ post_with_comments.each { |p| p.comments.first.author.name }
151
+ comments_with_author.each { |c| c.name }
152
+ Bullet::Association.check_unused_preload_associations
153
+ Bullet::Association.should be_unused_preload_associations_for(Comment, :author)
154
+ Bullet::Association.should be_detecting_unpreloaded_association_for(Comment, :author)
155
+ end
156
+
157
+ # this test passes right now. But is a regression test to ensure that if only a small set of returned records
158
+ # is not used that a unused preload association error is not generated
159
+ it "should not have unused when small set of returned records are discarded" do
160
+ comments_with_author = Comment.all(:include => :author)
161
+ comment_collection = comments_with_author.first(2)
162
+ comment_collection.collect { |com| com.author.name }
163
+ Bullet::Association.check_unused_preload_associations
164
+ Bullet::Association.should_not be_unused_preload_associations_for(Comment, :author)
165
+ end
166
+ end
167
+
168
+
169
+ context "comments => posts => category" do
170
+
171
+ # this happens because the post isn't a possible object even though the writer is access through the post
172
+ # which leads to an 1+N queries
173
+ it "should detect unpreloaded writer" do
174
+ Comment.all(:include => [:author, :post],
175
+ :conditions => ["base_users.id = ?", BaseUser.first]).each do |com|
176
+ com.post.writer.name
177
+ end
178
+ Bullet::Association.should be_detecting_unpreloaded_association_for(Post, :writer)
179
+ end
180
+
181
+ # this happens because the comment doesn't break down the hash into keys
182
+ # properly creating an association from comment to post
183
+ it "should detect preload of comment => post" do
184
+ comments = Comment.all(:include => [:author, {:post => :writer}],
185
+ :conditions => ["base_users.id = ?", BaseUser.first]).each do |com|
186
+ com.post.writer.name
187
+ end
188
+ Bullet::Association.should_not be_detecting_unpreloaded_association_for(Comment, :post)
189
+ Bullet::Association.should be_completely_preloading_associations
190
+ end
191
+
192
+ it "should detect preload of post => writer" do
193
+ comments = Comment.all(:include => [:author, {:post => :writer}],
194
+ :conditions => ["base_users.id = ?", BaseUser.first]).each do |com|
195
+ com.post.writer.name
196
+ end
197
+ Bullet::Association.should be_creating_object_association_for(comments.first, :author)
198
+ Bullet::Association.should_not be_detecting_unpreloaded_association_for(Post, :writer)
199
+ Bullet::Association.should be_completely_preloading_associations
200
+ end
201
+
202
+ # To flyerhzm: This does not detect that newspaper is unpreloaded. The association is
203
+ # not within possible objects, and thus cannot be detected as unpreloaded
204
+ it "should detect unpreloading of writer => newspaper" do
205
+ comments = Comment.all(:include => {:post => :writer}, :conditions => "posts.name like '%first%'").each do |com|
206
+ com.post.writer.newspaper.name
207
+ end
208
+ Bullet::Association.should be_detecting_unpreloaded_association_for(Writer, :newspaper)
209
+ end
210
+
211
+ # when we attempt to access category, there is an infinite overflow because load_target is hijacked leading to
212
+ # a repeating loop of calls in this test
213
+ it "should not raise a stack error from posts to category" do
214
+ lambda {
215
+ Comment.all(:include => {:post => :category}).each do |com|
216
+ com.post.category
217
+ end
218
+ }.should_not raise_error(SystemStackError)
219
+ end
220
+ end
221
+
222
+ context "post => comments" do
223
+ it "should detect preload with post => comments" do
224
+ Post.find(:all, :include => :comments).each do |post|
225
+ post.comments.collect(&:name)
226
+ end
227
+ Bullet::Association.should_not be_has_unpreload_associations
228
+ end
229
+
230
+ it "should detect no preload post => comments" do
231
+ Post.find(:all).each do |post|
232
+ post.comments.collect(&:name)
233
+ end
234
+ Bullet::Association.should be_has_unpreload_associations
235
+ end
236
+
237
+ it "should detect unused preload post => comments for post" do
238
+ Post.find(:all, :include => :comments).collect(&:name)
239
+ Bullet::Association.check_unused_preload_associations
240
+ Bullet::Association.should be_has_unused_preload_associations
241
+ end
242
+
243
+ it "should detect no unused preload post => comments for post" do
244
+ Post.find(:all).collect(&:name)
245
+ Bullet::Association.check_unused_preload_associations
246
+ Bullet::Association.should_not be_has_unused_preload_associations
247
+ end
248
+
249
+ it "should detect no unused preload post => comments for comment" do
250
+ Post.find(:all).each do |post|
251
+ post.comments.collect(&:name)
252
+ end
253
+ Bullet::Association.check_unused_preload_associations
254
+ Bullet::Association.should_not be_has_unused_preload_associations
255
+
256
+ Bullet::Association.end_request
257
+ Bullet::Association.start_request
258
+
259
+ Post.find(:all).each do |post|
260
+ post.comments.collect(&:name)
261
+ end
262
+ Bullet::Association.check_unused_preload_associations
263
+ Bullet::Association.should_not be_has_unused_preload_associations
264
+ end
265
+ end
266
+
267
+ context "category => posts => comments" do
268
+ it "should detect preload with category => posts => comments" do
269
+ Category.find(:all, :include => {:posts => :comments}).each do |category|
270
+ category.posts.each do |post|
271
+ post.comments.collect(&:name)
272
+ end
273
+ end
274
+ Bullet::Association.should_not be_has_unpreload_associations
275
+ end
276
+
277
+ it "should detect preload category => posts, but no post => comments" do
278
+ Category.find(:all, :include => :posts).each do |category|
279
+ category.posts.each do |post|
280
+ post.comments.collect(&:name)
281
+ end
282
+ end
283
+ Bullet::Association.should be_has_unpreload_associations
284
+ end
285
+
286
+ it "should detect no preload category => posts => comments" do
287
+ Category.find(:all).each do |category|
288
+ category.posts.each do |post|
289
+ post.comments.collect(&:name)
290
+ end
291
+ end
292
+ Bullet::Association.should be_has_unpreload_associations
293
+ end
294
+
295
+ it "should detect unused preload with category => posts => comments" do
296
+ Category.find(:all, :include => {:posts => :comments}).collect(&:name)
297
+ Bullet::Association.check_unused_preload_associations
298
+ Bullet::Association.should be_has_unused_preload_associations
299
+ end
300
+
301
+ it "should detect unused preload with post => commnets, no category => posts" do
302
+ Category.find(:all, :include => {:posts => :comments}).each do |category|
303
+ category.posts.collect(&:name)
304
+ end
305
+ Bullet::Association.check_unused_preload_associations
306
+ Bullet::Association.should be_has_unused_preload_associations
307
+ end
308
+
309
+ it "should no detect preload with category => posts => comments" do
310
+ Category.find(:all).each do |category|
311
+ category.posts.each do |post|
312
+ post.comments.collect(&:name)
313
+ end
314
+ end
315
+ Bullet::Association.check_unused_preload_associations
316
+ Bullet::Association.should_not be_has_unused_preload_associations
317
+ end
318
+ end
319
+
320
+ context "category => posts, category => entries" do
321
+ it "should detect preload with category => [posts, entries]" do
322
+ Category.find(:all, :include => [:posts, :entries]).each do |category|
323
+ category.posts.collect(&:name)
324
+ category.entries.collect(&:name)
325
+ end
326
+ Bullet::Association.should_not be_has_unpreload_associations
327
+ end
328
+
329
+ it "should detect preload with category => posts, but no category => entries" do
330
+ Category.find(:all, :include => :posts).each do |category|
331
+ category.posts.collect(&:name)
332
+ category.entries.collect(&:name)
333
+ end
334
+ Bullet::Association.should be_has_unpreload_associations
335
+ end
336
+
337
+ it "should detect no preload with category => [posts, entries]" do
338
+ Category.find(:all).each do |category|
339
+ category.posts.collect(&:name)
340
+ category.entries.collect(&:name)
341
+ end
342
+ Bullet::Association.should be_has_unpreload_associations
343
+ end
344
+
345
+ it "should detect unused with category => [posts, entries]" do
346
+ Category.find(:all, :include => [:posts, :entries]).collect(&:name)
347
+ Bullet::Association.check_unused_preload_associations
348
+ Bullet::Association.should be_has_unused_preload_associations
349
+ end
350
+
351
+ it "should detect unused preload with category => entries, but no category => posts" do
352
+ Category.find(:all, :include => [:posts, :entries]).each do |category|
353
+ category.posts.collect(&:name)
354
+ end
355
+ Bullet::Association.check_unused_preload_associations
356
+ Bullet::Association.should be_has_unused_preload_associations
357
+ end
358
+
359
+ it "should detect no unused preload" do
360
+ Category.find(:all).each do |category|
361
+ category.posts.collect(&:name)
362
+ category.entries.collect(&:name)
363
+ end
364
+ Bullet::Association.check_unused_preload_associations
365
+ Bullet::Association.should_not be_has_unused_preload_associations
366
+ end
367
+ end
368
+
369
+ context "no preload" do
370
+ it "should no preload only display only one post => comment" do
371
+ Post.find(:all, :include => :comments).each do |post|
372
+ post.comments.first.name
373
+ end
374
+ Bullet::Association.should_not be_has_unpreload_associations
375
+ end
376
+
377
+ it "should no preload only one post => commnets" do
378
+ Post.first.comments.collect(&:name)
379
+ Bullet::Association.should_not be_has_unpreload_associations
380
+ end
381
+ end
382
+
383
+ context "named_scope for_category_name" do
384
+ it "should detect preload with post => category" do
385
+ Post.in_category_name('first').all.each do |post|
386
+ post.category.name
387
+ end
388
+ Bullet::Association.should_not be_has_unpreload_associations
389
+ end
390
+
391
+ it "should not be unused preload post => category" do
392
+ Post.in_category_name('first').all.collect(&:name)
393
+ Bullet::Association.should_not be_has_unpreload_associations
394
+ Bullet::Association.check_unused_preload_associations
395
+ Bullet::Association.should_not be_has_unused_preload_associations
396
+ end
397
+ end
398
+
399
+ context "named_scope preload_posts" do
400
+ it "should no preload post => comments with named_scope" do
401
+ Post.preload_posts.each do |post|
402
+ post.comments.collect(&:name)
403
+ end
404
+ Bullet::Association.should_not be_has_unpreload_associations
405
+ end
406
+
407
+ it "should unused preload with named_scope" do
408
+ Post.preload_posts.collect(&:name)
409
+ Bullet::Association.should_not be_has_unpreload_associations
410
+ Bullet::Association.check_unused_preload_associations
411
+ Bullet::Association.should be_has_unused_preload_associations
412
+ end
413
+ end
414
+
415
+ context "no unused" do
416
+ it "should no unused only display only one post => comment" do
417
+ Post.find(:all, :include => :comments).each do |post|
418
+ i = 0
419
+ post.comments.each do |comment|
420
+ if i == 0
421
+ comment.name
422
+ else
423
+ i += 1
424
+ end
425
+ end
426
+ end
427
+ Bullet::Association.check_unused_preload_associations
428
+ Bullet::Association.should_not be_has_unused_preload_associations
429
+ end
430
+ end
431
+
432
+ context "belongs_to" do
433
+ it "should preload comments => post" do
434
+ Comment.find(:all).each do |comment|
435
+ comment.post.name
436
+ end
437
+ Bullet::Association.should be_has_unpreload_associations
438
+ end
439
+
440
+ it "should no preload comment => post" do
441
+ Comment.first.post.name
442
+ Bullet::Association.should_not be_has_unpreload_associations
443
+ end
444
+
445
+ it "should no preload comments => post" do
446
+ Comment.find(:all, :include => :post).each do |comment|
447
+ comment.post.name
448
+ end
449
+ Bullet::Association.should_not be_has_unpreload_associations
450
+ end
451
+
452
+ it "should detect no unused preload comments => post" do
453
+ Comment.find(:all).collect(&:name)
454
+ Bullet::Association.check_unused_preload_associations
455
+ Bullet::Association.should_not be_has_unused_preload_associations
456
+ end
457
+
458
+ it "should detect unused preload comments => post" do
459
+ Comment.find(:all, :include => :post).collect(&:name)
460
+ Bullet::Association.check_unused_preload_associations
461
+ Bullet::Association.should be_has_unused_preload_associations
462
+ end
463
+
464
+ it "should dectect no unused preload comments => post" do
465
+ Comment.find(:all).each do |comment|
466
+ comment.post.name
467
+ end
468
+ Bullet::Association.check_unused_preload_associations
469
+ Bullet::Association.should_not be_has_unused_preload_associations
470
+ end
471
+
472
+ it "should dectect no unused preload comments => post" do
473
+ Comment.find(:all, :include => :post).each do |comment|
474
+ comment.post.name
475
+ end
476
+ Bullet::Association.check_unused_preload_associations
477
+ Bullet::Association.should_not be_has_unused_preload_associations
478
+ end
479
+ end
480
+ end
481
+
482
+ describe Bullet::Association, 'has_and_belongs_to_many' do
483
+
484
+ include BulletTestHelper
485
+ def setup_db
486
+ ActiveRecord::Schema.define(:version => 1) do
487
+ create_table :students do |t|
488
+ t.column :name, :string
489
+ end
490
+
491
+ create_table :teachers do |t|
492
+ t.column :name, :string
493
+ end
494
+
495
+ create_table :students_teachers, :id => false do |t|
496
+ t.column :student_id, :integer
497
+ t.column :teacher_id, :integer
498
+ end
499
+ end
500
+ end
501
+
502
+ def teardown_db
503
+ ActiveRecord::Base.connection.tables.each do |table|
504
+ ActiveRecord::Base.connection.drop_table(table)
505
+ end
506
+ end
507
+
508
+ class Student < ActiveRecord::Base
509
+ has_and_belongs_to_many :teachers
510
+ end
511
+
512
+ class Teacher < ActiveRecord::Base
513
+ has_and_belongs_to_many :students
514
+ end
515
+
516
+ before(:all) do
517
+ silence_logger { setup_db }
518
+ student1 = Student.create(:name => 'first')
519
+ student2 = Student.create(:name => 'second')
520
+ teacher1 = Teacher.create(:name => 'first')
521
+ teacher2 = Teacher.create(:name => 'second')
522
+ student1.teachers = [teacher1, teacher2]
523
+ student2.teachers = [teacher1, teacher2]
524
+ teacher1.students << student1
525
+ teacher2.students << student2
526
+ end
527
+
528
+ after(:all) do
529
+ teardown_db
530
+ end
531
+
532
+ before(:each) do
533
+ Bullet::Association.start_request
534
+ end
535
+
536
+ after(:each) do
537
+ Bullet::Association.end_request
538
+ end
539
+
540
+ it "should detect unpreload associations" do
541
+ Student.find(:all).each do |student|
542
+ student.teachers.collect(&:name)
543
+ end
544
+ Bullet::Association.should be_has_unpreload_associations
545
+ end
546
+
547
+ it "should detect no unpreload associations" do
548
+ Student.find(:all, :include => :teachers).each do |student|
549
+ student.teachers.collect(&:name)
550
+ end
551
+ Bullet::Association.should_not be_has_unpreload_associations
552
+ end
553
+
554
+ it "should detect unused preload associations" do
555
+ Student.find(:all, :include => :teachers).collect(&:name)
556
+ Bullet::Association.check_unused_preload_associations
557
+ Bullet::Association.should be_has_unused_preload_associations
558
+ end
559
+
560
+ it "should detect no unused preload associations" do
561
+ Student.find(:all).collect(&:name)
562
+ Bullet::Association.check_unused_preload_associations
563
+ Bullet::Association.should_not be_has_unused_preload_associations
564
+ end
565
+ end
566
+
567
+ describe Bullet::Association, 'has_many :through' do
568
+
569
+ include BulletTestHelper
570
+
571
+ def setup_db
572
+ ActiveRecord::Schema.define(:version => 1) do
573
+ create_table :firms do |t|
574
+ t.column :name, :string
575
+ end
576
+
577
+ create_table :clients do |t|
578
+ t.column :name, :string
579
+ end
580
+
581
+ create_table :relations do |t|
582
+ t.column :firm_id, :integer
583
+ t.column :client_id, :integer
584
+ end
585
+ end
586
+ end
587
+
588
+ def teardown_db
589
+ ActiveRecord::Base.connection.tables.each do |table|
590
+ ActiveRecord::Base.connection.drop_table(table)
591
+ end
592
+ end
593
+
594
+ class Firm < ActiveRecord::Base
595
+ has_many :relations
596
+ has_many :clients, :through => :relations
597
+ end
598
+
599
+ class Client < ActiveRecord::Base
600
+ has_many :relations
601
+ has_many :firms, :through => :relations
602
+ end
603
+
604
+ class Relation < ActiveRecord::Base
605
+ belongs_to :firm
606
+ belongs_to :client
607
+ end
608
+
609
+ before(:all) do
610
+ silence_logger { setup_db }
611
+ firm1 = Firm.create(:name => 'first')
612
+ firm2 = Firm.create(:name => 'second')
613
+ client1 = Client.create(:name => 'first')
614
+ client2 = Client.create(:name => 'second')
615
+ firm1.clients = [client1, client2]
616
+ firm2.clients = [client1, client2]
617
+ client1.firms << firm1
618
+ client2.firms << firm2
619
+ end
620
+
621
+ after(:all) do
622
+ teardown_db
623
+ end
624
+
625
+ before(:each) do
626
+ Bullet::Association.start_request
627
+ end
628
+
629
+ after(:each) do
630
+ Bullet::Association.end_request
631
+ end
632
+
633
+ it "should detect unpreload associations" do
634
+ Firm.find(:all).each do |firm|
635
+ firm.clients.collect(&:name)
636
+ end
637
+ Bullet::Association.should be_has_unpreload_associations
638
+ end
639
+
640
+ it "should detect no unpreload associations" do
641
+ Firm.find(:all, :include => :clients).each do |firm|
642
+ firm.clients.collect(&:name)
643
+ end
644
+ Bullet::Association.should_not be_has_unpreload_associations
645
+ end
646
+
647
+ it "should detect no unused preload associations" do
648
+ Firm.find(:all).collect(&:name)
649
+ Bullet::Association.check_unused_preload_associations
650
+ Bullet::Association.should_not be_has_unused_preload_associations
651
+ end
652
+
653
+ it "should detect unused preload associations" do
654
+ Firm.find(:all, :include => :clients).collect(&:name)
655
+ Bullet::Association.check_unused_preload_associations
656
+ Bullet::Association.should be_has_unused_preload_associations
657
+ end
658
+ end
659
+
660
+ describe Bullet::Association, 'has_many :as' do
661
+
662
+ include BulletTestHelper
663
+
664
+ def setup_db
665
+ ActiveRecord::Schema.define(:version => 1) do
666
+ create_table :votes do |t|
667
+ t.column :vote, :integer
668
+ t.references :voteable, :polymorphic => true
669
+ end
670
+
671
+ create_table :users do |t|
672
+ t.column :name, :string
673
+ end
674
+
675
+ create_table :pets do |t|
676
+ t.column :name, :string
677
+ t.column :user_id, :integer
678
+ end
679
+
680
+ create_table :news do |t|
681
+ t.column :name, :string
682
+ end
683
+ end
684
+ end
685
+
686
+ def teardown_db
687
+ ActiveRecord::Base.connection.tables.each do |table|
688
+ ActiveRecord::Base.connection.drop_table(table)
689
+ end
690
+ end
691
+
692
+ class Vote < ActiveRecord::Base
693
+ belongs_to :voteable, :polymorphic => true
694
+ end
695
+
696
+ class User < ActiveRecord::Base
697
+ has_many :votes, :as => :voteable
698
+ has_many :pets
699
+ end
700
+
701
+ class Pet < ActiveRecord::Base
702
+ belongs_to :user
703
+ end
704
+
705
+ class News < ActiveRecord::Base
706
+ has_many :votes, :as => :voteable
707
+ end
708
+
709
+ before(:all) do
710
+ silence_logger { setup_db }
711
+ user1 = User.create(:name => 'first')
712
+ user2 = User.create(:name => 'second')
713
+ user3 = User.create(:name => 'third')
714
+ user4 = User.create(:name => 'fourth')
715
+
716
+ pet1 = User.create(:name => "dog")
717
+ pet2 = User.create(:name => "dog")
718
+ pet3 = User.create(:name => "cat")
719
+ pet4 = User.create(:name => "cat")
720
+
721
+ user1.votes << Vote.create(:vote => 10)
722
+ user1.votes << Vote.create(:vote => 20)
723
+ user2.votes << Vote.create(:vote => 10)
724
+ user2.votes << Vote.create(:vote => 20)
725
+ user3.votes << Vote.create(:vote => 10)
726
+ user3.votes << Vote.create(:vote => 20)
727
+ user4.votes << Vote.create(:vote => 10)
728
+ user4.votes << Vote.create(:vote => 20)
729
+
730
+ news1 = News.create(:name => 'first')
731
+ news2 = News.create(:name => 'second')
732
+ news1.votes << Vote.create(:vote => 10)
733
+ news1.votes << Vote.create(:vote => 20)
734
+ news2.votes << Vote.create(:vote => 10)
735
+ news2.votes << Vote.create(:vote => 20)
736
+ end
737
+
738
+ after(:all) do
739
+ teardown_db
740
+ end
741
+
742
+ before(:each) do
743
+ Bullet::Association.start_request
744
+ end
745
+
746
+ after(:each) do
747
+ Bullet::Association.end_request
748
+ end
749
+
750
+ # this happens only when a polymorphic association is included along with another table which is being referenced in the query
751
+ it "should not have unused preloaded associations with conditions" do
752
+ all_users = User.all(:include => :pets)
753
+ users_with_ten_votes = User.all(:include => :votes, :conditions => ["votes.vote = ?", 10])
754
+ users_without_ten_votes = User.all(:conditions => ["users.id not in (?)", users_with_ten_votes.collect(&:id)])
755
+ all_users.collect { |t| t.pets.collect(&:name) }
756
+ Bullet::Association.check_unused_preload_associations
757
+ Bullet::Association.should_not be_unused_preload_associations_for(User, :pets)
758
+ Bullet::Association.should_not be_unused_preload_associations_for(User, :votes)
759
+ end
760
+
761
+ it "should detect unpreload associations" do
762
+ User.find(:all).each do |user|
763
+ user.votes.collect(&:vote)
764
+ end
765
+ Bullet::Association.should be_has_unpreload_associations
766
+ end
767
+
768
+ it "should detect no unpreload associations" do
769
+ User.find(:all, :include => :votes).each do |user|
770
+ user.votes.collect(&:vote)
771
+ end
772
+ Bullet::Association.should_not be_has_unpreload_associations
773
+ end
774
+
775
+ it "should detect unpreload associations with voteable" do
776
+ Vote.find(:all).each do |vote|
777
+ vote.voteable.name
778
+ end
779
+ Bullet::Association.should be_has_unpreload_associations
780
+ end
781
+
782
+ it "should detect no unpreload associations with voteable" do
783
+ Vote.find(:all, :include => :voteable).each do |vote|
784
+ vote.voteable.name
785
+ end
786
+ Bullet::Association.should_not be_has_unpreload_associations
787
+ end
788
+
789
+ it "should detect no unused preload associations" do
790
+ User.find(:all).collect(&:name)
791
+ Bullet::Association.check_unused_preload_associations
792
+ Bullet::Association.should_not be_has_unused_preload_associations
793
+ end
794
+
795
+ it "should detect unused preload associations" do
796
+ User.find(:all, :include => :votes).collect(&:name)
797
+ Bullet::Association.check_unused_preload_associations
798
+ Bullet::Association.should be_has_unused_preload_associations
799
+ end
800
+
801
+ it "should detect no unused preload associations with voteable" do
802
+ Vote.find(:all).collect(&:vote)
803
+ Bullet::Association.check_unused_preload_associations
804
+ Bullet::Association.should_not be_has_unused_preload_associations
805
+ end
806
+
807
+ it "should detect unused preload associations with voteable" do
808
+ Vote.find(:all, :include => :voteable).collect(&:vote)
809
+ Bullet::Association.check_unused_preload_associations
810
+ Bullet::Association.should be_has_unused_preload_associations
811
+ end
812
+ end
813
+
814
+ describe Bullet::Association, "has_one" do
815
+
816
+ include BulletTestHelper
817
+
818
+ def setup_db
819
+ ActiveRecord::Schema.define(:version => 1) do
820
+ create_table :companies do |t|
821
+ t.column :name, :string
822
+ end
823
+
824
+ create_table :addresses do |t|
825
+ t.column :name, :string
826
+ t.column :company_id, :integer
827
+ end
828
+ end
829
+ end
830
+
831
+ def teardown_db
832
+ ActiveRecord::Base.connection.tables.each do |table|
833
+ ActiveRecord::Base.connection.drop_table(table)
834
+ end
835
+ end
836
+
837
+ class Company < ActiveRecord::Base
838
+ has_one :address
839
+ end
840
+
841
+ class Address < ActiveRecord::Base
842
+ belongs_to :company
843
+ end
844
+
845
+ before(:all) do
846
+ silence_logger { setup_db }
847
+
848
+ company1 = Company.create(:name => 'first')
849
+ company2 = Company.create(:name => 'second')
850
+
851
+ Address.create(:name => 'first', :company => company1)
852
+ Address.create(:name => 'second', :company => company2)
853
+ end
854
+
855
+ after(:all) do
856
+ teardown_db
857
+ end
858
+
859
+ before(:each) do
860
+ Bullet::Association.start_request
861
+ end
862
+
863
+ after(:each) do
864
+ Bullet::Association.end_request
865
+ end
866
+
867
+ it "should detect unpreload association" do
868
+ Company.find(:all).each do |company|
869
+ company.address.name
870
+ end
871
+ Bullet::Association.should be_has_unpreload_associations
872
+ end
873
+
874
+ it "should detect no unpreload association" do
875
+ Company.find(:all, :include => :address).each do |company|
876
+ company.address.name
877
+ end
878
+ Bullet::Association.should_not be_has_unpreload_associations
879
+ end
880
+
881
+ it "should detect no unused preload association" do
882
+ Company.find(:all).collect(&:name)
883
+ Bullet::Association.check_unused_preload_associations
884
+ Bullet::Association.should_not be_has_unused_preload_associations
885
+ end
886
+
887
+ it "should detect unused preload association" do
888
+ Company.find(:all, :include => :address).collect(&:name)
889
+ Bullet::Association.check_unused_preload_associations
890
+ Bullet::Association.should be_has_unused_preload_associations
891
+ end
892
+ end
893
+
894
+ describe Bullet::Association, "call one association that in possible objects" do
895
+ include BulletTestHelper
896
+ def setup_db
897
+ ActiveRecord::Schema.define(:version => 1) do
898
+ create_table :contacts do |t|
899
+ t.column :name, :string
900
+ end
901
+
902
+ create_table :emails do |t|
903
+ t.column :name, :string
904
+ t.column :contact_id, :integer
905
+ end
906
+ end
907
+ end
908
+
909
+ def teardown_db
910
+ ActiveRecord::Base.connection.tables.each do |table|
911
+ ActiveRecord::Base.connection.drop_table(table)
912
+ end
913
+ end
914
+
915
+ class Contact < ActiveRecord::Base
916
+ has_many :emails
917
+ end
918
+
919
+ class Email < ActiveRecord::Base
920
+ belongs_to :contact
921
+ end
922
+
923
+ before(:all) do
924
+ silence_logger { setup_db }
925
+
926
+ contact1 = Contact.create(:name => 'first')
927
+ contact2 = Contact.create(:name => 'second')
928
+
929
+ email1 = contact1.emails.create(:name => 'first')
930
+ email2 = contact1.emails.create(:name => 'second')
931
+ email3 = contact2.emails.create(:name => 'third')
932
+ email4 = contact2.emails.create(:name => 'fourth')
933
+ end
934
+
935
+ after(:all) do
936
+ teardown_db
937
+ end
938
+
939
+ before(:each) do
940
+ Bullet::Association.start_request
941
+ end
942
+
943
+ after(:each) do
944
+ Bullet::Association.end_request
945
+ end
946
+
947
+ it "should detect no unpreload association" do
948
+ Contact.find(:all)
949
+ Contact.first.emails.collect(&:name)
950
+ Bullet::Association.should_not be_has_unpreload_associations
951
+ end
952
+ end
953
+
954
+ describe Bullet::Association, "STI" do
955
+ include BulletTestHelper
956
+ def setup_db
957
+ ActiveRecord::Schema.define(:version => 1) do
958
+ create_table :documents do |t|
959
+ t.string :name
960
+ t.string :type
961
+ t.integer :parent_id
962
+ t.integer :author_id
963
+ end
964
+
965
+ create_table :authors do |t|
966
+ t.string :name
967
+ end
968
+ end
969
+ end
970
+
971
+ def teardown_db
972
+ ActiveRecord::Base.connection.tables.each do |table|
973
+ ActiveRecord::Base.connection.drop_table(table)
974
+ end
975
+ end
976
+
977
+ class Document < ActiveRecord::Base
978
+ has_many :children, :class_name => "Document", :foreign_key => 'parent_id'
979
+ belongs_to :parent, :class_name => "Document", :foreign_key => 'parent_id'
980
+ belongs_to :author
981
+ end
982
+
983
+ class Page < Document
984
+ end
985
+
986
+ class Folder < Document
987
+ end
988
+
989
+ class Author < ActiveRecord::Base
990
+ has_many :documents
991
+ end
992
+
993
+ before(:all) do
994
+ silence_logger { setup_db }
995
+ author1 = Author.create(:name => 'author1')
996
+ author2 = Author.create(:name => 'author2')
997
+ folder1 = Folder.create(:name => 'folder1', :author_id => author1.id)
998
+ folder2 = Folder.create(:name => 'folder2', :author_id => author2.id)
999
+ page1 = Page.create(:name => 'page1', :parent_id => folder1.id, :author_id => author1.id)
1000
+ page2 = Page.create(:name => 'page2', :parent_id => folder1.id, :author_id => author1.id)
1001
+ page3 = Page.create(:name => 'page3', :parent_id => folder2.id, :author_id => author2.id)
1002
+ page4 = Page.create(:name => 'page4', :parent_id => folder2.id, :author_id => author2.id)
1003
+ end
1004
+
1005
+ before(:each) do
1006
+ Bullet::Association.start_request
1007
+ end
1008
+
1009
+ after(:each) do
1010
+ Bullet::Association.end_request
1011
+ end
1012
+
1013
+ it "should detect unpreload associations" do
1014
+ Page.find(:all).each do |page|
1015
+ page.author.name
1016
+ end
1017
+ Bullet::Association.should be_has_unpreload_associations
1018
+ Bullet::Association.check_unused_preload_associations
1019
+ Bullet::Association.should_not be_has_unused_preload_associations
1020
+ end
1021
+
1022
+ it "should not detect unpreload associations" do
1023
+ Page.find(:all, :include => :author).each do |page|
1024
+ page.author.name
1025
+ end
1026
+ Bullet::Association.should_not be_has_unpreload_associations
1027
+ Bullet::Association.check_unused_preload_associations
1028
+ Bullet::Association.should_not be_has_unused_preload_associations
1029
+ end
1030
+
1031
+ it "should detect unused preload associations" do
1032
+ Page.find(:all, :include => :author).collect(&:name)
1033
+ Bullet::Association.should_not be_has_unpreload_associations
1034
+ Bullet::Association.check_unused_preload_associations
1035
+ Bullet::Association.should be_has_unused_preload_associations
1036
+ end
1037
+
1038
+ it "should not detect unused preload associations" do
1039
+ Page.find(:all).collect(&:name)
1040
+ Bullet::Association.should_not be_has_unpreload_associations
1041
+ Bullet::Association.check_unused_preload_associations
1042
+ Bullet::Association.should_not be_has_unused_preload_associations
1043
+ end
1044
+ end