module Bullet class BulletAssociationError < StandardError end class Association class < [#{associations.map(&:inspect).join(', ')}]"}.join("\n")) end end if @@alert str << wrap_js_association("alert(#{response.join("\n").inspect});") end if @@console str << wrap_js_association("if (typeof(console) != 'undefined' && console.log) console.log(#{response.join("\n").inspect});") end if @@growl begin growl = Growl.new('localhost', 'ruby-growl', ['Bullet Notification'], nil, @@growl_password) growl.notify('Bullet Notification', 'Bullet Notification', response.join("\n")) rescue end str << '' end str end def wrap_js_association(message) str = '' str << "\n" end def log_bad_associations(path) if (@@bullet_logger || @@rails_logger) && (!unpreload_associations.empty? || !unused_preload_associations.empty?) Rails.logger.warn '' if @@rails_logger unused_preload_associations.each do |klazz, associations| log = ["Unused eager loadings: #{path}", klazz_associations_str(klazz, associations), " Remove from your finder: #{associations_str(associations)}"].join("\n") @@logger.info(log) if @@bullet_logger Rails.logger.warn(log) if @@rails_logger end unpreload_associations.each do |klazz, associations| log = ["N+1 Query in #{path}", klazz_associations_str(klazz, associations), " Add to your finder: #{associations_str(associations)}"].join("\n") @@logger.info(log) if @@bullet_logger Rails.logger.warn(log) if @@rails_logger end callers.each do |c| log = ["N+1 Query method call stack", c.map{|line| " #{line}"}].flatten.join("\n") @@logger.info(log) if @@bullet_logger Rails.logger.warn(log) if @@rails_logger end @@logger_file.flush if @@bullet_logger end end def bad_associations_str(bad_associations) # puts bad_associations.inspect bad_associations.to_a.collect{|klazz, associations| klazz_associations_str(klazz, associations)}.join("\n") end def klazz_associations_str(klazz, associations) " #{klazz} => [#{associations.map(&:inspect).join(', ')}]" end def associations_str(associations) ":include => #{associations.map{|a| a.to_sym unless a.is_a? Hash}.inspect}" end def has_klazz_association(klazz) !klazz_associations[klazz].nil? and klazz_associations.keys.include?(klazz) end def define_association(klazz, associations) # puts "define association, #{klazz} => #{associations.inspect}" add_klazz_associations(klazz, associations) end def call_association(object, associations) # puts "call association, #{object} => #{associations.inspect}" add_call_object_associations(object, associations) if unpreload_associations?(object, associations) add_unpreload_associations(object.class, associations) caller_in_project end end def unpreload_associations?(object, associations) klazz = object.class (!possible_objects[klazz].nil? and possible_objects[klazz].include?(object)) and (impossible_objects[klazz].nil? or !impossible_objects[klazz].include?(object)) and (object_associations[object].nil? or !object_associations[object].include?(associations)) end def add_unpreload_associations(klazz, associations) # puts "add unpreload associations, #{klazz} => #{associations.inspect}" unpreload_associations[klazz] ||= [] unpreload_associations[klazz] << associations unique(unpreload_associations[klazz]) end def add_unused_preload_associations(klazz, associations) # puts "add unused preload associations, #{klazz} => #{associations.inspect}" unused_preload_associations[klazz] ||= [] unused_preload_associations[klazz] << associations unique(unused_preload_associations[klazz]) end def add_association(object, associations) # puts "add associations, #{object} => #{associations.inspect}" object_associations[object] ||= [] object_associations[object] << associations unique(object_associations[object]) end def add_call_object_associations(object, associations) # puts "add call object associations, #{object} => #{associations.inspect}" call_object_associations[object] ||= [] call_object_associations[object] << associations unique(call_object_associations[object]) end def add_possible_objects(objects) # puts "add possible objects, #{objects.inspect}" klazz= objects.first.class possible_objects[klazz] ||= [] possible_objects[klazz] << objects unique(possible_objects[klazz]) end def add_impossible_object(object) # puts "add impossible object, #{object}" klazz = object.class impossible_objects[klazz] ||= [] impossible_objects[klazz] << object impossible_objects[klazz].uniq! end def add_klazz_associations(klazz, associations) # puts "define associations, #{klazz} => #{associations.inspect}" klazz_associations[klazz] ||= [] klazz_associations[klazz] << associations unique(klazz_associations[klazz]) end def add_eager_loadings(objects, associations) # puts "add eager loadings, #{objects.inspect} => #{associations.inspect}" objects = Array(objects) eager_loadings[objects] ||= [] eager_loadings[objects] << associations unique(eager_loadings[objects]) end def unique(array) array.flatten! array.uniq! end def unpreload_associations @@unpreload_associations ||= {} end def unused_preload_associations @@unused_preload_associations ||= {} end def object_associations @@object_associations ||= {} end def call_object_associations @@call_object_associations ||= {} end def possible_objects @@possible_objects ||= {} end def impossible_objects @@impossible_objects ||= {} end def klazz_associations @@klazz_associations ||= {} end def eager_loadings @@eager_loadings ||= {} end VENDOR_ROOT = File.join(RAILS_ROOT, 'vendor') def caller_in_project callers << caller.select {|c| c =~ /#{RAILS_ROOT}/}.reject {|c| c =~ /#{VENDOR_ROOT}/} callers.uniq! end def callers @@callers ||= [] end end end end