diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b479c403a7..432a40ea54 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -699,7 +699,7 @@ module ActiveRecord #:nodoc: include Associations include IdentityMap include ActiveModel::SecurePassword - include Explain + extend Explain # AutosaveAssociation needs to be included before Transactions, because we want # #save_with_autosave_associations to be wrapped inside a transaction. diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index c9e85391cd..b64390250d 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -1,85 +1,83 @@ -require 'active_support/concern' +require 'active_support/core_ext/class/attribute' module ActiveRecord module Explain - extend ActiveSupport::Concern - - included do - # If a query takes longer than these many seconds we log its query plan - # automatically. nil disables this feature. - class_attribute :auto_explain_threshold_in_seconds, :instance_writer => false - self.auto_explain_threshold_in_seconds = nil + def self.extended(base) + base.class_eval do + # If a query takes longer than these many seconds we log its query plan + # automatically. nil disables this feature. + class_attribute :auto_explain_threshold_in_seconds, :instance_writer => false + self.auto_explain_threshold_in_seconds = nil + end end - module ClassMethods - # If auto explain is enabled, this method triggers EXPLAIN logging for the - # queries triggered by the block if it takes more than the threshold as a - # whole. That is, the threshold is not checked against each individual - # query, but against the duration of the entire block. This approach is - # convenient for relations. - # - # The available_queries_for_explain thread variable collects the queries - # to be explained. If the value is nil, it means queries are not being - # currently collected. A false value indicates collecting is turned - # off. Otherwise it is an array of queries. - def logging_query_plan # :nodoc: - threshold = auto_explain_threshold_in_seconds - current = Thread.current - if threshold && current[:available_queries_for_explain].nil? - begin - queries = current[:available_queries_for_explain] = [] - start = Time.now - result = yield - logger.warn(exec_explain(queries)) if Time.now - start > threshold - result - ensure - current[:available_queries_for_explain] = nil - end - else - yield + # If auto explain is enabled, this method triggers EXPLAIN logging for the + # queries triggered by the block if it takes more than the threshold as a + # whole. That is, the threshold is not checked against each individual + # query, but against the duration of the entire block. This approach is + # convenient for relations. + # + # The available_queries_for_explain thread variable collects the queries + # to be explained. If the value is nil, it means queries are not being + # currently collected. A false value indicates collecting is turned + # off. Otherwise it is an array of queries. + def logging_query_plan # :nodoc: + threshold = auto_explain_threshold_in_seconds + current = Thread.current + if threshold && current[:available_queries_for_explain].nil? + begin + queries = current[:available_queries_for_explain] = [] + start = Time.now + result = yield + logger.warn(exec_explain(queries)) if Time.now - start > threshold + result + ensure + current[:available_queries_for_explain] = nil end - end - - # Relation#explain needs to be able to collect the queries regardless of - # whether auto explain is enabled. This method serves that purpose. - def collecting_queries_for_explain # :nodoc: - current = Thread.current - original, current[:available_queries_for_explain] = current[:available_queries_for_explain], [] - return yield, current[:available_queries_for_explain] - ensure - # Note that the return value above does not depend on this assigment. - current[:available_queries_for_explain] = original - end - - # Makes the adapter execute EXPLAIN for the tuples of queries and bindings. - # Returns a formatted string ready to be logged. - def exec_explain(queries) # :nodoc: - queries && queries.map do |sql, bind| - [].tap do |msg| - msg << "EXPLAIN for: #{sql}" - unless bind.empty? - bind_msg = bind.map {|col, val| [col.name, val]}.inspect - msg.last << " #{bind_msg}" - end - msg << connection.explain(sql, bind) - end.join("\n") - end.join("\n") - end - - # Silences automatic EXPLAIN logging for the duration of the block. - # - # This has high priority, no EXPLAINs will be run even if downwards - # the threshold is set to 0. - # - # As the name of the method suggests this only applies to automatic - # EXPLAINs, manual calls to +ActiveRecord::Relation#explain+ run. - def silence_auto_explain - current = Thread.current - original, current[:available_queries_for_explain] = current[:available_queries_for_explain], false + else yield - ensure - current[:available_queries_for_explain] = original end end + + # Relation#explain needs to be able to collect the queries regardless of + # whether auto explain is enabled. This method serves that purpose. + def collecting_queries_for_explain # :nodoc: + current = Thread.current + original, current[:available_queries_for_explain] = current[:available_queries_for_explain], [] + return yield, current[:available_queries_for_explain] + ensure + # Note that the return value above does not depend on this assigment. + current[:available_queries_for_explain] = original + end + + # Makes the adapter execute EXPLAIN for the tuples of queries and bindings. + # Returns a formatted string ready to be logged. + def exec_explain(queries) # :nodoc: + queries && queries.map do |sql, bind| + [].tap do |msg| + msg << "EXPLAIN for: #{sql}" + unless bind.empty? + bind_msg = bind.map {|col, val| [col.name, val]}.inspect + msg.last << " #{bind_msg}" + end + msg << connection.explain(sql, bind) + end.join("\n") + end.join("\n") + end + + # Silences automatic EXPLAIN logging for the duration of the block. + # + # This has high priority, no EXPLAINs will be run even if downwards + # the threshold is set to 0. + # + # As the name of the method suggests this only applies to automatic + # EXPLAINs, manual calls to +ActiveRecord::Relation#explain+ run. + def silence_auto_explain + current = Thread.current + original, current[:available_queries_for_explain] = current[:available_queries_for_explain], false + yield + ensure + current[:available_queries_for_explain] = original + end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index ab2882516e..258c1959a0 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -9,7 +9,7 @@ module ActiveRecord MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind] SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order, :uniq] - include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain::ClassMethods, Delegation + include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation attr_reader :table, :klass, :loaded attr_accessor :extensions, :default_scoped