flipsasser-bullet 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Richard Huang (flyerhzm@gmail.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,197 @@
1
+ *Please note!* This is the Bullet gem as written by Richard Huang, with a few added niceties:
2
+
3
+ - It has been refactored to support Rails initializers
4
+ - Growl, console.log, and Rails.logger support have been added
5
+
6
+ # Bullet #
7
+
8
+ 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.
9
+
10
+ Best practice is to use Bullet while building your application, but UNINSTALL OR DEACTIVATE IT when you deploy to a production server. The last thing you want is your clients getting alerts about how lazy you are.
11
+
12
+ ## Installation ##
13
+
14
+ ### Get the source ##
15
+
16
+ You can add Bullet to your Rails gem requirements:
17
+
18
+ config.gem 'flipsasser-bullet', :lib => nil, :source => 'http://gems.github.com'
19
+
20
+ Or you can install it as a gem like so:
21
+
22
+ sudo gem install flipsasser-bullet --source http://gems.github.com
23
+
24
+ Finally, you can install it as a Rails plugins:
25
+
26
+ ruby script/plugin install git://github.com/flipsasser/bullet.git
27
+
28
+ ### Configure Bullet ###
29
+
30
+ Bullet boots up from a Rails initializer. It won't do ANYTHING unless you tell it to explicitly. Add a RAILS_ROOT/config/initializers/bullet.rb initializer with the following code:
31
+
32
+ # Only use Bullet in development...
33
+ if Bullet.enable = RAILS_ENV == 'development'
34
+ Bullet::Association.alert = true
35
+ Bullet::Association.bullet_logger = true
36
+ Bullet::Association.console = true
37
+ Bullet::Association.growl = true
38
+ Bullet::Association.rails_logger = true
39
+ end
40
+
41
+ The code above will enable all five of the Bullet notification systems:
42
+
43
+ - `Bullet::Association.alert`: pop up a JavaScript alert in the browser
44
+ - `Bullet::Association.bullet_logger`: log to the Bullet log file (RAILS_ROOT/log/bullet.log)
45
+ - `Bullet::Association.rails_logger`: add warnings directly to the Rails log
46
+ - `Bullet::Association.console`: log warnings to your browser's console.log (Safari/Webkit browsers or Firefox w/Firebug installed)
47
+ - `Bullet::Association.growl`: pop up Growl warnings if your system has Growl installed. Requires a little bit of configuration
48
+
49
+ ### The Bullet log ###
50
+
51
+ The Bullet log (log/bullet.log) will look something like this:
52
+
53
+ 2009-08-25 20:40:17[INFO] N+1 Query: PATH_INFO: /posts; model: Post => associations: [comments]·
54
+ Add to your finder: :include => [:comments]
55
+ 2009-08-25 20:40:17[INFO] N+1 Query: method call stack:·
56
+ /Users/richard/Downloads/test/app/views/posts/index.html.erb:11:in `_run_erb_app47views47posts47index46html46erb'
57
+ /Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `each'
58
+ /Users/richard/Downloads/test/app/views/posts/index.html.erb:8:in `_run_erb_app47views47posts47index46html46erb'
59
+ /Users/richard/Downloads/test/app/controllers/posts_controller.rb:7:in `index'
60
+
61
+ The first two lines are notifications that N+1 queries have been encountered. The remaining lines are stack traces so you can find exactly where the queries were invoked in your code, and fix them.
62
+
63
+ Bullet also has a Rake task, `rake bullet:log:clear`, which will clear out the Bullet log.
64
+
65
+ ### Growl Support ###
66
+
67
+ To get Growl support up-and-running for Bullet, follow the steps below:
68
+
69
+ 1. Install the ruby-growl gem: `sudo gem install ruby-growl`
70
+ 2. Open the Growl preference pane in Systems Preferences
71
+ 3. Click the "Network" tab
72
+ 4. Make sure both "Listen for incoming notifications" and "Allow remote application registration" are checked. *Note:* If you set a password, you will need to set `Bullet::Association.growl_password = 'your_growl_password'` in the initializer.
73
+ 5. Restart Growl ("General" tab -> Stop Growl -> Start Growl)
74
+ 6. Boot up your application. Bullet will automatically send a Growl notification when Growl is turned on. If you do not see it when your application loads, make sure it is enabled in your initializer and double-check the steps above.
75
+
76
+ ## Step by step example ##
77
+
78
+ Bullet is designed to function as you browse through your application in development. It will alert you whenever it encounters N+1 queries or unused eager loading.
79
+
80
+ *Important*: It is strongly recommended you disable your browser's cache.
81
+
82
+ 1. Setup your test environment
83
+
84
+ $ rails test
85
+ $ cd test
86
+ $ script/generate scaffold post name:string
87
+ $ script/generate scaffold comment name:string post_id:integer
88
+ $ rake db:migrate
89
+
90
+ 2. Add relationships to `app/model/post.rb` and `app/model/comment.rb`
91
+
92
+ class Post < ActiveRecord::Base
93
+ has_many :comments
94
+ end
95
+
96
+ class Comment < ActiveRecord::Base
97
+ belongs_to :post
98
+ end
99
+
100
+ 3. Go to script/console and execute
101
+
102
+ post1 = Post.create(:name => 'first')
103
+ post2 = Post.create(:name => 'second')
104
+ post1.comments.create(:name => 'first')
105
+ post1.comments.create(:name => 'second')
106
+ post2.comments.create(:name => 'third')
107
+ post2.comments.create(:name => 'fourth')
108
+
109
+ 4. Change the `app/views/posts/index.html.erb` to produce an N+1 query
110
+
111
+ <% @posts.each do |post| %>
112
+ <tr>
113
+ <td><%= h post.name %></td>
114
+ <td><%= post.comments.collect(&:name) %></td>
115
+ <td><%= link_to 'Show', post %></td>
116
+ <td><%= link_to 'Edit', edit_post_path(post) %></td>
117
+ <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
118
+ </tr>
119
+ <% end %>
120
+
121
+ 5. Install the Bullet plugin
122
+
123
+ $ script/plugin install git://github.com/flipsasser/bullet.git
124
+
125
+ 6. Enable the bullet plugin in development with a Rails initializer (RAILS_ROOT/config/intializers/bullet.rb):
126
+
127
+ if Bullet.enable = RAILS_ENV == 'development'
128
+ Bullet::Association.alert = true
129
+ end
130
+
131
+ 7. Boot up your development server
132
+
133
+ $ script/server
134
+
135
+ 8. Visit http://localhost:3000/posts in your browser. Bullet will alert you that an N+1 query has occurred.
136
+
137
+ The request has N+1 queries as follows:
138
+ model: Post => associations: [comment]
139
+
140
+ ... which means there is an N+1 query from the Post object to the comments association.
141
+
142
+ 9. Fix the N+1 query. Change `app/controllers/posts_controller.rb`:
143
+
144
+ def index
145
+ @posts = Post.find(:all, :include => :comments)
146
+
147
+ respond_to do |format|
148
+ format.html # index.html.erb
149
+ format.xml { render :xml => @posts }
150
+ end
151
+ end
152
+
153
+ 10. Refresh your browser. No alert should show up.
154
+
155
+ This is because the original query, which read:
156
+
157
+ Post Load (1.0ms) SELECT * FROM "posts"
158
+ Comment Load (0.4ms) SELECT * FROM "comments" WHERE ("comments".post_id = 1)
159
+ Comment Load (0.3ms) SELECT * FROM "comments" WHERE ("comments".post_id = 2)
160
+
161
+ ... would cause one additional query for each post it found. The new SQL should look like this:
162
+
163
+ Post Load (0.5ms) SELECT * FROM "posts"
164
+ Comment Load (0.5ms) SELECT "comments".* FROM "comments" WHERE ("comments".post_id IN (1,2))
165
+
166
+ And your N+1 query is fixed. Cool!
167
+
168
+ 11. Now simulate unused eager loading. Change `app/controllers/posts_controller.rb` and `app/views/posts/index.html.erb`
169
+
170
+ app/controllers/posts_controller.rb:
171
+
172
+ def index
173
+ @posts = Post.find(:all, :include => :comments)
174
+
175
+ respond_to do |format|
176
+ format.html # index.html.erb
177
+ format.xml { render :xml => @posts }
178
+ end
179
+ end
180
+
181
+ app/views/posts/index.html.erb:
182
+
183
+ <% @posts.each do |post| %>
184
+ <tr>
185
+ <td><%=h post.name %></td>
186
+ <td><%= link_to 'Show', post %></td>
187
+ <td><%= link_to 'Edit', edit_post_path(post) %></td>
188
+ <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete %></td>
189
+ </tr>
190
+ <% end %>
191
+
192
+ 12. Refresh your browser. Bullet will alert you that you have unused, eager-loaded objects.
193
+
194
+ The request has unused preload associations as follows:
195
+ model: Post => associations: [comment]
196
+
197
+ Copyright (c) 2009 Richard Huang (flyerhzm@gmail.com), released under the MIT license.
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ require 'rake'
2
+ require 'spec/rake/spectask'
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
+ desc "Run all specs in spec directory"
19
+ Spec::Rake::SpecTask.new(:spec) do |t|
20
+ t.spec_files = FileList['spec/spec_helper.rb', 'spec/**/*_spec.rb']
21
+ end
22
+
23
+ Jeweler::Tasks.new do |gemspec|
24
+ gemspec.name = "bullet"
25
+ gemspec.summary = "A plugin to kill N+1 queries and unused eager loading"
26
+ 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."
27
+ gemspec.email = "flip@x451.com"
28
+ gemspec.homepage = "http://github.com/flipsasser/bullet"
29
+ gemspec.authors = ["Richard Huang", "Flip Sasser"]
30
+ gemspec.files.exclude '.gitignore'
31
+ gemspec.files.exclude 'log/'
32
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.1.0
data/bullet.gemspec ADDED
@@ -0,0 +1,55 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{bullet}
8
+ s.version = "1.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Richard Huang", "Flip Sasser"]
12
+ s.date = %q{2009-08-27}
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{flip@x451.com}
15
+ s.extra_rdoc_files = [
16
+ "README.markdown"
17
+ ]
18
+ s.files = [
19
+ "MIT-LICENSE",
20
+ "README.markdown",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "bullet.gemspec",
24
+ "lib/bullet.rb",
25
+ "lib/bullet/active_record.rb",
26
+ "lib/bullet/association.rb",
27
+ "lib/bullet/logger.rb",
28
+ "lib/bulletware.rb",
29
+ "rails/init.rb",
30
+ "spec/bullet_association_spec.rb",
31
+ "spec/spec.opts",
32
+ "spec/spec_helper.rb",
33
+ "tasks/bullet_tasks.rake"
34
+ ]
35
+ s.has_rdoc = true
36
+ s.homepage = %q{http://github.com/flipsasser/bullet}
37
+ s.rdoc_options = ["--charset=UTF-8"]
38
+ s.require_paths = ["lib"]
39
+ s.rubygems_version = %q{1.3.1}
40
+ s.summary = %q{A plugin to kill N+1 queries and unused eager loading}
41
+ s.test_files = [
42
+ "spec/bullet_association_spec.rb",
43
+ "spec/spec_helper.rb"
44
+ ]
45
+
46
+ if s.respond_to? :specification_version then
47
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
48
+ s.specification_version = 2
49
+
50
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
51
+ else
52
+ end
53
+ else
54
+ end
55
+ end
data/lib/bullet.rb ADDED
@@ -0,0 +1,21 @@
1
+ module Bullet
2
+ @@enable = nil
3
+
4
+ class <<self
5
+ def enable=(enable)
6
+ if enable != @@enable && @@enable = enable
7
+ Bullet::ActiveRecord.enable
8
+ ActionController::Dispatcher.middleware.use Bulletware
9
+ end
10
+ @@enable
11
+ end
12
+
13
+ def enable?
14
+ class_variables.include?('@@enable') and @@enable == true
15
+ end
16
+ end
17
+
18
+ autoload :ActiveRecord, 'bullet/active_record'
19
+ autoload :Association, 'bullet/association'
20
+ autoload :BulletLogger, 'bullet/logger'
21
+ end
@@ -0,0 +1,62 @@
1
+ module Bullet
2
+ module ActiveRecord
3
+ def self.enable
4
+ ::ActiveRecord::Base.class_eval do
5
+ class << self
6
+ alias_method :bullet_find_every, :find_every
7
+ # if select a collection of objects, then these objects have possible to cause N+1 query
8
+ # if select only one object, then the only one object has impossible to cause N+1 query
9
+ def find_every(options)
10
+ records = bullet_find_every(options)
11
+
12
+ if records
13
+ if records.size > 1
14
+ Bullet::Association.add_possible_objects(records)
15
+ elsif records.size == 1
16
+ Bullet::Association.add_impossible_object(records.first)
17
+ end
18
+ end
19
+
20
+ records
21
+ end
22
+
23
+ alias_method :bullet_preload_associations, :preload_associations
24
+ # add include for one to many associations query
25
+ def preload_associations(records, associations, preload_options={})
26
+ records = [records].flatten.compact.uniq
27
+ return if records.empty?
28
+ records.each do |record|
29
+ Bullet::Association.add_association(record, associations)
30
+ end
31
+ bullet_preload_associations(records, associations, preload_options={})
32
+ end
33
+
34
+ # define one to many associations
35
+ alias_method :bullet_collection_reader_method, :collection_reader_method
36
+ def collection_reader_method(reflection, association_proxy_class)
37
+ Bullet::Association.define_association(self, reflection.name)
38
+ bullet_collection_reader_method(reflection, association_proxy_class)
39
+ end
40
+ end
41
+ end
42
+
43
+ ::ActiveRecord::Associations::AssociationCollection.class_eval do
44
+ # call one to many associations
45
+ alias_method :bullet_load_target, :load_target
46
+ def load_target
47
+ Bullet::Association.call_association(@owner, @reflection.name)
48
+ bullet_load_target
49
+ end
50
+ end
51
+
52
+ ::ActiveRecord::Associations::AssociationProxy.class_eval do
53
+ # call has_one association
54
+ alias_method :bullet_load_target, :load_target
55
+ def load_target
56
+ Bullet::Association.call_association(@owner, @reflection.name)
57
+ bullet_load_target
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,277 @@
1
+ module Bullet
2
+ class BulletAssociationError < StandardError
3
+ end
4
+
5
+ class Association
6
+ class <<self
7
+ @@alert = nil
8
+ @@bullet_logger = nil
9
+ @@console = nil
10
+ @@growl = nil
11
+ @@growl_password = nil
12
+ @@rails_logger = nil
13
+
14
+ def start_request
15
+ # puts "start request"
16
+ end
17
+
18
+ def end_request
19
+ # puts "end request"
20
+ @@object_associations = nil
21
+ @@unpreload_associations = nil
22
+ @@unused_preload_associations = nil
23
+ @@callers = nil
24
+ @@possible_objects = nil
25
+ @@impossible_objects = nil
26
+ @@call_object_associations = nil
27
+ end
28
+
29
+ def alert=(alert)
30
+ @@alert = alert
31
+ end
32
+
33
+ def bullet_logger=(bullet_logger)
34
+ if @@bullet_logger = bullet_logger
35
+ @@logger_file = File.open(Bullet::BulletLogger::LOG_FILE, 'a+')
36
+ @@logger = Bullet::BulletLogger.new(@@logger_file)
37
+ end
38
+ end
39
+
40
+ def console=(console)
41
+ @@console = console
42
+ end
43
+
44
+ def growl=(growl)
45
+ if growl
46
+ begin
47
+ require 'ruby-growl'
48
+ growl = Growl.new('localhost', 'ruby-growl', ['Bullet Notification'], nil, @@growl_password)
49
+ growl.notify('Bullet Notification', 'Bullet Notification', 'Bullet Growl notifications have been turned on')
50
+ rescue MissingSourceFile
51
+ raise BulletAssociationError.new('You must install the ruby-growl gem to use Growl notifications: `sudo gem install ruby-growl`')
52
+ end
53
+ end
54
+ @@growl = growl
55
+ end
56
+
57
+ def growl_password=(growl_password)
58
+ @@growl_password = growl_password
59
+ end
60
+
61
+ def rails_logger=(rails_logger)
62
+ @@rails_logger = rails_logger
63
+ end
64
+
65
+ def check_unused_preload_associations
66
+ object_associations.each do |object, association|
67
+ call_object_association = call_object_associations[object] || []
68
+ add_unused_preload_associations(object.class, association - call_object_association) unless (association - call_object_association).empty?
69
+ end
70
+ end
71
+
72
+ def has_bad_assocations?
73
+ check_unused_preload_associations
74
+ has_unpreload_associations? or has_unused_preload_associations?
75
+ end
76
+
77
+ def has_unused_preload_associations?
78
+ !unused_preload_associations.empty?
79
+ end
80
+
81
+ def has_unpreload_associations?
82
+ !unpreload_associations.empty?
83
+ end
84
+
85
+ def bad_associations_alert
86
+ str = ''
87
+ if @@alert || @@console || @@growl
88
+ response = []
89
+ if has_unused_preload_associations?
90
+ response.push("Unused preload associations detected:\n")
91
+ response.push(*@@unused_preload_associations.to_a.collect{|klazz, associations| klazz_associations_str(klazz, associations)}.join('\n'))
92
+ end
93
+ if has_unpreload_associations?
94
+ response.push("#{"\n" unless response.empty?}N+1 queries detected:\n")
95
+ response.push(*@@unpreload_associations.to_a.collect{|klazz, associations| " #{klazz} => [#{associations.map(&:inspect).join(', ')}]"}.join('\n'))
96
+ end
97
+ end
98
+ if @@alert
99
+ str << wrap_js_association("alert(#{response.join("\n").inspect});")
100
+ end
101
+ if @@console
102
+ str << wrap_js_association("if (typeof(console) != 'undefined' && console.log) console.log(#{response.join("\n").inspect});")
103
+ end
104
+ if @@growl
105
+ begin
106
+ growl = Growl.new('localhost', 'ruby-growl', ['Bullet Notification'], nil, @@growl_password)
107
+ growl.notify('Bullet Notification', 'Bullet Notification', response.join("\n"))
108
+ rescue
109
+ end
110
+ str << '<!-- Sent Growl notification -->'
111
+ end
112
+ str
113
+ end
114
+
115
+ def wrap_js_association(message)
116
+ str = ''
117
+ str << "<script type=\"text/javascript\">/*<![CDATA[*/"
118
+ str << message
119
+ str << "/*]]>*/</script>\n"
120
+ end
121
+
122
+ def log_bad_associations(path)
123
+ if (@@bullet_logger || @@rails_logger) && (!unpreload_associations.empty? || !unused_preload_associations.empty?)
124
+ Rails.logger.warn '' if @@rails_logger
125
+ unused_preload_associations.each do |klazz, associations|
126
+ log = ["Unused preload associations: #{path}", klazz_associations_str(klazz, associations), " Remove from your finder: #{associations_str(associations)}"].join("\n")
127
+ @@logger.info(log) if @@bullet_logger
128
+ Rails.logger.warn(log) if @@rails_logger
129
+ end
130
+ unpreload_associations.each do |klazz, associations|
131
+ log = ["N+1 Query in #{path}", klazz_associations_str(klazz, associations), " Add to your finder: #{associations_str(associations)}"].join("\n")
132
+ @@logger.info(log) if @@bullet_logger
133
+ Rails.logger.warn(log) if @@rails_logger
134
+ end
135
+ callers.each do |c|
136
+ log = ["N+1 Query method call stack", c.map{|line| " #{line}"}].flatten.join("\n")
137
+ @@logger.info(log) if @@bullet_logger
138
+ Rails.logger.warn(log) if @@rails_logger
139
+ end
140
+ @@logger_file.flush if @@bullet_logger
141
+ end
142
+ end
143
+
144
+ def bad_associations_str(bad_associations)
145
+ # puts bad_associations.inspect
146
+ bad_associations.to_a.collect{|klazz, associations| klazz_associations_str(klazz, associations)}.join('\\n')
147
+ end
148
+
149
+ def klazz_associations_str(klazz, associations)
150
+ " #{klazz} => [#{associations.map(&:inspect).join(', ')}]"
151
+ end
152
+
153
+ def associations_str(associations)
154
+ ":include => #{associations.map{|a| a.to_sym unless a.is_a? Hash}.inspect}"
155
+ end
156
+
157
+ def has_klazz_association(klazz)
158
+ !klazz_associations[klazz].nil? and klazz_associations.keys.include?(klazz)
159
+ end
160
+
161
+ def define_association(klazz, associations)
162
+ # puts "define association, #{klazz} => #{associations.inspect}"
163
+ add_klazz_associations(klazz, associations)
164
+ end
165
+
166
+ def call_association(object, associations)
167
+ # puts "call association, #{object} => #{associations.inspect}"
168
+ add_call_object_associations(object, associations)
169
+ if unpreload_associations?(object, associations)
170
+ add_unpreload_associations(object.class, associations)
171
+ caller_in_project
172
+ end
173
+ end
174
+
175
+ def unpreload_associations?(object, associations)
176
+ klazz = object.class
177
+ (!possible_objects[klazz].nil? and possible_objects[klazz].include?(object)) and
178
+ (impossible_objects[klazz].nil? or !impossible_objects[klazz].include?(object)) and
179
+ (object_associations[object].nil? or !object_associations[object].include?(associations))
180
+ end
181
+
182
+ def add_unpreload_associations(klazz, associations)
183
+ # puts "add unpreload associations, #{klazz} => #{associations.inspect}"
184
+ unpreload_associations[klazz] ||= []
185
+ unpreload_associations[klazz] << associations
186
+ unique(unpreload_associations[klazz])
187
+ end
188
+
189
+ def add_unused_preload_associations(klazz, associations)
190
+ # puts "add unused preload associations, #{klazz} => #{associations.inspect}"
191
+ unused_preload_associations[klazz] ||= []
192
+ unused_preload_associations[klazz] << associations
193
+ unique(unused_preload_associations[klazz])
194
+ end
195
+
196
+ def add_association(object, associations)
197
+ # puts "add associations, #{object} => #{associations.inspect}"
198
+ object_associations[object] ||= []
199
+ object_associations[object] << associations
200
+ unique(object_associations[object])
201
+ end
202
+
203
+ def add_call_object_associations(object, associations)
204
+ # puts "add call object associations, #{object} => #{associations.inspect}"
205
+ call_object_associations[object] ||= []
206
+ call_object_associations[object] << associations
207
+ unique(call_object_associations[object])
208
+ end
209
+
210
+ def add_possible_objects(objects)
211
+ # puts "add possible objects, #{objects.inspect}"
212
+ klazz= objects.first.class
213
+ possible_objects[klazz] ||= []
214
+ possible_objects[klazz] << objects
215
+ unique(possible_objects[klazz])
216
+ end
217
+
218
+ def add_impossible_object(object)
219
+ # puts "add impossible object, #{object}"
220
+ klazz = object.class
221
+ impossible_objects[klazz] ||= []
222
+ impossible_objects[klazz] << object
223
+ impossible_objects[klazz].uniq!
224
+ end
225
+
226
+ def add_klazz_associations(klazz, associations)
227
+ # puts "define associations, #{klazz} => #{associations.inspect}"
228
+ klazz_associations[klazz] ||= []
229
+ klazz_associations[klazz] << associations
230
+ unique(klazz_associations[klazz])
231
+ end
232
+
233
+ def unique(array)
234
+ array.flatten!
235
+ array.uniq!
236
+ end
237
+
238
+ def unpreload_associations
239
+ @@unpreload_associations ||= {}
240
+ end
241
+
242
+ def unused_preload_associations
243
+ @@unused_preload_associations ||= {}
244
+ end
245
+
246
+ def object_associations
247
+ @@object_associations ||= {}
248
+ end
249
+
250
+ def call_object_associations
251
+ @@call_object_associations ||= {}
252
+ end
253
+
254
+ def possible_objects
255
+ @@possible_objects ||= {}
256
+ end
257
+
258
+ def impossible_objects
259
+ @@impossible_objects ||= {}
260
+ end
261
+
262
+ def klazz_associations
263
+ @@klazz_associations ||= {}
264
+ end
265
+
266
+ VENDOR_ROOT = File.join(RAILS_ROOT, 'vendor')
267
+ def caller_in_project
268
+ callers << caller.select {|c| c =~ /#{RAILS_ROOT}/}.reject {|c| c =~ /#{VENDOR_ROOT}/}
269
+ callers.uniq!
270
+ end
271
+
272
+ def callers
273
+ @@callers ||= []
274
+ end
275
+ end
276
+ end
277
+ end