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.
- data/MIT-LICENSE +1 -1
- data/README.textile +38 -8
- data/README_for_rails2.textile +19 -3
- data/lib/bullet/action_controller2.rb +4 -4
- data/lib/bullet/active_record2.rb +16 -16
- data/lib/bullet/active_record3.rb +16 -25
- data/lib/bullet/detector/association.rb +135 -0
- data/lib/bullet/detector/base.rb +19 -0
- data/lib/bullet/detector/counter.rb +43 -0
- data/lib/bullet/detector/n_plus_one_query.rb +39 -0
- data/lib/bullet/detector/unused_eager_association.rb +39 -0
- data/lib/bullet/detector.rb +9 -0
- data/lib/bullet/notification/base.rb +57 -0
- data/lib/bullet/notification/counter_cache.rb +13 -0
- data/lib/bullet/notification/n_plus_one_query.rb +32 -0
- data/lib/bullet/notification/unused_eager_loading.rb +14 -0
- data/lib/bullet/notification.rb +4 -79
- data/lib/bullet/notification_collector.rb +25 -0
- data/lib/bullet/rack.rb +44 -0
- data/lib/bullet/registry/association.rb +16 -0
- data/lib/bullet/registry/base.rb +39 -0
- data/lib/bullet/registry/object.rb +15 -0
- data/lib/bullet/registry.rb +7 -0
- data/lib/bullet/version.rb +5 -0
- data/lib/bullet.rb +63 -42
- metadata +60 -42
- data/Rakefile +0 -34
- data/VERSION +0 -1
- data/bullet.gemspec +0 -67
- data/lib/bullet/association.rb +0 -294
- data/lib/bullet/counter.rb +0 -101
- data/lib/bullet/logger.rb +0 -9
- data/lib/bulletware.rb +0 -42
- data/rails/init.rb +0 -1
- data/spec/bullet/association_for_chris_spec.rb +0 -96
- data/spec/bullet/association_for_peschkaj_spec.rb +0 -86
- data/spec/bullet/association_spec.rb +0 -1043
- data/spec/bullet/counter_spec.rb +0 -136
- data/spec/spec.opts +0 -3
- data/spec/spec_helper.rb +0 -45
- 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
|
-
|
data/lib/bullet/association.rb
DELETED
|
@@ -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
|
data/lib/bullet/counter.rb
DELETED
|
@@ -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
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
|