bullet 2.0.0.beta.2 → 2.0.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 (41) hide show
  1. data/MIT-LICENSE +1 -1
  2. data/README.textile +38 -8
  3. data/README_for_rails2.textile +19 -3
  4. data/lib/bullet/action_controller2.rb +4 -4
  5. data/lib/bullet/active_record2.rb +16 -16
  6. data/lib/bullet/active_record3.rb +16 -25
  7. data/lib/bullet/detector/association.rb +135 -0
  8. data/lib/bullet/detector/base.rb +19 -0
  9. data/lib/bullet/detector/counter.rb +43 -0
  10. data/lib/bullet/detector/n_plus_one_query.rb +39 -0
  11. data/lib/bullet/detector/unused_eager_association.rb +39 -0
  12. data/lib/bullet/detector.rb +9 -0
  13. data/lib/bullet/notification/base.rb +57 -0
  14. data/lib/bullet/notification/counter_cache.rb +13 -0
  15. data/lib/bullet/notification/n_plus_one_query.rb +32 -0
  16. data/lib/bullet/notification/unused_eager_loading.rb +14 -0
  17. data/lib/bullet/notification.rb +4 -79
  18. data/lib/bullet/notification_collector.rb +25 -0
  19. data/lib/bullet/rack.rb +44 -0
  20. data/lib/bullet/registry/association.rb +16 -0
  21. data/lib/bullet/registry/base.rb +39 -0
  22. data/lib/bullet/registry/object.rb +15 -0
  23. data/lib/bullet/registry.rb +7 -0
  24. data/lib/bullet/version.rb +5 -0
  25. data/lib/bullet.rb +63 -42
  26. metadata +60 -42
  27. data/Rakefile +0 -34
  28. data/VERSION +0 -1
  29. data/bullet.gemspec +0 -67
  30. data/lib/bullet/association.rb +0 -294
  31. data/lib/bullet/counter.rb +0 -101
  32. data/lib/bullet/logger.rb +0 -9
  33. data/lib/bulletware.rb +0 -42
  34. data/rails/init.rb +0 -1
  35. data/spec/bullet/association_for_chris_spec.rb +0 -96
  36. data/spec/bullet/association_for_peschkaj_spec.rb +0 -86
  37. data/spec/bullet/association_spec.rb +0 -1043
  38. data/spec/bullet/counter_spec.rb +0 -136
  39. data/spec/spec.opts +0 -3
  40. data/spec/spec_helper.rb +0 -45
  41. data/tasks/bullet_tasks.rake +0 -9
data/Rakefile DELETED
@@ -1,34 +0,0 @@
1
- require 'rake'
2
- require 'rspec/core/rake_task'
3
- require 'rake/rdoctask'
4
- require 'jeweler'
5
-
6
- desc 'Default: run unit tests.'
7
- task :default => :spec
8
-
9
- desc 'Generate documentation for the sitemap plugin.'
10
- Rake::RDocTask.new(:rdoc) do |rdoc|
11
- rdoc.rdoc_dir = 'rdoc'
12
- rdoc.title = 'Bullet'
13
- rdoc.options << '--line-numbers' << '--inline-source'
14
- rdoc.rdoc_files.include('README')
15
- rdoc.rdoc_files.include('lib/**/*.rb')
16
- end
17
-
18
- # FIXME: 'rake spec' will break the test, just run 'ruby spec/association_spec.rb'
19
- #desc "Run all specs in spec directory"
20
- #Rspec::Core::RakeTask.new(:spec) do |t|
21
- #t.pattern = FileList['spec/**/*_spec.rb']
22
- #end
23
-
24
- Jeweler::Tasks.new do |gemspec|
25
- gemspec.name = "bullet"
26
- gemspec.summary = "A plugin to kill N+1 queries and unused eager loading"
27
- gemspec.description = "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."
28
- gemspec.email = "flyerhzm@gmail.com"
29
- gemspec.homepage = "http://github.com/flyerhzm/bullet"
30
- gemspec.authors = ["Richard Huang"]
31
- gemspec.files.exclude '.gitignore'
32
- gemspec.files.exclude 'log/*'
33
- end
34
- Jeweler::GemcutterTasks.new
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 2.0.0.beta.2
data/bullet.gemspec DELETED
@@ -1,67 +0,0 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
- # -*- encoding: utf-8 -*-
5
-
6
- Gem::Specification.new do |s|
7
- s.name = %q{bullet}
8
- s.version = "2.0.0.beta.2"
9
-
10
- s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Richard Huang"]
12
- s.date = %q{2010-03-07}
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
- s.email = %q{flyerhzm@gmail.com}
15
- s.extra_rdoc_files = [
16
- "README.textile",
17
- "README_for_rails2.textile"
18
- ]
19
- s.files = [
20
- "MIT-LICENSE",
21
- "README.textile",
22
- "README_for_rails2.textile",
23
- "Rakefile",
24
- "VERSION",
25
- "bullet.gemspec",
26
- "lib/bullet.rb",
27
- "lib/bullet/action_controller2.rb",
28
- "lib/bullet/active_record2.rb",
29
- "lib/bullet/active_record3.rb",
30
- "lib/bullet/association.rb",
31
- "lib/bullet/counter.rb",
32
- "lib/bullet/logger.rb",
33
- "lib/bullet/notification.rb",
34
- "lib/bulletware.rb",
35
- "rails/init.rb",
36
- "spec/bullet/association_for_chris_spec.rb",
37
- "spec/bullet/association_for_peschkaj_spec.rb",
38
- "spec/bullet/association_spec.rb",
39
- "spec/bullet/counter_spec.rb",
40
- "spec/spec.opts",
41
- "spec/spec_helper.rb",
42
- "tasks/bullet_tasks.rake"
43
- ]
44
- s.homepage = %q{http://github.com/flyerhzm/bullet}
45
- s.rdoc_options = ["--charset=UTF-8"]
46
- s.require_paths = ["lib"]
47
- s.rubygems_version = %q{1.3.6}
48
- s.summary = %q{A plugin to kill N+1 queries and unused eager loading}
49
- s.test_files = [
50
- "spec/spec_helper.rb",
51
- "spec/bullet/counter_spec.rb",
52
- "spec/bullet/association_spec.rb",
53
- "spec/bullet/association_for_chris_spec.rb",
54
- "spec/bullet/association_for_peschkaj_spec.rb"
55
- ]
56
-
57
- if s.respond_to? :specification_version then
58
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
59
- s.specification_version = 3
60
-
61
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
62
- else
63
- end
64
- else
65
- end
66
- end
67
-
@@ -1,294 +0,0 @@
1
- module Bullet
2
- class Association
3
- class <<self
4
- include Bullet::Notification
5
-
6
- def start_request
7
- @@checked = false
8
- end
9
-
10
- def end_request
11
- clear
12
- end
13
-
14
- def clear
15
- @@object_associations = nil
16
- @@unpreload_associations = nil
17
- @@unused_preload_associations = nil
18
- @@callers = nil
19
- @@possible_objects = nil
20
- @@impossible_objects = nil
21
- @@call_object_associations = nil
22
- @@eager_loadings = nil
23
- end
24
-
25
- def notification?
26
- check_unused_preload_associations unless @@checked
27
- has_unpreload_associations? or has_unused_preload_associations?
28
- end
29
-
30
- def add_unpreload_associations(klazz, associations)
31
- unpreload_associations[klazz] ||= []
32
- unpreload_associations[klazz] << associations
33
- unique(unpreload_associations[klazz])
34
- end
35
-
36
- def add_unused_preload_associations(klazz, associations)
37
- unused_preload_associations[klazz] ||= []
38
- unused_preload_associations[klazz] << associations
39
- unique(unused_preload_associations[klazz])
40
- end
41
-
42
- def add_object_associations(object, associations)
43
- object_associations[object] ||= []
44
- object_associations[object] << associations
45
- unique(object_associations[object])
46
- end
47
-
48
- def add_call_object_associations(object, associations)
49
- call_object_associations[object] ||= []
50
- call_object_associations[object] << associations
51
- unique(call_object_associations[object])
52
- end
53
-
54
- def add_possible_objects(objects)
55
- klazz = objects.is_a?(Array) ? objects.first.class : objects.class
56
- possible_objects[klazz] ||= []
57
- possible_objects[klazz] << objects
58
- unique(possible_objects[klazz])
59
- end
60
-
61
- def add_impossible_object(object)
62
- klazz = object.class
63
- impossible_objects[klazz] ||= []
64
- impossible_objects[klazz] << object
65
- impossible_objects[klazz].uniq!
66
- end
67
-
68
- def add_eager_loadings(objects, associations)
69
- objects = Array(objects)
70
- eager_loadings[objects] ||= []
71
- eager_loadings.each do |k, v|
72
- unless (k & objects).empty?
73
- if (k & objects) == k
74
- eager_loadings[k] << associations
75
- unique(eager_loadings[k])
76
- break
77
- else
78
- eager_loadings.merge!({(k & objects) => (eager_loadings[k].dup << associations)})
79
- unique(eager_loadings[(k & objects)])
80
- eager_loadings.merge!({(k - objects) => eager_loadings[k]}) unless (k - objects).empty?
81
- unique(eager_loadings[(k - objects)])
82
- eager_loadings.delete(k)
83
- objects = objects - k
84
- end
85
- end
86
- end
87
- unless objects.empty?
88
- eager_loadings[objects] << associations
89
- unique(eager_loadings[objects])
90
- end
91
- end
92
-
93
- # executed when object.assocations is called.
94
- # first, it keeps this method call for object.association.
95
- # then, it checks if this associations call is unpreload.
96
- # if it is, keeps this unpreload associations and caller.
97
- def call_association(object, associations)
98
- add_call_object_associations(object, associations)
99
- if unpreload_associations?(object, associations)
100
- add_unpreload_associations(object.class, associations)
101
- caller_in_project
102
- end
103
- end
104
-
105
- # check if there are unused preload associations.
106
- # for each object => association
107
- # get related_objects from eager_loadings associated with object and associations
108
- # get call_object_association from associations of call_object_associations whose object is in related_objects
109
- # if association not in call_object_association, then the object => association - call_object_association is ununsed preload assocations
110
- def check_unused_preload_associations
111
- @@checked = true
112
- object_associations.each do |object, association|
113
- related_objects = eager_loadings.select {|key, value| key.include?(object) and value == association}.collect(&:first).flatten
114
- call_object_association = related_objects.collect { |related_object| call_object_associations[related_object] }.compact.flatten.uniq
115
- diff_object_association = (association - call_object_association).reject {|a| a.is_a? Hash}
116
- add_unused_preload_associations(object.class, diff_object_association) unless diff_object_association.empty?
117
- end
118
- end
119
-
120
- def has_unused_preload_associations?
121
- !unused_preload_associations.empty?
122
- end
123
-
124
- def has_unpreload_associations?
125
- !unpreload_associations.empty?
126
- end
127
-
128
- private
129
- # decide whether the object.associations is unpreloaded or not.
130
- def unpreload_associations?(object, associations)
131
- possible?(object) and !impossible?(object) and !association?(object, associations)
132
- end
133
-
134
- def possible?(object)
135
- klazz = object.class
136
- possible_objects[klazz] and possible_objects[klazz].include?(object)
137
- end
138
-
139
- def impossible?(object)
140
- klazz = object.class
141
- impossible_objects[klazz] and impossible_objects[klazz].include?(object)
142
- end
143
-
144
- # check if object => associations already exists in object_associations.
145
- def association?(object, associations)
146
- object_associations.each do |key, value|
147
- if key == object
148
- value.each do |v|
149
- result = v.is_a?(Hash) ? v.has_key?(associations) : v == associations
150
- return true if result
151
- end
152
- end
153
- end
154
- return false
155
- end
156
-
157
- def notification_response
158
- response = []
159
- if has_unused_preload_associations?
160
- response << unused_preload_messages.join("\n")
161
- end
162
- if has_unpreload_associations?
163
- response << unpreload_messages.join("\n")
164
- end
165
- response
166
- end
167
-
168
- def console_title
169
- title = []
170
- title << unused_preload_messages.first.first unless unused_preload_messages.empty?
171
- title << unpreload_messages.first.first unless unpreload_messages.empty?
172
- title
173
- end
174
-
175
- def log_messages(path = nil)
176
- messages = []
177
- messages << unused_preload_messages(path)
178
- messages << unpreload_messages(path)
179
- messages << call_stack_messages
180
- messages
181
- end
182
-
183
- def unused_preload_messages(path = nil)
184
- messages = []
185
- unused_preload_associations.each do |klazz, associations|
186
- messages << [
187
- "Unused Eager Loading #{path ? "in #{path}" : 'detected'}",
188
- klazz_associations_str(klazz, associations),
189
- " Remove from your finder: #{associations_str(associations)}"
190
- ]
191
- end
192
- messages
193
- end
194
-
195
- def unpreload_messages(path = nil)
196
- messages = []
197
- unpreload_associations.each do |klazz, associations|
198
- messages << [
199
- "N+1 Query #{path ? "in #{path}" : 'detected'}",
200
- klazz_associations_str(klazz, associations),
201
- " Add to your finder: #{associations_str(associations)}"
202
- ]
203
- end
204
- messages
205
- end
206
-
207
- def call_stack_messages
208
- callers.inject([]) do |messages, c|
209
- messages << ['N+1 Query method call stack', c.collect {|line| " #{line}"}].flatten
210
- end
211
- end
212
-
213
- def klazz_associations_str(klazz, associations)
214
- " #{klazz} => [#{associations.map(&:inspect).join(', ')}]"
215
- end
216
-
217
- def associations_str(associations)
218
- ":include => #{associations.map{|a| a.to_sym unless a.is_a? Hash}.inspect}"
219
- end
220
-
221
- def unique(array)
222
- array.flatten!
223
- array.uniq!
224
- end
225
-
226
- # unpreload_associations keep the class relationships
227
- # that the associations, belongs to the class, are used but not preloaded.
228
- # e.g. { Post => [:comments] }
229
- # so the unpreload_associations should be preloaded by find :include.
230
- def unpreload_associations
231
- @@unpreload_associations ||= {}
232
- end
233
-
234
- # unused_preload_associations keep the class relationships
235
- # that the associations, belongs to the class, are preloaded but not used.
236
- # e.g. { Post => [:comments] }
237
- # so the unused_preload_associations should be removed from find :include.
238
- def unused_preload_associations
239
- @@unused_preload_associations ||= {}
240
- end
241
-
242
- # object_associations keep the object relationships
243
- # that the object has many associations.
244
- # e.g. { <Post id:1> => [:comments] }
245
- # the object_associations keep all associations that may be or may no be
246
- # unpreload associations or unused preload associations.
247
- def object_associations
248
- @@object_associations ||= {}
249
- end
250
-
251
- # call_object_assciations keep the object relationships
252
- # that object.associations is called.
253
- # e.g. { <Post id:1> => [:comments] }
254
- # they are used to detect unused preload associations.
255
- def call_object_associations
256
- @@call_object_associations ||= {}
257
- end
258
-
259
- # possible_objects keep the class to object relationships
260
- # that the objects may cause N+1 query.
261
- # e.g. { Post => [<Post id:1>, <Post id:2>] }
262
- def possible_objects
263
- @@possible_objects ||= {}
264
- end
265
-
266
- # impossible_objects keep the class to objects relationships
267
- # that the objects may not cause N+1 query.
268
- # e.g. { Post => [<Post id:1>, <Post id:2>] }
269
- # Notice: impossible_objects are not accurate,
270
- # if find collection returns only one object, then the object is impossible object,
271
- # impossible_objects are used to avoid treating 1+1 query to N+1 query.
272
- def impossible_objects
273
- @@impossible_objects ||= {}
274
- end
275
-
276
- # eager_loadings keep the object relationships
277
- # that the associations are preloaded by find :include.
278
- # e.g. { [<Post id:1>, <Post id:2>] => [:comments, :user] }
279
- def eager_loadings
280
- @@eager_loadings ||= {}
281
- end
282
-
283
- def caller_in_project
284
- vender_root ||= File.join(Rails.root, 'vendor')
285
- callers << caller.select {|c| c =~ /#{Rails.root}/}.reject {|c| c =~ /#{vender_root}/}
286
- callers.uniq!
287
- end
288
-
289
- def callers
290
- @@callers ||= []
291
- end
292
- end
293
- end
294
- end
@@ -1,101 +0,0 @@
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
data/lib/bullet/logger.rb DELETED
@@ -1,9 +0,0 @@
1
- module Bullet
2
- class BulletLogger < Logger
3
- LOG_FILE = File.join(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
data/lib/bulletware.rb DELETED
@@ -1,42 +0,0 @@
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 status == 200 and !response.body.frozen? and 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'] 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
data/rails/init.rb DELETED
@@ -1 +0,0 @@
1
- require 'bullet'
@@ -1,96 +0,0 @@
1
- require File.dirname(__FILE__) + '/../spec_helper'
2
-
3
- ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
4
- # This test is just used for http://github.com/flyerhzm/bullet/issues/#issue/14
5
- describe Bullet::Association do
6
-
7
- describe "for chris" do
8
- def setup_db
9
- ActiveRecord::Schema.define(:version => 1) do
10
- create_table :locations do |t|
11
- t.column :name, :string
12
- end
13
-
14
- create_table :hotels do |t|
15
- t.column :name, :string
16
- t.column :location_id, :integer
17
- end
18
-
19
- create_table :deals do |t|
20
- t.column :name, :string
21
- t.column :hotel_id, :integer
22
- end
23
- end
24
- end
25
-
26
- def teardown_db
27
- ActiveRecord::Base.connection.tables.each do |table|
28
- ActiveRecord::Base.connection.drop_table(table)
29
- end
30
- end
31
-
32
- class Location < ActiveRecord::Base
33
- has_many :hotels
34
- end
35
-
36
- class Hotel < ActiveRecord::Base
37
- belongs_to :location
38
- has_many :deals
39
- end
40
-
41
- class Deal < ActiveRecord::Base
42
- belongs_to :hotel
43
- has_one :location, :through => :hotel
44
- end
45
-
46
- before(:all) do
47
- setup_db
48
-
49
- location1 = Location.create(:name => "location1")
50
- location2 = Location.create(:name => "location2")
51
-
52
- hotel1 = location1.hotels.create(:name => "hotel1")
53
- hotel2 = location1.hotels.create(:name => "hotel2")
54
- hotel3 = location2.hotels.create(:name => "hotel3")
55
- hotel4 = location2.hotels.create(:name => "hotel4")
56
-
57
- deal1 = hotel1.deals.create(:name => "deal1")
58
- deal2 = hotel2.deals.create(:name => "deal2")
59
- deal3 = hotel3.deals.create(:name => "deal3")
60
- deal4 = hotel4.deals.create(:name => "deal4")
61
- end
62
-
63
- after(:all) do
64
- teardown_db
65
- end
66
-
67
- before(:each) do
68
- Bullet::Association.start_request
69
- end
70
-
71
- after(:each) do
72
- Bullet::Association.end_request
73
- end
74
-
75
- it "should detect unpreload association from deal to hotel" do
76
- Deal.all.each do |deal|
77
- deal.hotel.location.name
78
- end
79
- Bullet::Association.should be_detecting_unpreloaded_association_for(Deal, :hotel)
80
- end
81
-
82
- it "should detect unpreload association from hotel to location" do
83
- Deal.includes(:hotel).each do |deal|
84
- deal.hotel.location.name
85
- end
86
- Bullet::Association.should be_detecting_unpreloaded_association_for(Hotel, :location)
87
- end
88
-
89
- it "should not detect unpreload association" do
90
- Deal.includes({:hotel => :location}).each do |deal|
91
- deal.hotel.location.name
92
- end
93
- Bullet::Association.should_not be_has_unused_preload_associations
94
- end
95
- end
96
- end