diff options
Diffstat (limited to 'activerecord/lib/active_record')
-rw-r--r-- | activerecord/lib/active_record/associations/collection_association.rb | 12 | ||||
-rw-r--r-- | activerecord/lib/active_record/base.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/explain.rb | 140 | ||||
-rw-r--r-- | activerecord/lib/active_record/fixtures.rb | 1 | ||||
-rw-r--r-- | activerecord/lib/active_record/railties/databases.rake | 59 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/scoping/named.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/version.rb | 4 |
8 files changed, 121 insertions, 101 deletions
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 207080973c..fe9f30bd2a 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -49,10 +49,18 @@ module ActiveRecord end else column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}" + relation = scoped - scoped.select(column).map! do |record| - record.send(reflection.association_primary_key) + including = (relation.eager_load_values + relation.includes_values).uniq + + if including.any? + join_dependency = ActiveRecord::Associations::JoinDependency.new(reflection.klass, including, []) + relation = join_dependency.join_associations.inject(relation) do |r, association| + association.join_relation(r) + end end + + relation.uniq.pluck(column) end end 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 + else + yield 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 + # 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") + # 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 + 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 + # 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/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index d8340bf1c9..da72d1718e 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -10,7 +10,6 @@ require 'zlib' require 'active_support/dependencies' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/logger' require 'active_support/ordered_hash' require 'active_record/fixtures/file' diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 0aafb5184f..199eee4359 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -371,36 +371,37 @@ db_namespace = namespace :db do end namespace :structure do - desc 'Dump the database structure to an SQL file' + desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql' task :dump => :environment do abcs = ActiveRecord::Base.configurations + filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql") case abcs[Rails.env]['adapter'] when /mysql/, 'oci', 'oracle' ActiveRecord::Base.establish_connection(abcs[Rails.env]) - File.open("#{Rails.root}/db/structure.sql", "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump } + File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump } when /postgresql/ set_psql_env(abcs[Rails.env]) search_path = abcs[Rails.env]['schema_search_path'] unless search_path.blank? search_path = search_path.split(",").map{|search_path_part| "--schema=#{search_path_part.strip}" }.join(" ") end - `pg_dump -i -s -x -O -f db/structure.sql #{search_path} #{abcs[Rails.env]['database']}` + `pg_dump -i -s -x -O -f #{filename} #{search_path} #{abcs[Rails.env]['database']}` raise 'Error dumping database' if $?.exitstatus == 1 when /sqlite/ dbfile = abcs[Rails.env]['database'] - `sqlite3 #{dbfile} .schema > db/structure.sql` + `sqlite3 #{dbfile} .schema > #{filename}` when 'sqlserver' - `smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f db\\structure.sql -A -U` + `smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f #{filename} -A -U` when "firebird" set_firebird_env(abcs[Rails.env]) db_string = firebird_db_string(abcs[Rails.env]) - sh "isql -a #{db_string} > #{Rails.root}/db/structure.sql" + sh "isql -a #{db_string} > #{filename}" else raise "Task not supported by '#{abcs[Rails.env]["adapter"]}'" end if ActiveRecord::Base.connection.supports_migrations? - File.open("#{Rails.root}/db/structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information } + File.open(filename, "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information } end end @@ -409,30 +410,31 @@ db_namespace = namespace :db do env = ENV['RAILS_ENV'] || 'test' abcs = ActiveRecord::Base.configurations + filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql") case abcs[env]['adapter'] when /mysql/ ActiveRecord::Base.establish_connection(abcs[env]) ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0') - IO.read("#{Rails.root}/db/structure.sql").split("\n\n").each do |table| + IO.read(filename).split("\n\n").each do |table| ActiveRecord::Base.connection.execute(table) end when /postgresql/ set_psql_env(abcs[env]) - `psql -f "#{Rails.root}/db/structure.sql" #{abcs[env]['database']} #{abcs[env]['template']}` + `psql -f "#{filename}" #{abcs[env]['database']} #{abcs[env]['template']}` when /sqlite/ dbfile = abcs[env]['database'] - `sqlite3 #{dbfile} < "#{Rails.root}/db/structure.sql"` + `sqlite3 #{dbfile} < "#{filename}"` when 'sqlserver' - `sqlcmd -S #{abcs[env]['host']} -d #{abcs[env]['database']} -U #{abcs[env]['username']} -P #{abcs[env]['password']} -i db\\structure.sql` + `sqlcmd -S #{abcs[env]['host']} -d #{abcs[env]['database']} -U #{abcs[env]['username']} -P #{abcs[env]['password']} -i #{filename}` when 'oci', 'oracle' ActiveRecord::Base.establish_connection(abcs[env]) - IO.read("#{Rails.root}/db/structure.sql").split(";\n\n").each do |ddl| + IO.read(filename).split(";\n\n").each do |ddl| ActiveRecord::Base.connection.execute(ddl) end when 'firebird' set_firebird_env(abcs[env]) db_string = firebird_db_string(abcs[env]) - sh "isql -i #{Rails.root}/db/structure.sql #{db_string}" + sh "isql -i #{filename} #{db_string}" else raise "Task not supported by '#{abcs[env]['adapter']}'" end @@ -444,26 +446,39 @@ db_namespace = namespace :db do end namespace :test do - # desc "Recreate the test database from the current schema.rb" + + # desc "Recreate the test database from the current schema" task :load => 'db:test:purge' do - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) - ActiveRecord::Schema.verbose = false - db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby + case ActiveRecord::Base.schema_format + when :ruby + db_namespace["test:load_schema"].invoke + when :sql + db_namespace["test:load_structure"].invoke + end + end + # desc "Recreate the test database from an existent structure.sql file" + task :load_structure => 'db:test:purge' do begin old_env, ENV['RAILS_ENV'] = ENV['RAILS_ENV'], 'test' - db_namespace["structure:load"].invoke if ActiveRecord::Base.schema_format == :sql + db_namespace["structure:load"].invoke ensure ENV['RAILS_ENV'] = old_env end + end + # desc "Recreate the test database from an existent schema.rb file" + task :load_schema => 'db:test:purge' do + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) + ActiveRecord::Schema.verbose = false + db_namespace["schema:load"].invoke end - # desc "Recreate the test database from the current environment's database schema" - task :clone => %w(db:schema:dump db:test:load) + # desc "Recreate the test database from a fresh schema.rb file" + task :clone => %w(db:schema:dump db:test:load_schema) - # desc "Recreate the test databases from the structure.sql file" - task :clone_structure => [ "db:structure:dump", "db:test:load" ] + # desc "Recreate the test database from a fresh structure.sql file" + task :clone_structure => [ "db:structure:dump", "db:test:load_structure" ] # desc "Empty the test database" task :purge => :environment do 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 diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index f7512bbf5f..17122740da 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -177,7 +177,7 @@ module ActiveRecord extension = Module.new(&Proc.new) if block_given? scope_proc = lambda do |*args| - options = scope_options.respond_to?(:call) ? scope_options.call(*args) : scope_options + options = scope_options.respond_to?(:call) ? unscoped { scope_options.call(*args) } : scope_options options = scoped.apply_finder_options(options) if options.is_a?(Hash) relation = scoped.merge(options) diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index 838aa8fb1e..0c35adc11d 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -1,7 +1,7 @@ module ActiveRecord module VERSION #:nodoc: - MAJOR = 3 - MINOR = 2 + MAJOR = 4 + MINOR = 0 TINY = 0 PRE = "beta" |