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/MIT-LICENSE
CHANGED
data/README.textile
CHANGED
|
@@ -17,6 +17,7 @@ You can install it as a gem:
|
|
|
17
17
|
<pre><code>
|
|
18
18
|
sudo gem install bullet --pre
|
|
19
19
|
</code></pre>
|
|
20
|
+
|
|
20
21
|
****************************************************************************
|
|
21
22
|
|
|
22
23
|
h2. Configuration
|
|
@@ -29,6 +30,10 @@ config.after_initialize do
|
|
|
29
30
|
Bullet.bullet_logger = true
|
|
30
31
|
Bullet.console = true
|
|
31
32
|
Bullet.growl = true
|
|
33
|
+
Bullet.xmpp = { :account => 'bullets_account@jabber.org',
|
|
34
|
+
:password => 'bullets_password_for_jabber',
|
|
35
|
+
:receiver => 'your_account@jabber.org',
|
|
36
|
+
:show_online_status => true }
|
|
32
37
|
Bullet.rails_logger = true
|
|
33
38
|
Bullet.disable_browser_cache = true
|
|
34
39
|
end
|
|
@@ -43,13 +48,27 @@ It is recommended to config growl notification as follows if your collaborators
|
|
|
43
48
|
end
|
|
44
49
|
</code></pre>
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
and similarly for XMPP:
|
|
52
|
+
<pre><code>
|
|
53
|
+
begin
|
|
54
|
+
require 'xmpp4r'
|
|
55
|
+
Bullet.xmpp = { :account => 'bullets_account@jabber.org',
|
|
56
|
+
:password => 'bullets_password_for_jabber',
|
|
57
|
+
:receiver => 'your_account@jabber.org',
|
|
58
|
+
:show_online_status => true }
|
|
59
|
+
rescue MissingSourceFile
|
|
60
|
+
end
|
|
61
|
+
</code></pre>
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
The code above will enable all six of the Bullet notification systems:
|
|
47
65
|
* <code>Bullet.enable</code>: enable Bullet plugin/gem, otherwise do nothing
|
|
48
66
|
* <code>Bullet.alert</code>: pop up a JavaScript alert in the browser
|
|
49
67
|
* <code>Bullet.bullet_logger</code>: log to the Bullet log file (Rails.root/log/bullet.log)
|
|
50
68
|
* <code>Bullet.rails_logger</code>: add warnings directly to the Rails log
|
|
51
69
|
* <code>Bullet.console</code>: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
|
|
52
70
|
* <code>Bullet.growl</code>: pop up Growl warnings if your system has Growl installed. Requires a little bit of configuration
|
|
71
|
+
* <code>Bullet.xmpp</code>: send XMPP/Jabber notifications to the receiver indicated. Note that the code will currently not handle the adding of contacts, so you will need to make both accounts indicated know each other manually before you will receive any notifications. If you restart the development server frequently, the 'coming online' sound for the bullet account may start to annoy - in this case set :show_online_status to false; you will still get notifications, but the bullet account won't announce it's online status anymore.
|
|
53
72
|
* <code>Bullet.disable_browser_cache</code>: disable browser cache which usually causes unexpected problems
|
|
54
73
|
|
|
55
74
|
****************************************************************************
|
|
@@ -105,6 +124,17 @@ ruby-growl gem has an issue about md5 in ruby 1.9, if you use growl and ruby 1.9
|
|
|
105
124
|
|
|
106
125
|
****************************************************************************
|
|
107
126
|
|
|
127
|
+
h2. XMPP/Jabber Support
|
|
128
|
+
|
|
129
|
+
To get XMPP support up-and-running for Bullet, follow the steps below:
|
|
130
|
+
* Install the xmpp4r gem: <code>sudo gem install xmpp4r</code>
|
|
131
|
+
* Make both the bullet and the receipient account add each other as contacts.
|
|
132
|
+
This will require you to manually log into both accounts, add each other
|
|
133
|
+
as contact and confirm each others contact request.
|
|
134
|
+
* Boot up your application. Bullet will automatically send an XMPP notification when XMPP is turned on.
|
|
135
|
+
|
|
136
|
+
****************************************************************************
|
|
137
|
+
|
|
108
138
|
h2. Important
|
|
109
139
|
|
|
110
140
|
If you find bullet does not work for you, *please disable your browser's cache*.
|
|
@@ -134,8 +164,7 @@ end
|
|
|
134
164
|
|
|
135
165
|
after(:each)
|
|
136
166
|
if Bullet.enable?
|
|
137
|
-
Bullet.
|
|
138
|
-
Bullet.log_notification('Test: ')
|
|
167
|
+
Bullet.perform_out_of_channel_notifications
|
|
139
168
|
Bullet.end_request
|
|
140
169
|
end
|
|
141
170
|
end
|
|
@@ -155,6 +184,7 @@ h2. Links
|
|
|
155
184
|
|
|
156
185
|
h2. Contributors
|
|
157
186
|
|
|
187
|
+
sriedel did a lot of awesome refactors, added xmpp notification support, and added Hacking.textile about how to extend to bullet plugin.
|
|
158
188
|
flipsasser added Growl, console.log and Rails.log support, very awesome. And he also improved README.
|
|
159
189
|
rainux added group style console.log.
|
|
160
190
|
2collegebums added some great specs to generate red bar.
|
|
@@ -203,7 +233,7 @@ post2.comments.create(:name => 'fourth')
|
|
|
203
233
|
<pre><code>
|
|
204
234
|
<% @posts.each do |post| %>
|
|
205
235
|
<tr>
|
|
206
|
-
<td><%=
|
|
236
|
+
<td><%= post.name %></td>
|
|
207
237
|
<td><%= post.comments.collect(&:name) %></td>
|
|
208
238
|
<td><%= link_to 'Show', post %></td>
|
|
209
239
|
<td><%= link_to 'Edit', edit_post_path(post) %></td>
|
|
@@ -315,7 +345,7 @@ a N+1 query fixed. Cool!
|
|
|
315
345
|
<pre><code>
|
|
316
346
|
<% @posts.each do |post| %>
|
|
317
347
|
<tr>
|
|
318
|
-
<td><%=
|
|
348
|
+
<td><%= post.name %></td>
|
|
319
349
|
<td><%= link_to 'Show', post %></td>
|
|
320
350
|
<td><%= link_to 'Edit', edit_post_path(post) %></td>
|
|
321
351
|
<td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
|
|
@@ -355,8 +385,8 @@ Remove from your finder: :include => [:comments]
|
|
|
355
385
|
<pre><code>
|
|
356
386
|
<% @posts.each do |post| %>
|
|
357
387
|
<tr>
|
|
358
|
-
<td><%=
|
|
359
|
-
<td><%=
|
|
388
|
+
<td><%= post.name %></td>
|
|
389
|
+
<td><%= post.comments.size %></td>
|
|
360
390
|
<td><%= link_to 'Show', post %></td>
|
|
361
391
|
<td><%= link_to 'Edit', edit_post_path(post) %></td>
|
|
362
392
|
<td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
|
|
@@ -381,4 +411,4 @@ In the meanwhile, there's a log appended into <code>log/bullet.log</code> file.
|
|
|
381
411
|
****************************************************************************
|
|
382
412
|
|
|
383
413
|
|
|
384
|
-
Copyright (c) 2009 Richard Huang (flyerhzm@gmail.com), released under the MIT license
|
|
414
|
+
Copyright (c) 2009 - 2010 Richard Huang (flyerhzm@gmail.com), released under the MIT license
|
data/README_for_rails2.textile
CHANGED
|
@@ -18,6 +18,7 @@ There is a large refactor from gem 1.4 to 1.5, so if you upgrade to 1.5 gem, ple
|
|
|
18
18
|
|
|
19
19
|
h2. Contributors
|
|
20
20
|
|
|
21
|
+
sriedel did a lot of awesome refactors, added xmpp notification support, and added Hacking.textile about how to extend to bullet plugin.
|
|
21
22
|
flipsasser added Growl, console.log and Rails.log support, very awesome. And he also improved README.
|
|
22
23
|
rainux added group style console.log.
|
|
23
24
|
2collegebums added some great specs to generate red bar.
|
|
@@ -52,6 +53,10 @@ config.after_initialize do
|
|
|
52
53
|
Bullet.growl = true
|
|
53
54
|
Bullet.rails_logger = true
|
|
54
55
|
Bullet.disable_browser_cache = true
|
|
56
|
+
Bullet.xmpp = { :account => 'bullets_account@jabber.org',
|
|
57
|
+
:password => 'bullets_password_for_jabber',
|
|
58
|
+
:receiver => 'your_account@jabber.org',
|
|
59
|
+
:show_online_status => true }
|
|
55
60
|
end
|
|
56
61
|
</code></pre>
|
|
57
62
|
|
|
@@ -64,6 +69,18 @@ It is recommended to config growl notification as follows if your collaborators
|
|
|
64
69
|
end
|
|
65
70
|
</code></pre>
|
|
66
71
|
|
|
72
|
+
and similarly for XMPP:
|
|
73
|
+
<pre><code>
|
|
74
|
+
begin
|
|
75
|
+
require 'xmpp4r'
|
|
76
|
+
Bullet.xmpp = { :account => 'bullets_account@jabber.org',
|
|
77
|
+
:password => 'bullets_password_for_jabber',
|
|
78
|
+
:receiver => 'your_account@jabber.org',
|
|
79
|
+
:show_online_status => true }
|
|
80
|
+
rescue MissingSourceFile
|
|
81
|
+
end
|
|
82
|
+
</code></pre>
|
|
83
|
+
|
|
67
84
|
The code above will enable all five of the Bullet notification systems:
|
|
68
85
|
* <code>Bullet.enable</code>: enable Bullet plugin/gem, otherwise do nothing
|
|
69
86
|
* <code>Bullet.alert</code>: pop up a JavaScript alert in the browser
|
|
@@ -167,8 +184,7 @@ end
|
|
|
167
184
|
|
|
168
185
|
after(:each)
|
|
169
186
|
if Bullet.enable?
|
|
170
|
-
Bullet.
|
|
171
|
-
Bullet.log_notification('Test: ')
|
|
187
|
+
Bullet.perform_out_of_channel_notifications
|
|
172
188
|
Bullet.end_request
|
|
173
189
|
end
|
|
174
190
|
end
|
|
@@ -401,4 +417,4 @@ In the meanwhile, there's a log appended into <code>log/bullet.log</code> file.
|
|
|
401
417
|
****************************************************************************
|
|
402
418
|
|
|
403
419
|
|
|
404
|
-
Copyright (c) 2009 Richard Huang (flyerhzm@gmail.com), released under the MIT license
|
|
420
|
+
Copyright (c) 2009 - 2010 Richard Huang (flyerhzm@gmail.com), released under the MIT license
|
|
@@ -3,7 +3,8 @@ module Bullet
|
|
|
3
3
|
def self.enable
|
|
4
4
|
require 'action_controller'
|
|
5
5
|
case Rails.version
|
|
6
|
-
when /^2.3/
|
|
6
|
+
when /^2.3/
|
|
7
|
+
::ActionController::Dispatcher.middleware.use Bullet::Rack
|
|
7
8
|
::ActionController::Dispatcher.class_eval do
|
|
8
9
|
class <<self
|
|
9
10
|
alias_method :origin_reload_application, :reload_application
|
|
@@ -30,12 +31,11 @@ module Bullet
|
|
|
30
31
|
|
|
31
32
|
if Bullet.notification?
|
|
32
33
|
if response.headers["type"] and response.headers["type"].include? 'text/html' and response.body =~ %r{<html.*</html>}m
|
|
33
|
-
response.body <<= Bullet.
|
|
34
|
+
response.body <<= Bullet.gather_inline_notifications
|
|
34
35
|
response.headers["Content-Length"] = response.body.length.to_s
|
|
35
36
|
end
|
|
36
37
|
|
|
37
|
-
Bullet.
|
|
38
|
-
Bullet.log_notification(request.params['PATH_INFO'])
|
|
38
|
+
Bullet.perform_bullet_out_of_channel_notifications
|
|
39
39
|
end
|
|
40
40
|
Bullet.end_request
|
|
41
41
|
response
|
|
@@ -12,11 +12,11 @@ module Bullet
|
|
|
12
12
|
|
|
13
13
|
if records
|
|
14
14
|
if records.size > 1
|
|
15
|
-
Bullet::Association.add_possible_objects(records)
|
|
16
|
-
Bullet::Counter.add_possible_objects(records)
|
|
15
|
+
Bullet::Detector::Association.add_possible_objects(records)
|
|
16
|
+
Bullet::Detector::Counter.add_possible_objects(records)
|
|
17
17
|
elsif records.size == 1
|
|
18
|
-
Bullet::Association.add_impossible_object(records.first)
|
|
19
|
-
Bullet::Counter.add_impossible_object(records.first)
|
|
18
|
+
Bullet::Detector::Association.add_impossible_object(records.first)
|
|
19
|
+
Bullet::Detector::Counter.add_impossible_object(records.first)
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -33,9 +33,9 @@ module Bullet
|
|
|
33
33
|
records = [records].flatten.compact.uniq
|
|
34
34
|
return if records.empty?
|
|
35
35
|
records.each do |record|
|
|
36
|
-
Bullet::Association.add_object_associations(record, associations)
|
|
36
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
37
37
|
end
|
|
38
|
-
Bullet::Association.add_eager_loadings(records, associations)
|
|
38
|
+
Bullet::Detector::Association.add_eager_loadings(records, associations)
|
|
39
39
|
origin_preload_associations(records, associations, preload_options={})
|
|
40
40
|
end
|
|
41
41
|
end
|
|
@@ -47,10 +47,10 @@ module Bullet
|
|
|
47
47
|
records = origin_find_with_associations(options)
|
|
48
48
|
associations = merge_includes(scope(:find, :include), options[:include])
|
|
49
49
|
records.each do |record|
|
|
50
|
-
Bullet::Association.add_object_associations(record, associations)
|
|
51
|
-
Bullet::
|
|
50
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
51
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
52
52
|
end
|
|
53
|
-
Bullet::Association.add_eager_loadings(records, associations)
|
|
53
|
+
Bullet::Detector::Association.add_eager_loadings(records, associations)
|
|
54
54
|
records
|
|
55
55
|
end
|
|
56
56
|
end
|
|
@@ -60,8 +60,8 @@ module Bullet
|
|
|
60
60
|
alias_method :origin_construct_association, :construct_association
|
|
61
61
|
def construct_association(record, join, row)
|
|
62
62
|
associations = join.reflection.name
|
|
63
|
-
Bullet::Association.add_object_associations(record, associations)
|
|
64
|
-
Bullet::
|
|
63
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
64
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
65
65
|
origin_construct_association(record, join, row)
|
|
66
66
|
end
|
|
67
67
|
end
|
|
@@ -70,7 +70,7 @@ module Bullet
|
|
|
70
70
|
# call one to many associations
|
|
71
71
|
alias_method :origin_load_target, :load_target
|
|
72
72
|
def load_target
|
|
73
|
-
Bullet::
|
|
73
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
74
74
|
origin_load_target
|
|
75
75
|
end
|
|
76
76
|
end
|
|
@@ -81,8 +81,8 @@ module Bullet
|
|
|
81
81
|
def load_target
|
|
82
82
|
# avoid stack level too deep
|
|
83
83
|
result = origin_load_target
|
|
84
|
-
Bullet::
|
|
85
|
-
Bullet::Association.add_possible_objects(result)
|
|
84
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless caller.to_s.include? 'load_target'
|
|
85
|
+
Bullet::Detector::Association.add_possible_objects(result)
|
|
86
86
|
result
|
|
87
87
|
end
|
|
88
88
|
end
|
|
@@ -91,7 +91,7 @@ module Bullet
|
|
|
91
91
|
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
|
92
92
|
def has_cached_counter?
|
|
93
93
|
result = origin_has_cached_counter?
|
|
94
|
-
Bullet::Counter.add_counter_cache(@owner, @reflection.name) unless result
|
|
94
|
+
Bullet::Detector::Counter.add_counter_cache(@owner, @reflection.name) unless result
|
|
95
95
|
result
|
|
96
96
|
end
|
|
97
97
|
end
|
|
@@ -100,7 +100,7 @@ module Bullet
|
|
|
100
100
|
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
|
101
101
|
def has_cached_counter?
|
|
102
102
|
result = origin_has_cached_counter?
|
|
103
|
-
Bullet::Counter.add_counter_cache(@owner, @reflection.name) unless result
|
|
103
|
+
Bullet::Detector::Counter.add_counter_cache(@owner, @reflection.name) unless result
|
|
104
104
|
result
|
|
105
105
|
end
|
|
106
106
|
end
|
|
@@ -9,11 +9,11 @@ module Bullet
|
|
|
9
9
|
def to_a
|
|
10
10
|
records = origin_to_a
|
|
11
11
|
if records.size > 1
|
|
12
|
-
Bullet::Association.add_possible_objects(records)
|
|
13
|
-
Bullet::Counter.add_possible_objects(records)
|
|
12
|
+
Bullet::Detector::Association.add_possible_objects(records)
|
|
13
|
+
Bullet::Detector::Counter.add_possible_objects(records)
|
|
14
14
|
elsif records.size == 1
|
|
15
|
-
Bullet::Association.add_impossible_object(records.first)
|
|
16
|
-
Bullet::Counter.add_impossible_object(records.first)
|
|
15
|
+
Bullet::Detector::Association.add_impossible_object(records.first)
|
|
16
|
+
Bullet::Detector::Counter.add_impossible_object(records.first)
|
|
17
17
|
end
|
|
18
18
|
records
|
|
19
19
|
end
|
|
@@ -27,9 +27,9 @@ module Bullet
|
|
|
27
27
|
records = [records].flatten.compact.uniq
|
|
28
28
|
return if records.empty?
|
|
29
29
|
records.each do |record|
|
|
30
|
-
Bullet::Association.add_object_associations(record, associations)
|
|
30
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
31
31
|
end
|
|
32
|
-
Bullet::Association.add_eager_loadings(records, associations)
|
|
32
|
+
Bullet::Detector::Association.add_eager_loadings(records, associations)
|
|
33
33
|
origin_preload_associations(records, associations, preload_options={})
|
|
34
34
|
end
|
|
35
35
|
end
|
|
@@ -41,10 +41,10 @@ module Bullet
|
|
|
41
41
|
records = origin_find_with_associations
|
|
42
42
|
associations = (@eager_load_values + @includes_values).uniq
|
|
43
43
|
records.each do |record|
|
|
44
|
-
Bullet::Association.add_object_associations(record, associations)
|
|
45
|
-
Bullet::
|
|
44
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
45
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
46
46
|
end
|
|
47
|
-
Bullet::Association.add_eager_loadings(records, associations)
|
|
47
|
+
Bullet::Detector::Association.add_eager_loadings(records, associations)
|
|
48
48
|
records
|
|
49
49
|
end
|
|
50
50
|
end
|
|
@@ -54,8 +54,8 @@ module Bullet
|
|
|
54
54
|
# call join associations
|
|
55
55
|
def construct_association(record, join, row)
|
|
56
56
|
associations = join.reflection.name
|
|
57
|
-
Bullet::Association.add_object_associations(record, associations)
|
|
58
|
-
Bullet::
|
|
57
|
+
Bullet::Detector::Association.add_object_associations(record, associations)
|
|
58
|
+
Bullet::Detector::NPlusOneQuery.call_association(record, associations)
|
|
59
59
|
origin_construct_association(record, join, row)
|
|
60
60
|
end
|
|
61
61
|
end
|
|
@@ -64,7 +64,7 @@ module Bullet
|
|
|
64
64
|
# call one to many associations
|
|
65
65
|
alias_method :origin_load_target, :load_target
|
|
66
66
|
def load_target
|
|
67
|
-
Bullet::
|
|
67
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name)
|
|
68
68
|
origin_load_target
|
|
69
69
|
end
|
|
70
70
|
end
|
|
@@ -75,8 +75,8 @@ module Bullet
|
|
|
75
75
|
def load_target
|
|
76
76
|
# avoid stack level too deep
|
|
77
77
|
result = origin_load_target
|
|
78
|
-
Bullet::
|
|
79
|
-
Bullet::Association.add_possible_objects(result)
|
|
78
|
+
Bullet::Detector::NPlusOneQuery.call_association(@owner, @reflection.name) unless caller.to_s.include? 'load_target'
|
|
79
|
+
Bullet::Detector::Association.add_possible_objects(result)
|
|
80
80
|
result
|
|
81
81
|
end
|
|
82
82
|
end
|
|
@@ -86,7 +86,7 @@ module Bullet
|
|
|
86
86
|
|
|
87
87
|
def has_cached_counter?
|
|
88
88
|
result = origin_has_cached_counter?
|
|
89
|
-
Bullet::Counter.add_counter_cache(@owner, @reflection.name) unless result
|
|
89
|
+
Bullet::Detector::Counter.add_counter_cache(@owner, @reflection.name) unless result
|
|
90
90
|
result
|
|
91
91
|
end
|
|
92
92
|
end
|
|
@@ -95,16 +95,7 @@ module Bullet
|
|
|
95
95
|
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
|
96
96
|
def has_cached_counter?
|
|
97
97
|
result = origin_has_cached_counter?
|
|
98
|
-
Bullet::Counter.add_counter_cache(@owner, @reflection.name) unless result
|
|
99
|
-
result
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
::ActiveRecord::Associations::HasManyThroughAssociation.class_eval do
|
|
104
|
-
alias_method :origin_has_cached_counter?, :has_cached_counter?
|
|
105
|
-
def has_cached_counter?
|
|
106
|
-
result = origin_has_cached_counter?
|
|
107
|
-
Bullet::Counter.add_counter_cache(@owner, @reflection.name) unless result
|
|
98
|
+
Bullet::Detector::Counter.add_counter_cache(@owner, @reflection.name) unless result
|
|
108
99
|
result
|
|
109
100
|
end
|
|
110
101
|
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
module Bullet
|
|
2
|
+
module Detector
|
|
3
|
+
class Association < Base
|
|
4
|
+
class <<self
|
|
5
|
+
def start_request
|
|
6
|
+
@@checked = false
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def clear
|
|
10
|
+
# Note that under ruby class variables are shared among the class
|
|
11
|
+
# that declares them and all classes derived from that class.
|
|
12
|
+
# The following variables are accessible by all classes that
|
|
13
|
+
# derive from Bullet::Detector::Association - changing the variable
|
|
14
|
+
# in one subclass will make the change visible to all subclasses!
|
|
15
|
+
@@object_associations = nil
|
|
16
|
+
@@callers = nil
|
|
17
|
+
@@possible_objects = nil
|
|
18
|
+
@@impossible_objects = nil
|
|
19
|
+
@@call_object_associations = nil
|
|
20
|
+
@@eager_loadings = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_object_associations(object, associations)
|
|
24
|
+
object_associations.add( object, associations )
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def add_call_object_associations(object, associations)
|
|
28
|
+
call_object_associations.add( object, associations )
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def add_possible_objects(objects)
|
|
32
|
+
possible_objects.add objects
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def add_impossible_object(object)
|
|
36
|
+
impossible_objects.add object
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def add_eager_loadings(objects, associations)
|
|
40
|
+
objects = Array(objects)
|
|
41
|
+
|
|
42
|
+
eager_loadings.each do |k, v|
|
|
43
|
+
key_objects_overlap = k & objects
|
|
44
|
+
|
|
45
|
+
next if key_objects_overlap.empty?
|
|
46
|
+
|
|
47
|
+
if key_objects_overlap == k
|
|
48
|
+
eager_loadings.add k, associations
|
|
49
|
+
break
|
|
50
|
+
|
|
51
|
+
else
|
|
52
|
+
eager_loadings.merge key_objects_overlap, ( eager_loadings[k].dup << associations )
|
|
53
|
+
|
|
54
|
+
keys_without_objects = k - objects
|
|
55
|
+
eager_loadings.merge keys_without_objects, eager_loadings[k] unless keys_without_objects.empty?
|
|
56
|
+
|
|
57
|
+
eager_loadings.delete(k)
|
|
58
|
+
objects = objects - k
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
eager_loadings.add objects, associations unless objects.empty?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
def possible?(object)
|
|
67
|
+
possible_objects.contains? object
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def impossible?(object)
|
|
71
|
+
impossible_objects.contains? object
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# check if object => associations already exists in object_associations.
|
|
75
|
+
def association?(object, associations)
|
|
76
|
+
object_associations.each do |key, value|
|
|
77
|
+
next unless key == object
|
|
78
|
+
|
|
79
|
+
value.each do |v|
|
|
80
|
+
result = v.is_a?(Hash) ? v.has_key?(associations) : v == associations
|
|
81
|
+
return true if result
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
return false
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# object_associations keep the object relationships
|
|
89
|
+
# that the object has many associations.
|
|
90
|
+
# e.g. { <Post id:1> => [:comments] }
|
|
91
|
+
# the object_associations keep all associations that may be or may no be
|
|
92
|
+
# unpreload associations or unused preload associations.
|
|
93
|
+
def object_associations
|
|
94
|
+
@@object_associations ||= Bullet::Registry::Base.new
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# call_object_assciations keep the object relationships
|
|
98
|
+
# that object.associations is called.
|
|
99
|
+
# e.g. { <Post id:1> => [:comments] }
|
|
100
|
+
# they are used to detect unused preload associations.
|
|
101
|
+
def call_object_associations
|
|
102
|
+
@@call_object_associations ||= Bullet::Registry::Base.new
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# possible_objects keep the class to object relationships
|
|
106
|
+
# that the objects may cause N+1 query.
|
|
107
|
+
# e.g. { Post => [<Post id:1>, <Post id:2>] }
|
|
108
|
+
def possible_objects
|
|
109
|
+
@@possible_objects ||= Bullet::Registry::Object.new
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# impossible_objects keep the class to objects relationships
|
|
113
|
+
# that the objects may not cause N+1 query.
|
|
114
|
+
# e.g. { Post => [<Post id:1>, <Post id:2>] }
|
|
115
|
+
# Notice: impossible_objects are not accurate,
|
|
116
|
+
# if find collection returns only one object, then the object is impossible object,
|
|
117
|
+
# impossible_objects are used to avoid treating 1+1 query to N+1 query.
|
|
118
|
+
def impossible_objects
|
|
119
|
+
@@impossible_objects ||= Bullet::Registry::Object.new
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# eager_loadings keep the object relationships
|
|
123
|
+
# that the associations are preloaded by find :include.
|
|
124
|
+
# e.g. { [<Post id:1>, <Post id:2>] => [:comments, :user] }
|
|
125
|
+
def eager_loadings
|
|
126
|
+
@@eager_loadings ||= Bullet::Registry::Association.new
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def callers
|
|
130
|
+
@@callers ||= []
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Bullet
|
|
2
|
+
module Detector
|
|
3
|
+
class Counter < Base
|
|
4
|
+
def self.clear
|
|
5
|
+
@@possible_objects = nil
|
|
6
|
+
@@impossible_objects = nil
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.add_counter_cache(object, associations)
|
|
10
|
+
if conditions_met?( object, associations )
|
|
11
|
+
create_notification object.class, associations
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.add_possible_objects(objects)
|
|
16
|
+
possible_objects.add objects
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.add_impossible_object(object)
|
|
20
|
+
impossible_objects.add object
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
def self.create_notification( klazz, associations )
|
|
25
|
+
notice = Bullet::Notification::CounterCache.new klazz, associations
|
|
26
|
+
Bullet.notification_collector.add notice
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.possible_objects
|
|
30
|
+
@@possible_objects ||= Bullet::Registry::Object.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.impossible_objects
|
|
34
|
+
@@impossible_objects ||= Bullet::Registry::Object.new
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.conditions_met?( object, associations )
|
|
38
|
+
possible_objects.contains?( object ) and
|
|
39
|
+
!impossible_objects.contains?( object )
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Bullet
|
|
2
|
+
module Detector
|
|
3
|
+
class NPlusOneQuery < Association
|
|
4
|
+
# executed when object.assocations is called.
|
|
5
|
+
# first, it keeps this method call for object.association.
|
|
6
|
+
# then, it checks if this associations call is unpreload.
|
|
7
|
+
# if it is, keeps this unpreload associations and caller.
|
|
8
|
+
def self.call_association(object, associations)
|
|
9
|
+
@@checked = true
|
|
10
|
+
add_call_object_associations(object, associations)
|
|
11
|
+
|
|
12
|
+
if conditions_met?(object, associations)
|
|
13
|
+
caller_in_project
|
|
14
|
+
create_notification object.class, associations
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
def self.create_notification(klazz, associations)
|
|
20
|
+
notice = Bullet::Notification::NPlusOneQuery.new( callers, klazz, associations )
|
|
21
|
+
Bullet.notification_collector.add( notice )
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# decide whether the object.associations is unpreloaded or not.
|
|
25
|
+
def self.conditions_met?(object, associations)
|
|
26
|
+
possible?(object) and
|
|
27
|
+
!impossible?(object) and
|
|
28
|
+
!association?(object, associations)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.caller_in_project
|
|
32
|
+
vender_root ||= File.join(Rails.root, 'vendor')
|
|
33
|
+
callers << caller.select { |c| c =~ /#{Rails.root}/ }.
|
|
34
|
+
reject { |c| c =~ /#{vender_root}/ }
|
|
35
|
+
callers.uniq!
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Bullet
|
|
2
|
+
module Detector
|
|
3
|
+
class UnusedEagerAssociation < Association
|
|
4
|
+
# check if there are unused preload associations.
|
|
5
|
+
# for each object => association
|
|
6
|
+
# get related_objects from eager_loadings associated with object and associations
|
|
7
|
+
# get call_object_association from associations of call_object_associations whose object is in related_objects
|
|
8
|
+
# if association not in call_object_association, then the object => association - call_object_association is ununsed preload assocations
|
|
9
|
+
def self.check_unused_preload_associations
|
|
10
|
+
@@checked = true
|
|
11
|
+
object_associations.each do |object, association|
|
|
12
|
+
object_association_diff = diff_object_association object, association
|
|
13
|
+
next if object_association_diff.empty?
|
|
14
|
+
|
|
15
|
+
create_notification object.class, object_association_diff
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
protected
|
|
20
|
+
def self.create_notification(klazz, associations)
|
|
21
|
+
notice = Bullet::Notification::UnusedEagerLoading.new( klazz, associations )
|
|
22
|
+
Bullet.notification_collector.add( notice )
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.call_object_association( object, association )
|
|
26
|
+
eager_loadings.similarly_associated( object, association ).
|
|
27
|
+
collect { |related_object| call_object_associations[related_object] }.
|
|
28
|
+
compact.
|
|
29
|
+
flatten.
|
|
30
|
+
uniq
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.diff_object_association( object, association )
|
|
34
|
+
potential_objects = association - call_object_association( object, association )
|
|
35
|
+
potential_objects.reject {|a| a.is_a?( Hash ) }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module Bullet
|
|
2
|
+
module Detector
|
|
3
|
+
autoload :Base, 'bullet/detector/base'
|
|
4
|
+
autoload :Association, 'bullet/detector/association'
|
|
5
|
+
autoload :NPlusOneQuery, 'bullet/detector/n_plus_one_query'
|
|
6
|
+
autoload :UnusedEagerAssociation, 'bullet/detector/unused_eager_association'
|
|
7
|
+
autoload :Counter, 'bullet/detector/counter'
|
|
8
|
+
end
|
|
9
|
+
end
|